#脚本语言 #交互式编程环境 #别名 #命令行 #命名空间 #执行命令 #计算器

adana-script-wasm

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

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日

#912 in 编程语言

Download history 177/week @ 2024-05-02 3/week @ 2024-05-16 4/week @ 2024-05-23 2/week @ 2024-05-30 4/week @ 2024-06-06 176/week @ 2024-06-13 6/week @ 2024-06-20 57/week @ 2024-07-25 17/week @ 2024-08-01

74 每月下载次数

MIT 许可证

130KB
2.5K SLoC

Adana

脚本编程语言,交互式编程环境以及命令的命名空间别名。

目录

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

简介

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

它包含了我认为有用的功能,例如交互式编程环境、计算器、脚本语言以及基于我正在工作的项目存储、执行和加载命令行别名的功能。

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

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

名字的由来是什么?

我最喜欢的菜 😋

image


安装

  1. 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
  2. Cargo
    • 来自crate.io
      • cargo安装adana
      • adana
    • 手动
      • cargo构建 --发布
      • ./target/release/adana
  3. 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("无法处理...")

结构体

你可以定义结构体。结构体是将相关变量或函数组合在一起的方式。你可以在结构体内部定义函数变量,但无法在结构体内部更新函数的成员(没有 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)
绝对值 绝对值 绝对值(-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 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 不完整

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