67 个版本

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.10.0 2022 年 7 月 28 日

#16 in 编程语言

Download history 182/week @ 2024-04-29 7/week @ 2024-05-20 4/week @ 2024-06-03 177/week @ 2024-06-10 21/week @ 2024-06-17 473/week @ 2024-07-29 129/week @ 2024-08-12

602 每月下载量

MIT 许可证

560KB
12K SLoC

阿达纳

脚本编程语言,命令的 repl 和命名空间别名。

目录

  1. 简介
  2. 安装
  3. 编程语言
  4. 命名空间别名

简介

这个项目最初是为了将我在阅读 Rust 编程语言书籍时所学到的知识付诸实践。

它包含了我认为有用的功能,例如 REPL、计算器、脚本语言以及根据我正在进行的项目的存储、执行和加载命令行别名的方式。

最佳实践和性能优化不是优先考虑的事项,因此代码可能不是最简洁或最优化。

如果您想做出贡献,您的拉取请求将受到欢迎。

名字的由来?

我最喜欢的菜肴 😋

image


安装

  1. Docker
    • 来自 Docker Hub
      • docker run-它 nbittich/adana#master 上的最新版本
      • docker run-它 nbittich/adana:v0.17.11 #最新版本
    • 手动
      • 克隆仓库
      • 构建 Docker 镜像: docker build -t adana .
      • docker run-它 adana
  2. Cargo
    • 来自 crate.io
      • cargo安装 adana
      • adana
    • 手动
      • cargo构建 --发布
      • ./target/release/adana
  3. 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-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 文件,它将自动加载它。

这个repo中有一个插件的示例(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("无法处理...")

结构体

您可以定义结构体。结构体是分组相关变量或函数的一种方式。您可以在结构体中定义函数变量,但不能在结构体内部更新函数的成员(没有selfthis)。

每个成员之间需要用逗号分隔,但最后一个成员除外。

定义结构体的示例

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,用于在repl中包含脚本的 include,以及用于打印内容的 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 打印不换行 print("hello")
println 打印并换行 println("hello")
include 包含一个脚本 include("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 转换为字符串 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 将命名空间中的一个值作为操作系统命令运行。这是完全可选的,如果您只写别名,它也会工作,例如 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 12022023store_script_ctx
load_script_ctx 加载脚本上下文(可选名称)例如 load_script_ctx 12022023load_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 NOT EXHAUSTIVE

在不进入 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

依赖项

~8–22MB
~334K SLoC