#脚本语言 #命令行 #别名 #编程语言 #REPL #命名空间 #Adana

adana-cache-command

命名空间别名,用于命令行和基本脚本语言

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日

580编程语言 中排名

Download history 181/week @ 2024-05-02 6/week @ 2024-05-16 7/week @ 2024-05-23 4/week @ 2024-05-30 8/week @ 2024-06-06 181/week @ 2024-06-13 7/week @ 2024-06-20 3/week @ 2024-06-27 14/week @ 2024-07-04 55/week @ 2024-07-25 18/week @ 2024-08-01

73 每月下载量
adana 中使用

MIT 许可证

510KB
12K 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-Strings。你可以使用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


在某些情况下,隐式使用乘法操作符是合法的。只有在数字(int,小数)和变量名之间没有空格时,它才会工作。

示例

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循环中,如果满足特定条件,您可以跳出循环

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
导出 将命名空间以 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
帮助 显示帮助信息。

快捷键

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

依赖项

约 5–19MB
约 270K SLoC