23个版本
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.15.1 | 2023年10月6日 |
#610 in 编程语言
446 次每月下载
用于 8 crates
125KB
2.5K SLoC
Adana
脚本编程语言,repl和命令的命名空间别名。
目录
简介
这个项目最初是作为实践我在阅读Rust编程语言书籍时所学到知识的方式开始的。
它包括了我认为有用的功能,例如REPL、计算器、脚本语言以及根据我所工作的项目存储、执行和加载命令行别名的功能。
最佳实践和性能优化不是优先考虑的事项,因此代码可能不是最干净或最优化。
如果您想贡献,您的拉取请求将受到欢迎。
名字的由来?
我最喜欢的菜 😋
安装
- 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
在repl中,您也可以简单地写下
"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-字符串中添加参数
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个常量
运算符 | 描述 |
---|---|
+ |
加 |
- |
减 |
/ |
除 |
* |
乘 |
% |
取模 |
^ |
幂 |
² |
pow 2 |
³ |
pow 3 |
< |
小于 |
> |
大于 |
<= |
小于或等于 |
>= |
大于或等于 |
&& |
与 |
|| |
或 |
| |
位或 |
~ |
位非 |
@ |
位与 |
$ |
位异或 |
<< |
位左移 |
>> |
位右移 |
== |
等于 |
() |
括号 |
π |
π数 |
γ |
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
)。
例如
- 在tmp中复制SO文件:
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) |
abs | 绝对值 | abs(-2) |
log | 对数 | log(2) |
ln | 自然对数 | ln(2) |
length | 数组或字符串的长度 | length("azert") |
sin | 数字的正弦值 | sin(2) |
cos | 数字的余弦值 | cos(2) |
tan | 数字的正切值 | tan(2.2) |
打印时不换行 | print("hello") |
|
println | 打印时换行 | println("hello") |
包含 | 包含脚本 | 包含("scripts/test_fn.adana") |
require | 加载共享对象 | require("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 | 检查是否为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
usemisc
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 | 切换到另一个命名空间。默认命名空间为DEFAULT。例如 use linux |
|
dump | 将命名空间(s)作为json转储。可选参数,命名空间名称。例如 dump linux |
|
clear | 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
参数
待办事项:非详尽
在不进入交互式解释器的情况下运行脚本
# 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
依赖项
~3.5–10MB
~94K SLoC