#command-line #scripting-language #repl #namespaces #aliases #adana #script

adana-script

命令行和基本脚本语言的命名空间别名

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.12024年2月7日

#261 in 数学

Download history 177/week @ 2024-04-28 8/week @ 2024-05-05 2/week @ 2024-05-12 19/week @ 2024-05-19 9/week @ 2024-05-26 14/week @ 2024-06-02 158/week @ 2024-06-09 51/week @ 2024-06-16 6/week @ 2024-06-23 1/week @ 2024-06-30 4/week @ 2024-07-07 1/week @ 2024-07-21 147/week @ 2024-07-28 124/week @ 2024-08-11

272 每月下载量
用于 3 crate

MIT 许可证

380KB
10K SLoC

Adana

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

目录

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

介绍

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

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

最佳实践和性能优化不是优先事项,因此代码可能不是最干净或最优化的。

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

名字从哪里来?

我最喜欢的菜肴 😋

image


安装

  1. Docker
    • 来自docker hub
      • docker run-运行nbittich/adana#从master的最新版本
      • docker run-运行nbittich/adana:v0.17.10 #最新版本
    • 手动
      • 克隆仓库
      • 构建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
< 小于
> 大于
<= 小于或等于
>= 大于或等于
&&
||
| 位或
~ 位非
@ 位与
$ 位异或
<< 位左移
>> 位右移
== 等于
() 括号
π π数
γ 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.10/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循环不需要括号。您只能遍历struct、字符串和数组。

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)
}

在struct的情况下,变量将是一个条目(键/值形式的struct)

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 来获取数组或字符串的长度,使用 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 无换行符的打印 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")

请注意,您可以使用 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 将命名空间以JSON格式导出。可带可选参数,即命名空间名称。例如:dump linux
清除 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 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

依赖项

~4–13MB
~147K SLoC