Repository navigation
SandScript 2.5.0 document (Interpreter)
Welcome to the SandScript-Language wiki!
后有汉语版
Version: 2.5.0 | Interpreter executable:
Sandi| Source file extension:.sandSandScript is a dynamically‑typed interpreted scripting language implemented in C++. It supports arbitrary‑precision decimal arithmetic, arrays, dictionaries, functions, namespaces, object‑oriented classes with inheritance, preprocessor macros, conditional compilation and module importing.
- Getting Started
- Lexical Basics
- Data Types
- Operators & Precedence
- Statements
- Expressions
- Functions
- Object‑Oriented Classes
- Built‑in Functions
- Preprocessor Directives
- Full BNF Grammar
- Code Examples
- Runtime Notes & Pitfalls
# Run a script [and set biggest divide presition,default presition is 50]
Sandi [--set_BD_DEV_PRECITION 50] test.sand
# Show grammar help
Sandi --help
# Show interpreter version
Sandi --version
# Show license information
Sandi --copyright
Sandi --copyright-apacheoutputLine("Hello SandScript!");
Run:
Sandi hello.sand-
//line comment: everything from//to end‑of‑line is ignored.
// single‑line comment
output(123); // trailing comment
- Start with a letter or underscore; subsequent characters can be letters, digits, underscores, non‑ASCII characters (supports Chinese identifiers).
- Case‑sensitive.
if else while for in func return break continue
output input range from import class static new super begin end lambda switch case default namespace emit
Double‑quoted "...". Supported escape sequences:
-
\nnewline -
\\backslash -
\"double quote Unknown escape sequences are output literally.
s = "hello\nworld";
Decimal integer / fractional numbers backed by high‑precision BigDecimal. No floating‑point precision loss.
123
-45.67
0.0001
Code blocks are wrapped with begin ... end. Curly braces {} are only for dictionary literals.
| Type | Description | Literal Example |
|---|---|---|
NUMBER |
Arbitrary‑precision decimal |
123, -0.5
|
STRING |
UTF‑8 string | "abc" |
ARRAY |
Dynamic ordered array | [1, 2, "hi"] |
DICT |
Key‑value map | {"a":1, 2:"b"} |
FUNCTION |
User‑defined function | func f() begin end |
CLASS_META |
Class metadata | class A begin end |
OBJECT |
Class instance object | new A() |
NIL |
Null value; default return for void operations |
Boolean semantics: there are no dedicated boolean types. Number
0means false; any non‑zero number means true.
| Precedence | Operators | Description |
|---|---|---|
| 1 (lowest) | ` | |
| 2 | &&``and |
Logical AND (short‑circuit) |
| 3 | == != < > <= >= |
Comparison |
| 4 | + - |
Addition / subtraction; + also concatenates strings |
| 5 | * / |
Multiplication / division; division‑by‑zero throws runtime error |
| 6 | ! - not |
Unary: logical NOT, arithmetic negation |
| 7 (highest) |
() call [] index . member ++ --
|
Postfix operations |
Assignment operators: = += -= *= /=. Assignments are statements, not expressions and cannot be nested inside expressions.
Full operator list:
++ -- += -= *= /=
+ - * /
== != < > <= >=
&& || !
= . ...
Every regular statement must end with a semicolon
;. Semicolons are not required afterbegin/ beforeend.
a = 10;
b = a + 20;
arr[0] = 99; // index assignment
obj.val = 123; // object member assignment
x += 5;
x -= 2;
arr[1] *= 10;
i++;
i--;
arr[2]++;
if(cond)
begin
output("yes");
end
else
begin
output("no");
end
Chained else if is supported:
if(a == 1)
begin end
else if(a == 2)
begin end
else
begin end
switch ([expression]) {
case Pattern1: Statements
case Pattern2: Statements
......
}
Supported Data Types for switch
Numeric type: number
String type: string
Type Identifier: Matches the type of the expression
if Guard Condition: An additional boolean condition. The branch executes only when the condition evaluates to true (the switch match must be satisfied first).
Example: case val if n % 2 == 0:
default Wildcard Pattern
It is recommended to always include default to handle unknown cases.
default can be placed anywhere, but it is usually put at the end. Any case that comes after it will never be matched.
Patterns can be combined with or / and.
Restrictions on case:
Duplicate case entries are not allowed.
while(i < 10)
begin
output(i);
i++;
end
Iterates over arrays only. Cannot iterate dictionaries directly.
for x in [10,20,30]
begin
output(x);
end
break exits the innermost loop; continue skips to next iteration. Valid only inside while / for‑in.
while(1)
begin
break;
end
Returns a value from inside a function.
func add(a,b)
begin
return a + b;
end
from "util.sand" import ;
Executes global‑scope code from another .sand source file; imported symbols enter the global scope.
123 // number
"test" // string
[1, 2, "abc"] // array literal
{"name":"tom", 1: 1} // dict literal
range(0,10) // generates array [0,1 ... 9]
range(0,10,2) // range(start, end, step)
arr[0]
dict["key"]
obj.name
obj.sayHi(1,2)
super.foo(); // call superclass method inside instance method
add(1,2);
! 0
-123
Definition syntax: func name(paramList) begin ... end
func sum(a, b)
begin
return a + b;
end
output(sum(3,5));
lambda (pamams) begin ... end
func total(a, b=2)
begin
return a + b;
end
output(total(1));//output:3
... marks variadic capture. Remaining arguments are collected into an array. Variadic parameter must appear last in parameter list.
func total(a, b, ...rest)
Argument count mismatch raises runtime error. Functions create a local scope; variables resolve locally first then propagate to parent scope.
class Person
begin
func init(name) // constructor: auto‑invoked on new()
begin
self.name = name;
end
func sayHello() // instance method: implicit self argument injected by parser
begin
output("hi, ");
output(self.name);
end
static func info() // static method: no self
begin
output("Person static");
end
end
// instantiate
p = new Person("Alice");
p.sayHello();
// invoke static method
Person.info();
-
self: implicit first parameter inside instance methods, points to current object instance. Do not writeselfin your parameter list manually. -
init: constructor method; automatically called whennew ClassName(args)executes. -
static func: static method belongs to the class, not instances; invoked via class name. - Object members can be freely read‑written with
.operator.
class Student : Person
begin
func init(name, grade)
begin
super.init(name); // invoke superclass instance method
self.grade = grade;
end
func show()
begin
super.sayHello();
output(self.grade);
end
end
s = new Student("Bob", 6);
s.show();
supercan only be used inside instance methods; forbidden in static methods or global scope.
Inside a class block use #private var1,var2...;. The preprocessor renames identifiers to __ClassName__varName to simulate private members which cannot be accessed externally.
class A
begin
#private secret;
func init()
begin
self.secret = 123;
end
end
(object >> func);:connect signals and slots
(object !> func);:disconnect signals and slots
emit object;:launch signal
| Function | Description |
|---|---|
output(expr) |
Print value without appending newline |
outputLine(expr) |
Print value followed by newline |
input() |
Read one line from stdin, returns string |
prminput(prompt) |
Print prompt string then read one input line |
tonum(str) |
Convert string to high‑precision number; error on invalid format |
tostring(val) |
Convert any runtime value to string representation |
time() |
Return UNIX timestamp in seconds as number |
time_ms() |
Return timestamp in milliseconds |
sleep(ms) |
Suspend execution for given milliseconds |
args() |
CLI argumment (string array) |
len(arrayOrString) |
length |
conc(arr,valOrArr) |
Concat array |
setOutputColor(str) |
Set color of console:Red, Blue, Green, Yellow, Cyan, White, Reset(default), Magenta, BrightRed, BrightGreen |
system(str) |
Run Command |
There are an easter egg function, you can look at the sourse.
Preprocessing runs before lexing / parsing. It handles text‑level macro substitution, file inclusion, conditional compilation and private‑member renaming.
Include and preprocess another source file; guards against cyclic duplicate includes.
#include "util.sand"
Resolve include relative to interpreter executable directory.
#from ; include "lib.sand"
Multi‑line text macro definition, terminated by #enddef.
#define PI 3.1415926
#enddef
output(PI);
Inline costnant.
#inline DEBUG=1
#ifdef DEBUG
output("debug mode");
#else
output("release");
#endif
#ifndef RELEASE
// compiled only if RELEASE symbol is undefined
#endif
#elif SYMBOL is also supported.
Pseudo‑private variable renaming inside class blocks.
Preprocessor performs raw text substitution; it does not validate syntax.
namespace ident begin
statement*
end
call:
namespace.member
Please look at sourse.
func fact(n)
begin
res = 1;
i = 1;
while(i <= n)
begin
res = res * i;
i++;
end
return res;
end
outputLine(fact(6));
class Animal
begin
func init(name)
begin
self.name = name;
end
func speak()
begin
outputLine("animal:");
outputLine(self.name);
end
end
class Dog : Animal
begin
func init(name)
begin
super.init(name);
end
func speak()
begin
super.speak();
outputLine("wang wang");
end
end
d = new Dog("wangcai");
d.speak();
arr = [10, 20, 30];
arr[1] = 99;
d = {"a":100, "b":200};
outputLine(d["a"]);
- Code blocks must use
begin ... end. Curly braces{}are only for dictionary literals. - Semicolon
;terminates every statement including bare‑expression statements. - No
true/falseliterals; use numeric0for false and non‑zero for true. - Dictionary keys cannot be array or object values, this triggers runtime error.
-
superis only valid inside instance methods; forbidden in static methods and global scope. - Objects use reference semantics: assigning object variables copies the reference, not the instance. Multiple variables may point to one same object.
- Array assignment
a = arrperforms deep copy of array contents; object assignment copies only reference pointer. - Division‑by‑zero throws runtime exception.
-
#defineperforms raw text substitution; be careful with operator precedence and whitespace in macro bodies. -
for‑initerates arrays only. Modifying the loop variable inside the body writes‑back to original array elements. 汉语版:
版本:2.5.0|解释器程序名:
Sandi|源码后缀:.sandSandScript是一门C++实现的动态解释型脚本语言,支持大数高精度运算、数组、字典、函数、面向对象类与继承、预处理器宏、条件编译、模块导入、命名空间。
# 执行脚本[,设置除法商最大小数位数,默认50]
Sandi [set_BD_DIV_PRESITION 50 ]test.sand
# 查看帮助语法
Sandi --help
# 版本
Sandi --version
# 版权
Sandi --copyright
Sandi --copyright-apacheoutputLine("Hello SandScript!");
运行:Sandi hello.sand
-
//行注释,从//到行末尾全部忽略
// 这是单行注释
output(123); // 行尾注释
- 字母、下划线开头;后续可以是字母、数字、下划线、非ASCII字符(中文标识符可用)
- 区分大小写
if else while for in func return break continue
output input range from import class static new super begin end switch lambda case default namespace emit
双引号包裹"...",支持转义:
-
\n换行 -
\\反斜杠 -
\"双引号 其他原样输出。
s = "hello\nworld";
十进制,支持小数,高精度BigDecimal,无浮点数精度丢失。
123
-45.67
0.0001
代码块使用 begin ... end,不是大括号{}。
| 类型 | 说明 | 字面量示例 |
|---|---|---|
NUMBER |
高精度十进制大数 |
123, -0.5
|
STRING |
字符串 | "abc" |
ARRAY |
动态数组 []
|
[1,2,"hi"] |
DICT |
字典map,key支持数字/字符串,不支持数组字典做key | {"a":1, 2:"b"} |
FUNCTION |
用户函数 | func f() begin end |
CLASS_META |
类元数据 | class A begin end |
OBJECT |
类实例对象 | new A() |
NIL |
空值,函数无返回默认nil |
布尔:没有单独bool类型;用数字
0代表假,非0代表真。
| 优先级 | 运算符 | 说明 |
|---|---|---|
| 1(最低) | ` | |
| 2 | &&``and |
逻辑与,短路求值 |
| 3 | == != < > <= >= |
比较 |
| 4 | + - |
加减,+支持字符串拼接 |
| 5 | * / |
乘除,大数除法,除零抛运行时错误 |
| 6 | ! - not |
一元:逻辑非、负号 |
| 7(最高) |
()调用 []下标 .成员访问 ++ --
|
后缀操作 |
赋值运算符:= += -= *= /=,赋值是语句不是表达式,不能嵌套。
++ -- += -= *= /=
+ - * /
== != < > <= >=
&& || !
= . ...
所有普通语句末尾必须加分号
;,块内部语句也要分号;begin/end块不需要分号。
a = 10;
b = a + 20;
arr[0] = 99; //下标赋值
obj.val = 123; //对象成员赋值
x += 5;
x -= 2;
arr[1] *= 10;
i++;
i--;
arr[2]++;
if(cond)
begin
output("yes");
end
else
begin
output("no");
end
支持else if链式
if(a == 1)
begin end
else if(a == 2)
begin end
else
begin end
switch ([表达式]) {
case 模式1:语句
case 模式2:语句
......
}
switch支持的数据类型
数类型:number
字符串类型:string
类型标识 匹配表达式的类型
if 守卫条件 附加布尔条件,仅当条件为true时执行分支(需先满足switch) case val n % 2 == 0:
default通配符模式
建议总是包含 default 处理未知情况
default 位置可以在任意位置,但通常放在最后,因为位于其后的case永远不会被匹配到
模式之间可用or/and连接
case 限制:
不能有重复的 case
while(i < 10)
begin
output(i);
i++;
end
只支持遍历数组;不支持字典直接遍历。
for x in [10,20,30]
begin
output(x);
end
跳出循环、继续下一轮循环,只能在while/for‑in内部。
while(1)
begin
break;
end
用于函数内部返回值。
func add(a,b)
begin
return a + b;
end
from "util.sand" import ;
导入另一个sand源码文件,执行全局代码,符号进入当前全局作用域。
123 //数字
"test" //字符串
[1, 2, "abc"] //数组字面量
{"name":"tom", 1: true} //字典字面量
range(0,10) //range生成数组 [0,1...9]
range(0,10,2) //带步长 range(start,end,step)
arr[0]
dict["key"]
obj.name
obj.sayHi(1,2)
super.foo(); //子类内部调用父类同名方法
add(1,2);
! 0
-123
定义:func 名字(参数列表) begin ... end
func sum(a, b)
begin
return a + b;
end
output(sum(3,5));
lambda (pamams) begin ... end
func total(a, b=2)
begin
return a + b;
end
output(total(1));//output:3
...标记可变参数,剩余全部参数收集为数组给到rest变量。可变参数必须写在参数列表最后一位。
func total(a, b, ...rest)\
参数数量不匹配会报运行时错误。函数内部拥有独立局部作用域;变量先找局部,再向外找父scope。
class Person
begin
func init(name) //构造方法 new()时自动调用init
begin
self.name = name;
end
func sayHello() //实例方法,第一个隐式参数self
begin
output("hi, ");
output(self.name);
end
static func info() //静态方法,无self
begin
output("Person static");
end
end
//实例化
p = new Person("Alice");
p.sayHello();
//调用静态方法
Person.info();
-
self:实例方法隐式第一个参数,代表当前对象实例,不要手动写self参数,parser自动插入self参数。 -
init:构造函数;调用new ClassName(args)自动执行init(args)。 -
static func:静态方法,不属于实例,通过类名调用,没有self。 - 对象使用
.访问成员;对象成员可以任意读写。
class Student : Person
begin
func init(name, grade)
begin
super.init(name); //调用父类方法
self.grade = grade;
end
func show()
begin
super.sayHello();
output(self.grade);
end
end
s = new Student("Bob", 6);
s.show();
super只能在实例方法内部使用,用于调用父类的实例方法。
类内部写#private a,b...;,预处理器自动把标识符重命名为__ClassName__a,实现简单私有变量,外部无法直接访问。
class A
begin
#private secret;
func init()
begin
self.secret = 123;
end
end
(object >> func);:连接信号与槽
(object !> func);:断开信号与槽
emit object;:发射信号
| 函数 | 说明 |
|---|---|
output(expr) |
输出内容,不自动换行 |
outputLine(expr) |
输出并换行 |
input() |
读取一行标准输入,返回字符串 |
prminput(prompt) |
输出提示字符串,读取一行输入 |
tonum(str) |
字符串转高精度数字;失败报错 |
tostring(val) |
任意值转为字符串 |
time() |
返回当前系统时间戳(秒,数字) |
time_ms() |
毫秒级时间戳 |
sleep(ms) |
休眠指定毫秒数 |
args() |
命令行参数 |
len(arrayOrString) |
序列长度 |
conc(arr,valOrArr) |
拼接数组 |
setOutputColor(str) |
设置控制台颜色:Red, Blue, Green, Yellow, Cyan, White, Reset(default), Magenta, BrightRed, BrightGreen |
system(str) |
系统命令 |
有一个彩蛋函数,去看源代码吧!
预处理在词法解析之前执行,处理宏替换、include、条件编译、private重命名。
导入文件,递归预处理,防止循环重复导入。
从exe所在目录导入;;代表使用exe目录作为基准。
#from ; include "lib.sand"
多行文本宏替换;#enddef结束宏定义。
#define PI 3.1415926
#enddef
output(PI);
内联常量。
#inline DEBUG=1
#ifdef DEBUG
output("debug mode");
#else
output("release");
#endif
#ifndef RELEASE
//未定义RELEASE才编译
#endif
支持#elif SYMBOL。
类内伪私有,预处理器改名。
预处理器只做文本替换,不做语法检查。
namespace 名字 begin
语句*
end
使用:
namspace.member
看源码
func fact(n)
begin
res = 1;
i = 1;
while(i <= n)
begin
res = res * i;
i++;
end
return res;
end
outputLine(fact(6));
class Animal
begin
func init(name)
begin
self.name = name;
end
func speak()
begin
outputLine("animal:");
outputLine(self.name);
end
end
class Dog : Animal
begin
func init(name)
begin
super.init(name);
end
func speak()
begin
super.speak();
outputLine("wang wang");
end
end
d = new Dog("wangcai");
d.speak();
arr = [10, 20, 30];
arr[1] = 99;
d = {"a":100, "b":200};
outputLine(d["a"]);
-
块必须使用
begin end,不要使用{};大括号仅用于字典字面量。 - 语句末尾分号
;不可省略,表达式语句也需要分号。 - 布尔没有true/false字面量,
0假,非0数字为真。 - 字典key不能是数组/对象,会报运行时错误。
-
super只能在实例方法内部,不能在静态方法、全局作用域使用。 - 对象是引用语义;赋值对象变量不会拷贝对象,多个变量指向同一个实例。
- 数组赋值:
a = arr复制整个数组;对象赋值仅复制引用。 - 除法0会抛出runtime异常。
- 预处理器
#define是纯文本替换,注意宏的空格与优先级。 -
for‑in遍历中修改循环变量会同步回原数组对应位置。