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日

#396 in 编程语言

Download history 184/week @ 2024-05-02 7/week @ 2024-05-16 9/week @ 2024-05-23 4/week @ 2024-05-30 9/week @ 2024-06-06 191/week @ 2024-06-13 11/week @ 2024-06-20 1/week @ 2024-06-27 1/week @ 2024-07-04 123/week @ 2024-07-25 35/week @ 2024-08-01

159 个月下载量
用于 2 个crate

MIT 许可证

62KB
974

Adana

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

目录

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

简介

这个项目最初是作为一种将阅读Rust编程语言书籍中学到的知识付诸实践的方式。

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

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

如果您想贡献,您的pull request会受到欢迎。

这个名字从哪里来?

我最喜欢的菜 😋

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-字符串添加参数

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)
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 转换为整型 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 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 不完整

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

# 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