12 个版本
新版本 0.17.11 | 2024年8月15日 |
---|---|
0.17.10 | 2024年6月15日 |
0.17.9 | 2024年5月2日 |
0.17.8 | 2024年3月21日 |
0.16.5-rc.1 | 2024年2月7日 |
#912 in 编程语言
74 每月下载次数
130KB
2.5K SLoC
Adana
脚本编程语言,交互式编程环境以及命令的命名空间别名。
目录
简介
这个项目最初是为了将我在阅读Rust编程语言书籍时所学的知识付诸实践而开始的。
它包含了我认为有用的功能,例如交互式编程环境、计算器、脚本语言以及基于我正在工作的项目存储、执行和加载命令行别名的功能。
最佳实践和性能优化不是优先考虑的事项,因此代码可能不是最简洁或最优化。
如果您想做出贡献,您的拉取请求会受到欢迎。
名字的由来是什么?
我最喜欢的菜 😋
安装
- Docker
- 来自docker hub
docker run-it nbittich/adana#从master的最新版本
docker run-it nbittich/adana:v0.17.11 #最新版本
- 手动
- 克隆仓库
- 构建Docker镜像:
docker build -t adana .
docker run-it adana
- 来自docker hub
- Cargo
- 来自crate.io
cargo安装adana
adana
- 手动
cargo构建 --发布
./target/release/adana
- 来自crate.io
- WASM沙盒
编程语言
入门
首先,我们从传统的hello world开始
println("hello world!") # prints hello world
在交互式编程环境中,您也可以简单地写下
"hello world!" # prints hello world
注释
注释的定义类似于Python,以 #
开头。您可以将它们放在最后一条语句之后或任何有用的代码之前,例如
# to go to the next line in the repl, press CTRL+x
# this will be ignored by the repl
println("hello world!") # this is also ok
多行
不需要分号,如果需要多行语句,可以使用多行块
fancy_string = multiline {
"this string\n" +
"\tis\n" +
"\t\ton several\n" +
"lines\n"
}
多行在需要处理多行不同指令时非常有用
complex_math_stuff = multiline {
1 *2
+ 5 *sqrt(2) / 2.
+ 300 / 3
* 400 % 2 - (1 * 10^3)
}
F-字符串
对于更复杂的字符串,您可以使用字符串块/F-字符串。您可以使用Java语法来定义它们
block_string= """Hello world
I hope you are well.
This is a string block. you can use stuff like "string"
there, nothing will stop you"""
就像在javascript中一样,您可以向f-string添加参数
person = struct {
name : "nordine",
wasup : (age) => {
if (age > 30) {
"you are old!"
} else {
"you are young!"
}
},
age : 34
}
s1 = """Hello ${person.name}!
You are ${person.age} years old.
${person.wasup(person.age)}"""
操作符和常量
共有22个运算符和3个常量
运算符 | 描述 |
---|---|
+ |
加 |
- |
减 |
/ |
除 |
* |
乘 |
% |
取模 |
^ |
幂 |
² |
幂 2 |
³ |
幂 3 |
< |
小于 |
> |
大于 |
<= |
小于等于 |
>= |
大于等于 |
&& |
与 |
|| |
或 |
| |
位或 |
~ |
位非 |
@ |
位与 |
$ |
位异或 |
<< |
位左移 |
>> |
位右移 |
== |
等于 |
() |
括号 |
π |
PI 数 |
γ |
EULER 数 |
τ |
TAU 数 |
示例
5 + 5 # 10
5 + 5.5 # 10.5
5 / 5 # 1
5 / 6 # 0
5 / 6. # 0.8333333333333334 -- we force it to make a float division by adding "."
5 % 6 # 5 -- modulo on int
5 % 4.1 # 0.9000000000000004 modulo on double
5 ^ 5 # 3125
5 * 5 # 25
5 * 5.1 # 25.5
5 * (5+ 1/ (3.1 ^2) * 9) ^3. # 1046.084549281999
2² # 4
2³ # 8
您可以在重新分配变量之前应用运算符,例如
x =2
x+=1 # 3
x-=2 # 1
x*=4 # 4
x%=3 # 1
x/=0.5 # 2
在某些情况下,使用乘法运算符隐式地是合法的。它仅在数字(整型、小数)和变量名之间没有空格时才会工作。
示例
x=2
3x²+2x== x*(3x+2) # true
y=0.5x # 1
变量定义
要定义变量,只需键入变量的名称后跟"="。变量必须始终以字母开头,可以包含数字或"_"。加和赋值(+=)、减和赋值(-=)等也是支持的。
vat = 1.21 # 1.21
sub_total1 = 10000
total = vat * sub_total1 # 12100
sub_total2 = 500 * vat # 605
sub_total1 = sub_total1 + sub_total2 # 10605
可以简化为以下形式
vat = 1.21 # 1.21
sub_total1 = 10000
total = vat * sub_total1 # 12100
sub_total2 = 500 * vat # 605
sub_total1 += sub_total2 # 10605
还可以使用特殊的变量名"_"来通知语言,该值未使用且不需要存储在上下文中
_ = 1
for _, n in 1..3 {
println(n)
}
_ = struct {
_: "I will not be stored!",
x: 39
}
内存管理
默认情况下,所有内容都会被克隆。作为一个业余项目,这是一个简单的方法来实现更多功能和乐趣。
现在,由于已经构建了足够的功能,已经开始尝试实现自动的引用计数。
这是高度实验性的,可能需要部分或全部重写AST/解析器来实现它。为了保持乐趣,现在不是优先事项。
您可以这样定义引用
x = 100
y = &x # y points to x, no clone
p = 0
for _ in 0..&x {
p = p+1
}
x = 99
y = &x
x = 100 # now y == 100
插件
您还可以动态加载用rust编写的插件。由于Rust还没有稳定的ABI,插件必须使用与构建adana相同的版本构建。
运行repl时指定rust版本。
要动态加载库,您可以指定相对路径或绝对路径。如果是相对路径,它应该是相对于共享库路径的(默认:`$HOME/.local/share/adana/db`)。
您可以通过在启动repl时提供路径来覆盖此设置(例如:`adana -slp /tmp`)。
如果路径是目录,它将尝试使用`cargo`构建它,因此您需要在机器上安装rust。
如果是`.so`文件,它将自动加载它。
示例插件可以在这个仓库中找到(`dynamic_lib/example_lib_src`)。
例如
- 将SO文件复制到tmp中:
cp dynamic_lib/libplugin_example.so /tmp/
- 运行并覆盖库路径:
adana -slp /tmp
- 执行以下操作
lib = require("libplugin_example.so")
text = lib.hello("Nordine", "la", "forme?")
或者在一行中
text = require("libplugin_example.so").hello("Nordine", "la", "forme?")
标准库
基本标准库存在这里。
您可以使用这种方式使用它
fs = require("@std/fs")
fs.api_description() # description of the api
如果尚未安装,您将看到如何安装它的说明,例如
[rust~/toyprograms/adana(master)] fs = require("@std/fs")
std lib doesn't exist: "/home/nbittich/.local/share/adana/lib/adana-std/fs.so".
Try to install it like so:
- wget -P /tmp https://github.com/nbittich/adana-std/releases/download/0.17.11/adana-std.tar.gz
- mkdir /home/nbittich/.local/share/adana/lib/adana-std && tar xvzf /tmp/adana-std.tar.gz \
-C /home/nbittich/.local/share/adana/lib/adana-std
循环
有两种循环:while 循环和 for-each 循环。while 循环看起来像 C 中的循环,而 for-each 循环则更加现代一些。
for-each 循环和 while 循环不需要括号。你只能遍历结构体、字符串和数组。
count = 0
while(count < 10) {
println(count)
count = count + 1
}
# also valid
while count < 10 {
println(count)
count = count + 1
}
for n in [1,2,3] {
println(n)
}
在 for-each 循环中,你可以访问当前索引。
for index, n in [1, 2, 3] {
println("index: " + index + " value: " + n)
}
你也可以使用 for-each 循环来遍历字符串。
for i, letter in "hello" {
println(i)
}
对于结构体来说,变量将是一个条目(一个带有键/值的结构体)。
s = struct {
name: "nordine",
age: 34,
members: ["natalie", "roger","fred"]
}
for id, entry in s {
println("Id: "+id +" Key: "+entry.key + " Value: " + to_string(entry.value))
}
for-each 循环中的括号是可选的。
arr = [1,2,3,4]
total = 0
idx_total = 0
for (index,a in arr) {
total = total + a
idx_total = idx_total + index
}
如果你在 while 循环中满足某个条件,可以使用 break 来跳出循环。
while count < 10 {
println(count)
count = count + 1
if(count % 3 ==0) {
break
}
}
范围
你可以定义一个范围,如 "start..end"(不包括 end)或 "start..=end"(包括 end)。
x = 0..10 # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
x = 0..=10 # [0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in 1..=10 {
println("print 10 times this message")
}
条件
与 C 语言相同,但括号是可选的。
if age > 12 {
println("age > 12")
} else if age < 9 {
println("age < 9")
} else {
println("dunno")
}
类型
该语言中没有类型检查。你可以将字符串添加到数组中,没有任何东西可以阻止你这么做!
但在某些情况下,你可能会遇到错误。
以下是一个类型列表以及它们的声明方式。你还可以定义自己的结构体。
类型 | 示例 |
---|---|
null | null |
bool | true / false |
int | 5000 |
u8 | 5 |
i8 | -5 |
double | 12. / 12.2 |
string | "hello" |
array | [1,2,"3", true] |
function | () => {"hello"} (name) => {"hello" +name} (n) => { "hello" } |
struct | struct {x: 8, y()=> {println("hello!")}} |
error | make_err("无法处理...") |
结构体
你可以定义结构体。结构体是将相关变量或函数组合在一起的方式。你可以在结构体内部定义函数变量,但无法在结构体内部更新函数的成员(没有 self
或 this
)。
逗号用于分隔每个成员,但最后一个成员除外。
定义结构体的示例
person = struct {
name: "hello",
age: 20
}
person_service = struct {
say_hi: (person) => { println("hi " + person.name) },
check_age: (person) => {
if (person.age < 18) {
println("you are too young")
} else {
println("you are too old")
}
}
}
person_service.check_age(person)
你可以以两种方式访问结构体
name = person["name"] # name contains "hello"
println(person["age"])
age=person.age # age contains "age"
println(person.age)
操作数组
数组声明方式类似于 JavaScript,但它们是 "不可变的"。声明后,你无法(目前)向其中推送新数据。要这样做,你必须使用 "+" 运算符将另一个数组与它连接。
arr = [] # declare an empty array
arr[0] = "kl" # Err: index out of range
arr = arr + ["kl"] # arr now is ["kl"]
只要数组的大小大于提供的索引,你就可以使用上述语法更新数组中的值,例如
arr = ["this", "is", "ax", "array", true, 1, 2.3]
arr[2] = "an" #fix the typo
print(arr[2]) # an
要获取数组的长度,你可以使用内置函数 length
arr_len = length(arr) # 7
字符串中的字符可以像数组一样访问和更新
s = "this is a strink"
s[2] # i
length(s) # 16
s[15] = "g" # fix the typo
s # this is a string
以下是你可以使用数组的一些其他示例
count = 9
arr = null
# arr = [1, [2, [3, [4, [5, [6, [7, [8, [9, null]]]]]]]]]
while(count > 0) {
arr = [count, arr]
count = count -1
}
# prints 1,2,3,4,5,6,7,8,9,done
while(arr != null) {
print(arr[0] +",")
arr=arr[1]
}
print("done")
函数
函数可以声明为内联或块。在函数参数的情况下,你可以将函数分配给变量或使用匿名函数块。
函数内部无法修改参数。如果你想更新某个东西,你必须返回它并重新分配它。
# no parameters
hello = () => { println("hello, world!") }
hello()
# one parameter
hello_name = (name) => { println("hello "+name) }
hello_name("Bachir")
# takes an array and a function as a parameter
for_each = (arr, consumer) => {
count = 0
len = length(arr)
while(count < len) {
consumer(arr[count])
count = count + 1
}
return "" # do not print the count as the repl will print the latest statement
}
for_each(["Mohamed", "Hakim", "Sarah", "Yasmine", "Noah", "Sofia", "Sami"], hello_name)
# or for_each(["Mohamed", "Hakim", "Sarah", "Yasmine", "Noah", "Sofia", "Sami"],
(name) => { println("hello "+name) }
)
函数内部无法修改参数。如果你想更新某个东西,你必须返回它并重新分配它。函数作用域内发生的所有变化都不会对外部作用域产生影响。
以下是一些你可以使用函数的其他示例
arr = ["Mohamed", "Hakim", "Sarah", "Yasmine", "Noah", "Sofia", "Sami"]
acc = (arr, v) => {arr + [v]} # arr is immutable, thus you have to reassign it if you call that function
arr = acc(arr, "Malika")
find_first = (arr, predicate) => {
len = length(arr)
count = 0
while(count < len) {
temp = arr[count]
if(predicate(temp)) {
return temp
}
count = count + 1
}
return null
}
find_first(arr, (v) => {
v[0] == "S" || v[0] == "s"
})
# recursive
fact = (n) => {
if(n>=1) {
n * fact(n-1)
}else {
1
}
}
fact(10)
包含脚本文件
你可以在 repl 中动态加载用 adana 编写的脚本。假设你已经克隆了仓库并且使用 docker,以下是如何操作的示例。
注意扩展名可以是任何内容。
- 将示例目录映射为 docker 卷
docker run -v $PWD/file_tests:/scripts -it adana
include("scripts/test_fn.adana") # the built-in function to include
m = map()
m = push_v("nordine", 34, m)
get_v("nordine", m)
内置函数
有几种内置函数可用。
你已经看到了 length
,用于获取数组或字符串的长度,include
用于在 repl 中包含脚本,以及 println
用于打印内容。
以下是可用的内置函数列表
name | 描述 | 示例 |
---|---|---|
sqrt | 平方根 | sqrt(2) |
绝对值 | 绝对值 | 绝对值(-2) |
对数 | 对数 | 对数(2) |
自然对数 | 自然对数 | 自然对数(2) |
长度 | 数组或字符串的长度 | 长度("azert") |
正弦 | 一个数的正弦 | 正弦(2) |
余弦 | 一个数的余弦 | 余弦(2) |
正切 | 一个数的正切 | 正切(2.2) |
打印 | 不换行打印 | 打印("hello") |
println | 换行打印 | println("hello") |
包含 | 包含脚本 | 包含("scripts/test_fn.adana") |
需要 | 加载共享对象 | 需要("my_lib.so") |
to_int | 转换为int | to_int("2") to_int(2.2) |
to_hex | 将数字格式化为十六进制 | to_hex(2) to_hex(2.2) |
to_binary | 将数字格式化为二进制 | to_binary(2) |
to_double | 转换为double | to_double("2.2") |
to_bool | 转换为bool | to_bool("true") |
to_string | 转换为string | to_string(true) |
drop | 从上下文中删除变量 | drop("myvar") drop(arr[0]) |
eval | 将字符串作为代码评估 | eval("sqrt(9)") |
type_of | 变量的类型 | type_of(true) |
is_u8 | 检查是否为u8 | is_u8(0x1) |
is_i8 | 检查是否为i8 | is_i8(-1) |
is_int | 检查是否为int | is_int(512) |
is_double | 检查是否为double | is_double(1.2) |
is_function | 检查是否为函数 | is_function(()=> {1}) |
is_struct | 检查是否为结构体 | is_struct(struct {}) |
is_bool | 检查是否为bool | is_bool(false) |
is_array | 检查是否为数组 | is_bool([1,2]) |
is_error | 检查是否为错误 | is_error(err) |
make_err | 创建一个错误 | make_err("oops") |
is_match | 检查匹配的正则表达式 | is_match("AaAaAbbBBBb", """(?i)a+(?-i)b+""") |
match | 匹配正则表达式 | match("AaAaAbbBBBb", """(?i)a+(?-i)b+""") |
匹配正则表达式
有两个内置函数用于将正则表达式与字符串匹配。您必须使用F-string ("""s""") 语法表示模式
pattern = """(?i)a+(?-i)b+"""
text = "AaAaAbbBBBb"
is_match(text, pattern) # true
# match [["Item1: $100", "Item1", "100"], ["Item2: $200", "Item2", "200"], ["Item3: $300", "Item3", "300"]]
pattern = """(\w+): \$(\d+)"""
text = "Item1: $100, Item2: $200, Item3: $300"
match(text, pattern)
请注意,您可以使用repl命令script_ctx
来查看上下文中存储的变量。
命名空间别名
您可以在单独的命名空间中别名有用的命令(例如:“work”,“git”,“docker”)。
然后您可以通过repl运行该命令。它们将被保存在磁盘上,因此您可以备份它们,恢复它们等。
您还可以添加任何类型的值(例如,ssh密钥)以存储它们。
目前还不能与脚本语言进行交互。
试试看
docker run-it-v$PWD/sample.json:/adanadb.json nbittich/adana-im
restore
use其他
ds
printenv
可用命令
name | alt | 描述 |
---|---|---|
put | N/A | 将新值放入当前命名空间。可以具有多个别名,带有选项'-a'。例如 put -a drc -a drcomp docker-compose |
alias | N/A | 使用另一个键别名。例如 alias commit gc |
describe | ds | 列出当前命名空间中的值。 |
listns | lsns | 列出可用的命名空间。 |
currentns | currentns | 打印当前命名空间。 |
backup | bckp | 将命名空间的数据库备份到当前目录 |
flush | flush | 强制刷新数据库 |
restore | N/A | 从当前目录恢复数据库 |
deletens | delns | 删除命名空间或清除当前命名空间的值。 |
mergens | merge | 合并当前命名空间与给定的命名空间 |
delete | del | 从命名空间中删除值。例如 del drc |
get | 从命名空间中获取值。例如 get drc |
|
clip | clippy | 从命名空间获取值并将其复制到剪贴板。例如 clip drc |
exec | 将命名空间中的值作为OS命令运行。如果只写别名,它也将正常工作,例如 exec drc 或简单地 drc |
|
cd | 在文件系统中导航到目录 | |
use | 切换到另一个命名空间。默认ns是DEFAULT。例如 use linux |
|
dump | 将命名空间(s)作为JSON转储。可选参数,命名空间名称。例如 dump linux |
|
清除 | cls | 清除终端。 |
print_script_ctx | script_ctx | 打印脚本上下文 |
store_script_ctx | 存储脚本上下文(可选名称)例如:store_script_ctx 12022023 或 store_script_ctx |
|
load_script_ctx | 加载脚本上下文(可选名称)例如:load_script_ctx 12022023 或 load_script_ctx |
|
ast | 打印脚本代码的AST例如:ast 9*9 |
|
help | 显示帮助。 |
快捷键
CTRL + x => new line in the repl
CTRL + d => quit
CTRL + c => undo
CTRL + l => clear screen
CTRL + r => history search
CTRL + p => π
环境变量
RUST_LOG=adana=debug adana
参数
TODO 不完整
在不进入REPL的情况下运行脚本
# using file
adana -sp /path/to/script.adana
# using code
adana -e 1+1
# open an in memory db
adana --inmemory
# override db path & history path + no fallback in memory in case of an error (default to false)
# path must exist! file doesn't have to.
adana --dbpath /tmp/mydb.db --historypath /tmp/myhistory.txt --nofb
# specify shared lib path
adana -slp /tmp/shared
依赖
~0–7.5MB
~44K SLoC