VDSL的语法设计为Go开发人员所熟悉,同时更简单、更精简。
您可以在CVE PoC IDE中测试VDSL代码
在VDSL中,一切都是一个值,并且所有值都与一个类型相关联。
11 + 22 // int 值
"aa" + `bb` // string 值
-1.22 + 3e9 // float 值
true || false // bool 值
'七' > '7' // char 值
[33, false, "xx"] // array 值
{a: true, b: "oo"} // map 值
func() { /*...*/ } // function 值
以下是VDSL中所有可用值类型的列表。
VDSL 类型 | 描述 | go中的等效类型 |
---|---|---|
int | 有符号64位整数值 | int64 |
float | 64位浮点值 | float64 |
bool | 布尔值 | bool |
char | unicode字符 | rune |
string | unicode字符串 | string |
bytes | 字节数组 | []byte |
error | 错误 值 | - |
time | 时间值 | time.Time |
array | 值数组 (可变 ) | []any |
const array | 常量 数组 | - |
map | 具有字符串键的值映射 (可变) | map[string]any |
const map | 常量 映射 | - |
nil | nil 值 | - |
function | 函数 值 | - |
user-defined | 用户定义类型 | - |
在VDSL中,可以使用“error”类型的值来表示错误。一个错误值是使用“error”表达式创建的,并且它必须有一个底层值可以使用.value
访问错误值的基础值选择器。
err1 := error("oops") // error with string value
err2 := error(1+2+3) // error with int value
if is_error(err1) { // 'is_error' builtin function
err_val := err1.value // get underlying value
}
在VDSL中,基本上所有的值(除了数组和映射)都是常量。
s := "12345"
s[1] = 'b' // illegal: String is const
a := [1, 2, 3]
a[1] = "two" // ok: a is now [1, "two", 3]
可以使用“const”表达式将数组或映射值设为const。
b := const([1, 2, 3])
b[1] = "foo" // illegal: 'b' references to an const array.
请注意,将新值重新分配给变量与值不变性不冲突。
s := "abc"
s = "foo" // ok
a := const([1, 2, 3])
a = false // ok
请注意,如果复制(使用“copy”内置函数)常量值将返回一个“可变”副本。此外,不变性不适用于数组或映射值的各个元素,除非它们是显式生成的常量。
a := const({b: 4, c: [1, 2, 3]})
a.b = 5 // illegal
a.c[1] = 5 // ok: because 'a.c' is not const
a = const({b: 4, c: const([1, 2, 3])})
a.c[1] = 5 // illegal
在VDSL中,“nil”值可用于表示意外的或不存在的值:
- 一个函数,它不返回或明确返回
nil
值,都被认为返回nil值。 - 如果键或索引不存在,则复合值类型的索引器或选择器可能返回
nil
。 - 如果转换失败,没有默认值的类型转换内置函数将返回“nil”。
a := func() { b := 4 }() // a == nil
b := [1, 2, 3][10] // b == nil
c := {a: "foo"}["b"] // c == nil
d := int("foo") // d == nil
在VDSL中,数组是任何类型的值的有序列表。可以使用索引器“[]”访问数组的元素。
[1, 2, 3][0] // == 1
[1, 2, 3][2] // == 3
[1, 2, 3][3] // == nil
["foo", "bar", [1, 2, 3]] // ok: array with an array element
在VDSL中,map是一组键值对,其中key是字符串,value是任何值类型。可以使用索引器“[]”或选择器“”访问映射的值操作员。
m := { a: 1, b: false, c: "foo" }
m["b"] // == false
m.c // == "foo"
m.x // == nil
{a: [1,2,3], b: {c: "foo", d: "bar"}} // ok: map with an array element and a map element
在VDSL中,函数是一个具有多个函数参数和一个返回值的可调用值。就像任何其他值一样,函数可以传递到另一个函数中,也可以从该函数返回。
my_func := func(arg1, arg2) {
return arg1 + arg2
}
adder := func(base) {
return func(x) { return base + x } // capturing 'base'
}
add5 := adder(5)
nine := add5(4) // == 9
与Go不同,VDSL没有声明。因此,以下代码是非法的:
func my_func(arg1, arg2) { // illegal
return arg1 + arg2
}
VDSL还支持可变函数/闭包:
variadic := func (a, b, ...c) {
return [a, b, c]
}
variadic(1, 2, 3, 4) // [1, 2, [3, 4]]
variadicClosure := func(a) {
return func(b, ...c) {
return [a, b, c]
}
}
variadicClosure(1)(2, 3, 4) // [1, 2, [3, 4]]
只有最后一个参数可以是可变的。以下代码也是非法的:
// illegal, because a is variadic and is not the last parameter
illegal := func(a..., b) { /*... */ }
调用函数时,传递参数的数量必须与该函数定义相匹配。
f := func(a, b) {}
f(1, 2, 3) // Runtime Error: wrong number of arguments: want=2, got=3
像Go一样,您可以使用省略号“…”要传递数组类型值作为其最后一个参数:
f1 := func(a, b, c) { return a + b + c }
f1([1, 2, 3]...) // => 6
f1(1, [2, 3]...) // => 6
f1(1, 2, [3]...) // => 6
f1([1, 2]...) // Runtime Error: wrong number of arguments: want=3, got=2
f2 := func(a, ...b) {}
f2(1) // valid; a = 1, b = []
f2(1, 2) // valid; a = 1, b = [2]
f2(1, 2, 3) // valid; a = 1, b = [2, 3]
f2([1, 2, 3]...) // valid; a = 1, b = [2, 3]
可以使用赋值运算符“:=”和“=”将值赋值给变量。
:=
运算符在作用域中定义一个新变量并赋值。=
运算符为作用域中的现有变量分配一个新值。
变量在全局范围(在函数外部定义)或本地范围(在功能内部定义)中定义。
a := "foo" // define 'a' in global scope
func() { // function scope A
b := 52 // define 'b' in function scope A
func() { // function scope B
c := 19.84 // define 'c' in function scope B
a = "bee" // ok: assign new value to 'a' from global scope
b = 20 // ok: assign new value to 'b' from function scope A
b := true // ok: define new 'b' in function scope B
// (shadowing 'b' from function scope A)
}
a = "bar" // ok: assigne new value to 'a' from global scope
b = 10 // ok: assigne new value to 'b'
a := -100 // ok: define new 'a' in function scope A
// (shadowing 'a' from global scope)
c = -9.1 // illegal: 'c' is not defined
b := [1, 2] // illegal: 'b' is already defined in the same scope
}
b = 25 // illegal: 'b' is not defined
a := {d: 2} // illegal: 'a' is already defined in the same scope
与Go不同,可以为变量指定不同类型的值。
a := 123 // assigned 'int'
a = "123" // re-assigned 'string'
a = [1, 2, 3] // re-assigned 'array'
虽然VDSL中没有直接指定类型,但可以使用类型转换在值类型之间进行转换。
s1 := string(1984) // "1984"
i2 := int("-999") // -999
f3 := float(-51) // -51.0
b4 := bool(1) // true
c5 := char("X") // 'X'
运算符 | 用法 | 类型 |
---|---|---|
+ |
等同 0 + x |
int, float |
- |
等同 0 - x |
int, float |
! |
逻辑非 | all types* |
^ |
按位异或 | int |
在VDSL中,所有值都可以是真值也可以是假值
运算符 | 用法 | 类型 |
---|---|---|
== |
检查两个值是否相等 | all types |
!= |
检查两个值是否不相等 | all types |
&& |
逻辑 AND 运算符 | all types |
|| |
逻辑 OR 运算符 | all types |
+ |
相加/字符串连接 | int, float, string, char, time, array |
- |
相减 | int, float, char, time |
* |
相乘 | int, float |
/ |
相除 | int, float |
& |
按位与运算符 | int |
| |
按位或运算符 | int |
^ |
按位异或运算符 | int |
&^ |
位清除(AND NOT) | int |
<< |
左移运算符 | int |
>> |
右移运算符 | int |
< |
检查左边值是否小于右边值 | int, float, char, time, string |
<= |
检查左边值是否小于等于右边值 | int, float, char, time, string |
> |
检查左边值是否大于右边值 | int, float, char, time, string |
>= |
检查左边值是否大于等于右边值 | int, float, char, time, string |
VDSL有一个三元条件运算符(条件表达式)?(true表达式):(false表达式)
。
a := true ? 1 : -1 // a == 1
min := func(a, b) {
return a < b ? a : b
}
b := min(5, 10) // b == 5
运算符 | 用法 |
---|---|
+= |
(lhs) = (lhs) + (rhs) |
-= |
(lhs) = (lhs) - (rhs) |
*= |
(lhs) = (lhs) * (rhs) |
/= |
(lhs) = (lhs) / (rhs) |
%= |
(lhs) = (lhs) % (rhs) |
&= |
(lhs) = (lhs) & (rhs) |
|= |
(lhs) = (lhs) | (rhs) |
&^= |
(lhs) = (lhs) &^ (rhs) |
^= |
(lhs) = (lhs) ^ (rhs) |
<<= |
(lhs) = (lhs) << (rhs) |
>>= |
(lhs) = (lhs) >> (rhs) |
++ |
(lhs) = (lhs) + 1 |
-- |
(lhs) = (lhs) - 1 |
一元运算符具有最高优先级,三元运算符具有最低优先级。二进制运算符有五个优先级。
乘法运算符绑定最强,其次是加法运算符,比较运算符,“&&”(逻辑AND),最后是“||”(逻辑OR):
优先级 | 操作符 |
---|---|
5 | * / % << >> & &^ |
4 | + - | ^ |
3 | == != < <= > >= |
2 | && |
1 | || |
与Go一样,“++”和“--”运算符形成语句,而不是表达式,它们位于运算符层次结构之外。
可以使用选择器(.
)和索引器([]
)运算符来读取或写入复合类型(数组、映射、字符串、字节)的元素。
["one", "two", "three"][1] // == "two"
m := {
a: 1,
b: [2, 3, 4],
c: func() { return 10 }
}
m.a // == 1
m["b"][1] // == 3
m.c() // == 10
m.x = 5 // add 'x' to map 'm'
m["b"][5] // == nil
m["b"][5].d // == nil
m.b[5] = 0 // == nil
m.x.y.z // == nil
与Go一样,可以对数组、字符串、字节等序列值类型使用切片运算符“[:]”。
a := [1, 2, 3, 4, 5][1:3] // == [2, 3]
b := [1, 2, 3, 4, 5][3:] // == [4, 5]
c := [1, 2, 3, 4, 5][:3] // == [1, 2, 3]
d := "hello world"[2:10] // == "llo worl"
c := [1, 2, 3, 4, 5][-1:10] // == [1, 2, 3, 4, 5]
注意:关键字不能用作选择器
a := {in: true} // Parse Error: expected map key, found 'in'
a.func = "" // Parse Error: expected selector, found 'func'
使用双引号和索引器将关键字与映射一起使用。
a := {"in": true}
a["func"] = ""
“If”语句与Go非常相似。
if a < 0 {
// execute if 'a' is negative
} else if a == 0 {
// execute if 'a' is zero
} else {
// execute if 'a' is positive
}
与Go一样,条件表达式前面可能有一个简单的语句,该语句在计算表达式之前执行。
if a := foo(); a < 0 {
// execute if 'a' is negative
}
“For”语句与Go非常相似。
// for (init); (condition); (post) {}
for a:=0; a<10; a++ {
// ...
}
// for (condition) {}
for a < 10 {
// ...
}
// for {}
for {
// ...
}
VDSL中新增了“for in”语句。它类似于Go的“for range”语句。
“For In”语句可以迭代任何可迭代的值类型(数组、映射、字节、字符串、nil)。
for v in [1, 2, 3] { // array: element
// 'v' is value
}
for i, v in [1, 2, 3] { // array: index and element
// 'i' is index
// 'v' is value
}
for k, v in {k1: 1, k2: 2} { // map: key and value
// 'k' is key
// 'v' is value
}
模块是VDSL中的基本编译单元。一个模块可以使用“import”表达式导入另一个模块。
主模块:
sum := import("./sum") // load module from a local file
fmt.print(sum(10)) // module function
“sum.dsl”文件中的另一个模块:
base := 5
export func(x) {
return x + base
}
默认情况下,“import”将模块文件缺少的扩展名解决为".dsl
"1。
因此,“sum:=import("./sum")”相当于“sum:=import("./sum.dsl")”。
在VDSL中,模块与函数非常相似。
import
表达式加载模块代码并像函数一样执行它。- 模块应使用“export”语句返回一个值。
- 模块可以返回“导出”任何类型的值:int、map、function等。
- 模块中的export类似于函数中的return:它停止执行并导入代码返回一个值。
export
的值总是常量。- 如果模块没有任何“export”语句,则“import”表达式只返回“nil”(就像没有“return”的函数一样。)
- 请注意,如果代码作为主模块执行,则“export”语句将被完全忽略,并且不会进行求值。
此外,您还可以使用“import”表达式来加载标准库。
math := import("math")
a := math.abs(-19.84) // == 19.84
与Go一样,VDSL支持行注释(//…
)和块注释(/* ... */
).
/*
multi-line block comments
*/
a := 5 // line comments
与Go不同,VDSL没有以下功能:
- Declarations
- Imaginary 值
- Structs
- Pointers
- Channels
- Goroutines
- Tuple 赋值
- Variable 参数
- Switch 语句
- Goto 语句
- Defer 语句
- Panic
- Type 断言
Footnotes
-
如果在Go中使用VDSL作为库,则可以自定义文件扩展名“
.dsl
”。在这种情况下,请使用“Compiler”类型的“SetImportFileExt”函数。 ↩