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日 |
#396 in 编程语言
159 个月下载量
用于 2 个crate
62KB
974 行
Adana
脚本编程语言,repl和命令的命名空间别名。
目录
简介
这个项目最初是作为一种将阅读Rust编程语言书籍中学到的知识付诸实践的方式。
它包括了我认为有用的功能,例如REPL、计算器、脚本语言以及根据我所做的项目存储、执行和加载命令行别名的方法。
最佳实践和性能优化不是优先考虑的事项,因此代码可能不是最干净或最优化。
如果您想贡献,您的pull request会受到欢迎。
这个名字从哪里来?
我最喜欢的菜 😋
安装
- Docker
- 来自Docker Hub
docker run-运行 nbittich/adana#从master的最新版本
docker run-运行 nbittich/adana:v0.17.11 #最新版本
- 手动
- 克隆仓库
- 构建Docker镜像:
docker build -t adana .
docker run-运行 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个常量
运算符 | 描述 |
---|---|
+ |
加 |
- |
减 |
/ |
除 |
* |
乘 |
% |
取模 |
^ |
幂 |
² |
幂 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) |
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") |
include | 包含脚本 | include("scripts/test_fn.adana") |
require | 加载共享对象 | require("my_lib.so") |
to_int | 转换为整型 | to_int("2") to_int(2.2) |
to_hex | 格式化为十六进制数 | to_hex(2) to_hex(2.2) |
to_binary | 格式化为二进制数 | to_binary(2) |
to_double | 转换为双精度浮点数 | to_double("2.2") |
to_bool | 转换为布尔值 | to_bool("true") |
to_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 | 检查是否为整型 | is_int(512) |
is_double | 检查是否为双精度浮点型 | is_double(1.2) |
is_function | 检查是否为函数 | is_function(()=> {1}) |
is_struct | 检查是否为结构体 | is_struct(struct {}) |
is_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
参数
TODO 不完整
在不进入交互式解释器的情况下运行脚本
# 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.8–11MB
~75K SLoC