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 编程语言

Download history 20/week @ 2024-05-04 10/week @ 2024-05-11 26/week @ 2024-05-18 25/week @ 2024-05-25 30/week @ 2024-06-01 21/week @ 2024-06-08 212/week @ 2024-06-15 28/week @ 2024-06-22 6/week @ 2024-06-29 12/week @ 2024-07-06 20/week @ 2024-07-13 6/week @ 2024-07-20 253/week @ 2024-07-27 17/week @ 2024-08-03 129/week @ 2024-08-10 47/week @ 2024-08-17

446 次每月下载
用于 8 crates

MIT 许可证

125KB
2.5K SLoC

Adana

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

目录

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

简介

这个项目最初是作为实践我在阅读Rust编程语言书籍时所学到知识的方式开始的。

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

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

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

名字的由来?

我最喜欢的菜 😋

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

在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("无法处理...")

结构体

您可以为结构体定义结构体。结构体是将相关的变量或函数分组的一种方式。您可以在结构体内部定义函数变量,但不能在结构体内部更新函数的成员(没有 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")
包含 包含脚本 包含("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 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

参数

待办事项:非详尽

在不进入交互式解释器的情况下运行脚本

# 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