2. 语法参考

2.1. 基本类型

脚本语言支持以下基本类型

  • bool

  • int

  • double

  • str

  • function

  • None

在一些特殊的式子中,可以使用

  • 正则表达式\...\

2.1.1. 变量

2.1.1.1. 普通变量

  • 和大多数语言一样,变量可以是中文字符,大小写字符,数字,和下划线(_)的组合,但是变量不允许由数字开头。
    一个更精确的表达如下:

CN_ZH_LETTER: /[\u4e00-\u9fa5]/
LETTER: UCASE_LETTER | LCASE_LETTER | CN_ZH_LETTER
NAME : ("_"|LETTER) ("_"|LETTER|DIGIT)*
  • 变量不需要声明即可使用。

2.1.1.2. 作用域

与大多数语言不同,这里变量没有作用域。如果出现重名会产生覆盖的问题。

2.1.1.3. $变量

命令行传参和正则表达式结果可以通过$轻松获得。

2.1.1.3.1. 命令行传参

命令行传参只能在编译后的文件上传入。
首先需要通过arg_seq_addarg_option_add显式地定义参数。

arg_seq_add("arg1") ## 第一个顺序参数
arg_seq_add("arg2") ## 第二个
arg_option_add("option","o") ## --option 或者 -o

这样,编译运行后,我们就可以通过\(快速访问这些变量。 例如,对于`arg1`,你可以通过`\)1或者\(arg1`取得; 对于`arg2`,你可以通过`\)2或者\(arg2`取得; 对于`option`,可以通过`\)option`取得,

2.1.1.3.2. 正则表达式

正则表达式主要用于匹配数据。在完成匹配后,会把结果绑定到$mg0,$mg1..上面。
其中$mg的个数取决于正则表达式组的个数。 比如,下面的正则表达式匹配浮点数。

/(\d+).(\d+)/
匹配3.14
$mg0 = 3.14
$mg1 = 3
$mg2 = 14

注意,由于只有一个作用域,$mg的结果不会被清空掉。第二次匹配发生时,可能仍能访问到之前的结果。
此外,如果有参数名字叫$mg,那么编译器会报错

2.1.1.3.3. 其他

目前只绑定了$PWD。这是程序运行的路径

2.1.2. 字符串

和python类似,支持单行字符串,也支持"""构成的多行字符串

2.1.3. 注释

和python类似,使用#作为注释

2.2. 运算符

samoyed支持大多数基本运算

2.2.1. 符号

支持正负号 如果对字符串,None添加负号会报错

2.2.2. 四则运算

支持+,-,*,/,//,%运算

2.2.3. 逻辑运算

支持and,or,not

2.2.4. 三目表达式

三目表达式的形式比较接近c++

bool_expr?expr1:expr2

2.2.5. 运算符优先级

优先级(越小越高)

符号

结合性

0

括号(),function

-

1

正负号

-

2

乘除法,模(%)运算

从左到右

3

加减法

从左到右

4

比较符号(>,>=,<,<=,=,!=)

-

5

not运算

6

and运算

从左到右

7

or运算

从左到右

8

三目表达式

从左到右

2.3. 语句

2.3.1. 块和缩进

samoyed和python的块缩进规则类似。

state a:
    pass
state b:
    if c:
        if d:
            pass

下面的缩进是错误的:

state a:
    speak(1)
   speak(2)

同样也支持将tab转换成空格。默认tab转换为4个空格。

2.3.2. state语句

state是samoyed的基本组成部分。
state可以看成是有限状态机的声明。
一个有效的程序,最外层除了简单语句,就是state语句。其中,简单语句会在开始执行前就被全部执行,无论它在前面还是后面。
这里简单语句指的是赋值语句或者表达式。 例如:

a = 1
state main:
    speak(a)
a += 1

结果会是a=2

2.3.2.1. main

每一个程序必须要有一个起始状态。这里main就是唯一的起始状态。
如果缺少main,解释器会报错

2.3.3. if语句

和python语言的if类似:

if expr :
    do_some_thing
    
## 或者
if expr:
    do
else:
    do_other

2.3.4. pass

简单的占位功能

2.3.5. branch

跳转语句
使用方法是branch a_state
例如:

state A:
    branch B
state B:
    branch C
state C:
    pass

如果a_state不存在,会抛出异常。
对于一个state,当它执行完所有语句时,如果没有branch,那么它会默认退出。例如这里的state C

branch和goto类似。

如果branch在复杂语句(比如match,if),它也会马上执行跳转

state A:
    if expr:
        speak("A")
        branch B
        speak("B")

将会打印A.

2.3.6. match语句

match语句有两种用法。第一种用法是switch类似,第二种用法则带有时间控制。

2.3.6.1. 普通匹配match

这种match的格式是:

match expr:
    expr1 =>
        statement1    
    expr2 =>
        statement2
    ...
    default =>
        statement

match只会触发一次.并且采用顺序匹配。

2.3.6.1.1. branch缩写

对于branch语句,允许简写成:

expr1 => branch A
2.3.6.1.2. 子串

每个case的判断条件如果是字符串,且被判断的表达式也是字符串,那么不需要完全匹配,只需要case字符串是被判断字符串的子串即可。 例如:

match "hello world":
     "hello" =>
        speak("1")
     "world" =>
        speak("2")
     default =>
        speak("3")

输出1.
注意match只会触发一次.并且采用顺序匹配。

2.3.6.1.3. 正则表达式

此外,case的判断条件允许是正则表达式。

match "hello world":
     /hello(.+)d/ =>
        speak($mg0)
        speak($mg1)        
     "world" =>
        speak("2")
     default =>
        speak("3")

输出hello world  worl(前面有个空格)

2.3.6.2. 带时间控制的match

这种match的格式是

match @()func():
    expr1 =>
        st1
    ...
    silence =>
        stn

其中,@()表达式是时间控制表达式,我称它为at expression.

2.3.6.2.1. @ expression

@()至少要传入一个参数,表示超时的时间。 最多可以传入四个参数,剩下三个分别代表

  • 匹配开始时间(最早退出时间)

  • 函数超时时间(func允许运行的最大时间)

  • 睡眠时间(表达式一直在循环判断,可以给一个时间等待,避免浪费计算资源) 一般只需用前两个参数即可

2.3.6.2.2. 流匹配

和之前的match不同,这里会不断运行函数,产生一个“流”。以listen为例,假设我们在分别输入了hel,lo.
内部的结果队列就会形如["hel","lo"]。每次比较时,把结果队列拼接在一起进行判断。
第一次是"hel",第二次则为hello

2.3.6.2.3. example

例如:

state main:
    @(5,2)listen():
        "stop" =>
            pass
        silence =>
            branch main

上面这段代码表示,将会监听stdin最多5秒钟,持续匹配结果,同时,最少2s才会给出结果。如果匹配成功,终止运行。
用字典Dict[Int,Str]表示第is输入的字符串。
那么:

{0:"stop"}
{0:"st",2:"op"}
{0:"st",3:"oo",4:"stop"}

都是会结束执行的。 而

{0:"stoooooooooooop"}
{0:"st",4.9:"o",5:"p"}

则会超时, 同时,第一个例子得到2s才会匹配成功,结束程序

2.4. 内置函数

2.4.1. listen

内部实现即input。阻塞地从stdin中读入一行。

注意,listen可能导致程序长时间阻塞。建议搭配@表达式使用

2.4.2. speak(str)

内部实现即print。在stdout中输出一行

2.4.4. exit(int)

内部实现为sys.exit。会立刻退出程序。

可以传入一个值表示返回值。默认为0

2.4.5. arg_seq_add(name,help_msg)

添加一个顺序参数。在解释执行中这个函数没有作用。

例如:

arg_seq_add("arg1") ## 第一个顺序参数
arg_seq_add("arg2") ## 第二个

2.4.6. arg_option_add(full_name,short_name,help_msg)

添加一个可选参数。在解释执行中这个函数没有作用。

arg_option_add("option","o") ## --option 或者 -o

2.4.7. sqlite_connect(db_name)

连接到sqlite数据库。

db_name参数指的是数据库的路径。

会返回一个cursor

cursor = sqlite_connect("test.db")

2.4.8. sqlite(cursor,sql)

执行一条sql语句。需要带上上面获取的cursor

sqlite(cursor,"SELECT * FROM sqlite_master")

2.4.9. eval(str)

执行一条简单的python表达式。注意是表达式,语句是无法执行的。

默认会传入全局的命名域。

比如:

x = 2
x = eval("x**2")

这时x=4

使用eval也可以使用exec。比如

x = 2
x = eval("exec('import math') or math.sqrt(x)")

结果是1.414

因此使用eval是有风险的。它可以篡改程序的执行流(当然,它访问不到解释器),因此可能导致程序崩溃。