快速入门:十分钟教程
Yak 是一门纯 Golang 的嵌入式语言,是一门纯 Golang 实现的基于反射特性的语言,语法在一定程度上保留了 Golang 的风格,甚至可以实现 Golang 语言的对象无缝开放给 Yak 使用的功能。
Golang 的一些令人惊叹的特性我们在 Yak 中也可以找到实现,比如大家熟悉或者有所耳闻的 go func() {} ()
,defer
关键字,chan <T>
等。
在保留这些优秀功能的同时,我们极大的简化了 Golang 的语法,移除了静态类型的特性,移除了严格的语法检查,这是一门动态强类型语言,同时,我们也尽力兼容了一些动态语言常见的使用场景。
最令人激动的其实是我们在语言内置了各种安全从业人员需要的安全扫描工具和前无古人的模糊测试模块。
我们希望 Yak 对与安全从业人员是相当友好的,10 分钟即可上手!如果你熟悉 Golang 和 Python,我相信 Yak 一定也是你将会非常喜爱的胶水语言。
yak 每行结尾不需要加分号,当然加了也没关系
#
运算符支持#
基础运算符// 加减乘除: 1 + 4 * 5'+' '-' '*' '/'
// 取余数或格式化字符串'%'
// 赋值运算符:为保持 Golang 用户使用习惯,我们保留了 := 赋值模式,a := 1; 或者 a = 1 是等效的'=' ':='
// 位运算'^' '&' '&^' '|' '<<' '>>'
// 运算赋值,值得注意的是,++ 和 -- 只能用在变量后,其相当于 += 1 / -= 1'+=' '-=' '*=' '/=' '%=' '++' '--'
// 位运算赋值'^=' '&=' '&^=' '|=' '<<=' '>>='
// 单位运算符,取反'!'
// 逻辑比较运算符'>=' '<=' '>' '<' '==' '!=' '&&' '||'
condition ? value1 : value2
#
三元运算符: yak最近也新增了我们在c系语言中常见三元运算符,其使用方法完全相同,例如:
a = true ? "1" : "2" // 结果为: "1"b = 2>1?(2<1?true:false):false // 结果为: falsec = true?false?true:false:false // 结果为: false
<-
#
特殊运算符,操作 Golang 通道:<-
操作符既可以把元素写入现成的 chan 中,也可以从 chan 中读取相关元素
varName = <- chanVar
意思是把 chanVar 这个 chan 读出一个元素,赋值给 varName。chanVar <- someValue
意思是把 someValue 写入 chanVar 这个 chan 中
如果读者有写过 Golang,我们下面一段代码可以更容易帮你理解这个特殊操作符
// 声明一个 chan,类型为 var, chan 的缓冲区为 2,可以缓冲两个元素ch := make(chan var, 2)ch <- 123ch <- 456
firstIn = <- chsecondIn = <- chprintln("first: ", firstIn)println("seconds: ", secondIn)
我们很容易猜到,上面代码的执行结果为
first: 123seconds: 456
%
#
特殊运算符,格式化字符串: 我们可以使用sprintf实现简单地实现格式化字符串:
println(sprintf("here is string: %s and int: %d", "something", 0))
当然一个更简单的方法是类似于python的语法糖%
:
s = "here is string: %s" % "something"s2 = "here is int: %d and %d" % [1, 2]println(s) // here is string: somethingprintln(s2) // here is int: 1 and 2
in
#
特殊运算符,判断是否存在: in
运算符与python中的in
类似:
// 包含子字符串,等价于str.Contains("aabb", "aa")dump("aa" in "aabb")
// 元素是否在Slice/Array中存在dump(1 in [1, 2, 3])
// 键是否在Map/Dict中存在dump("key" in {"key":"value"})
// 方法是否在库中存在dump("Now" in time)
// 结构体字段/方法是否在结构体中存在dump("Add" in time.Now())
+
#
基础运算符的扩展,合并Slice/Array: 与python类似,yak可以使用+号合并Slice/Array:
println([1, 2, 3] + [4, 5, 6]) // [1 2 3 4 5 6]println([1, 2, 3] + ["aaa", "bbb"]) // [1 2 3 aaa bbb]
*
#
基础运算符的扩展,重复字符串: 与python类似,yak可以使用号来生成重复的字符串,注意,这里``号只能用于生成重复的字符串,不能像python一样生成重复元素组成数组等:
println("a" * 3) // aaaprintln([]byte{104} * 3) // [104 104 104]
#
类型与数据结构使用#
基础数据类型1. int2. float // 等效于 Golang 的 float643. string4. byte5. bool6. var // 等效于 Golang 的 interface{}
这些类型其实都是最基础的数据类型:
// 以下是一组基础声明a = 1 // 创建一个 int 类型变量,并初始化为 1b = "hello" // string 类型c = true // bool 类型d = 1.0 // float 类型e = 'h' // byte 类型f = var // f 为一个 兼容类型
#
复合类型slice
:是用于以列表的形式存储元素的数据类型 (等效于 Python 的list
),同 Golang 中的 slicemap
:用于以键值对的形式存储数据的结构(等效于 Python 中的dict
),同 Golang 中的 mapchan
:Yak 与 Golang 特有的复合数据类型,通过make(chan <T>)
创建,<T>
为这个 chan 数据通道传输的数据类型,可以类比为 Python 中的 Queue
#
函数类型与类- 闭包与函数均是特殊类型的实例,同 Golang 与 Python,我们可以把函数赋值给一个变量,在你想使用它的时候使用它
- 类与类成员函数也是本语言可支持的特色之一,但是对于脚本语言来说,OOP 的特性支持会极大增加语言复杂程度,尽管语言支持,但是作者并不希望这个特性被广泛使用。
#
如何使用 Slice / List#
声明一个 Sliceb = [1, 2.3, 5] // 创建一个 []floatc = ["a", "b", "c"] // 创建一个 []stringd = ["a", 1, 2.3] // 创建一个 []var (等价于 Go 语言的 []interface{})e = make([]int, 3, 3) // 创建一个 []int,并将长度设置为 3,容量设置为 3f = make([][]int, 3, 3) // 创建一个 [][]int,并将长度设置为 3,容量设置为 3g = []byte{1, 2, 3} // 创建一个 byte slice,并初始化为 [1, 2, 3]h = []byte(nil) // 创建一个空 byte slice
#
Slice / List 的各种操作- 含义与 Golang 相同的 append 用法
a = append(a, 1, 2, 3)
- 通过
len
获取 Slice 的元素个数,通过cap
获得 slice 的容量,同 Golang 用法
a = append([1,2,3], 5,6,7)length = len(a)println("len(a): ", length)
capValue = cap(a)println("cap(a): ", capValue)
// OUTPUT:// len(a): 6// cap(a): 6
copy(sliceA, sliceB)
把 sliceB 的元素复制到 sliceA 中,如果复制的元素个数为min(len(sliceA), len(sliceB))
a = [1,2,3]b = [4,5,6,7,8]copiedCount = copy(a, b)println("copy(a, b) returns ", copiedCount)println("slice a now: ", a)
// OUTPUT:// copy(a, b) returns 3// slice a now: [4 5 6]
- 按索引取数组元素,支持拆包,具体案例如下
a = [1,2,3,4,5,6,7,8]b = a[4:6]c = a[:5]d = a[3:]index1 = a[1]index2, index3 = a[2], a[3]
println("b: ", b)println("c: ", c)println("d: ", d)println("a[1]: ", index1)println("a[2]: ", index2)println("a[3]: ", index3)
上述脚本输出的结果为
b: [5 6]c: [1 2 3 4 5]d: [4 5 6 7 8]a[1]: 2a[2]: 3a[3]: 4
- 拆包都是基本操作
下面是一个 slice 拆包的基本案例
a, b, c = [1, 2.3, "stringValue"]println("a: ", a)println("b: ", b)println("c: ", c)
/*OUTPUT:
a: 1b: 2.3c: stringValue*/
当然,拆包+赋值的另一个案例读者可以尝试
a = make([]var, 4)a[1], a[2], a[3] = 1, "asdfasdf", false
println("slice a: ", a)
/*OUTPUT:
slice a: [<nil> 1 asdfasdf false]*/
#
如何使用 Map / Dict#
赋值与创建一个 Map / Dicta = {"a": 1, "b": 2, "c": 3} // 得到 map[string]int 类型的对象b = {"a":1,"b":2.3,"c": 3} // 得到 map[string]float64 类型的对象c = {1: "a", 2: "b", 3: "c"} // 得到 map[int]string 类型的对象d = {"a": "hello", "b": 2.0, "c": true} // 得到 map[string]interface{} 类型的对象e = make(map[string]int) // 创建一个空的 map[string]int 类型的对象f = make(map[string]map[string]int) // 创建一个 map[string]map[string]int 类型的对象g = map[string]int16{"a": 1, "b": 2} // 创建一个 map[string]int16 类型的对象x = {} // 创建一个 map[string]interface{}x = make(map[var]var) // 创建一个 map[interface{}]interface{}
上述方法均可创建一个 map,我们发现:
直接使用
{key: value ... }
方式创建的 map 会自动选择最兼容的类型使用
make(map[T1]T2)
可以创建指定类型的 map小贴士 通过
{"key": "value", ... }
方式创建的 map,key 必须是string
#
map / dict 的基本操作a = {"a": 234, "b": "sasdfasdf", "ccc": "13"} // 我们创建一个 map[var]varn = len(a) // 取 a 的元素个数println("len(a): ", n)// OUTPUT: len(a): 3
x = a["b"] // 取 a map 中 key 为 "b" 的元素println(`a["b"]: `, x)// OUTPUT: a["b"]: sasdfasdf
a.noSuchKey // 如果取一个不存在的 key,直接通过 .keyName 调用则会报错,退出程序// OUTPUT:// [ERRO] 2021-05-25 20:38:04 +0800 [default:yak.go:100] line 58: member `noSuchKey` not found
// 当然,我们也可以把拆包解包用在 map 中a["e"], a["f"], a["g"] = 4, 5, 6 // 同 Go 语言a.e, a.f, a.g = 4, 5, 6 // 含义同 a["e"], a["f"], a["g"] = 4, 5, 6
// 如果你想要删掉 map 中的某个元素a = {"a": 123, "b": 1345, "c": 999}println("a.b: ", a.b)delete(a, "b")println("NOW map a: ")dump(a)/*a.b: 1345NOW map a:(map[string]int) (len=2) { (string) (len=1) "a": (int) 123, (string) (len=1) "c": (int) 999}*/
如何判断元素是否存在或在元素不存在时取默认值?
注意
注意:在判断元素是否存在或在元素不存在时取默认值时,Yak 和 Golang 有区别
x = {"a": 1, "b": 2}a = x["a"] // 结果:a = 1// 判断元素是否存在// 方法1:判断取值是否为undefinedif a != undefined { // 判断a存在的逻辑
}c = x["c"] // 结果:c = undefined,注意不是0,也不是nild = x["d"] // 结果:d = undefined,注意不是0,也不是nil
// 方法2:使用in运算符if "e" in x { // key存在 e = x["e"]}
// 元素不存在时取默认值// 方法1:使用三元运算符f = "f" in x ? x["f"] : "fffff" // 结果: f = "fffff"
// 方法2:使用get函数,第一个参数为map/dict,第二个参数为键,第三个参数为取不到键值时的默认值g = get(x, "g", "ggggg") // 结果: g = "ggggg"
#
chan 操作make(chan T, [capBuffer: int])
#
chan 的创建只能通过 创建 chan 与 Golang 保持相同的方法
ch1 = make(chan bool, 2) // 得到 buffer = 2 的 chan boolch2 = make(chan int) // 得到 buffer = 0 的 chan intch3 = make(chan map[string]int) // 得到 buffer = 0 的 chan map[string]int
#
chan 的基础操作/*创建一个 chan var*/ch1 = make(chan var, 4)ch1 <- 1ch1 <- 2
// 一通操作n = len(ch1) // 取得chan当前的元素个数m = cap(ch1) // 取得chan的容量v = <-ch1 // 从chan取出一个值close(ch1) // 关闭chan,被关闭的chan是不能写,但是还可以读(直到已经写入的值全部被取完为止)v1 = <- ch1v2 = <- ch1
// 查看操作的结果和特性println("len(ch1): ", n)println("cap(ch1): ", m)println("<-ch1 执行第一次: ", v)println("<-ch1 执行第二次: ", v1)println("<-ch1 执行第三次: ", v2)
上述脚本执行结果为:
len(ch1): 2cap(ch1): 4<-ch1 执行第一次: 1<-ch1 执行第二次: 2<-ch1 执行第三次: undefined
chan 使用小贴士 需要注意的是,在 chan 被关闭后,<-ch 取得 undefined 值。所以在 yak 中应该这样:
v = <-ch1if v != undefined { // 判断chan没有被关闭的逻辑 ...}
def
func
fn
均等效#
使用函数/闭包:#
函数和闭包的定义函数和闭包定义关键字为 def
/ func
/ fn
, 这三个关键字完全等效!
// 定义了一个函数为 aaa,同时这个函数被赋值给了 bbb,所以这个函数拥有了两个名字bbb = func aaa() { println("具名函数 aaa 函数赋值给了 bbb,被调用了")}
// 定义了个匿名函数,赋值给了 ccc,所以 ccc 可以被当作函数调用了ccc = fn() { println("匿名函数赋值给了 ccc,被调用了")}
// 分别执行 aaa bbb ccc 来看结果aaa()bbb()ccc()
// 声明一个闭包def{ println("这是一个闭包,声明的时候,将立即会被调用")}
// 让 Goroutine 执行一个闭包函数go def{ sleep(0.5) println("在 Goroutine 中执行了闭包函数喔")}
// aaa() 放在 Goroutine 中执行go aaa()
// sleep 1 秒等待 Goroutine 执行完成sleep(1)
执行完如上脚本,我们看到结果为
具名函数 aaa 函数赋值给了 bbb,被调用了具名函数 aaa 函数赋值给了 bbb,被调用了匿名函数赋值给了 ccc,被调用了这是一个闭包,声明的时候,将立即会被调用具名函数 aaa 函数赋值给了 bbb,被调用了在 Gorouting 中执行了闭包函数喔
#
Yak 函数的定义:定义参数与可变参数本质上 yak 函数的定义本质上都是 func(params ...interface{}) interface{}
因此可以对常见任何形态的函数定义做兼容。
// 我们定义一个带参数的函数func x(a) { println("exec in x func, recv: ", a)}println(x("aaa"))
/*OUTPUT:
exec in x func, recv: aaa*/
func argsTest1(vars...) { println("argsTest1 recv: ", vars)}
func argsTest(var1, vars...) { println("var1: ", var1) println("vars: ", vars)}
argsTest1(123, 1, 2, 3, 4, 5)println("---------------------")argsTest(123, 1, 2, 3, 4, 5)
/*argsTest1 recv: [123 1 2 3 4 5]---------------------var1: 123vars: [1 2 3 4 5]*/
#
函数的返回值:可以支持多个返回值,拆包当我们定义的 yak 函数有多个返回值是,我们默认返回的是一个列表 []interface{}
,所以在函数返回的时候,可以支持自动拆包,这样做间接支持了 Golang 的多返回值语法。
// 我们看如下案例func testFunc(arg1, args...) { return 1, 2, arg1}
rets = testFunc("test")println("rets = testFunc(`test`) -> rets: ", rets)println("---------------")ret1, ret2, ret3 = testFunc("aaa", "bbb", "ccc")println(`ret1, ret2, ret3 = testFunc("aaa", "bbb", "ccc")`)println("ret1: ", ret1)println("ret2: ", ret2)println("ret3: ", ret3)
/*rets = testFunc(`test`) -> rets: [1 2 test]---------------ret1, ret2, ret3 = testFunc("aaa", "bbb", "ccc")ret1: 1ret2: 2ret3: aaa*/
在 rets = testFunc("test")
的情况下, 我们发现 rets
的值为 [1 2 test]
直接印证了我们的说法。
当然在不完全拆包的情况下,会报错,我们看如下情况
func testFunc(arg1, args...) { return 1, 2, arg1}ret1, ret2 = testFunc("aaa", "bbb", "ccc")
/*OUTPUT:
[ERRO] 2021-05-26 23:32:19 +0800 [default:yak.go:100] line 4: multi assignment error: require 3 variables, but we got 2*/
#
Go 关键字与 Goroutine#
基础用法Goroutine 是 Golang 最强大的特性之一,Yak 完美继承了这一特性。
Yak 脚本与 Golang 的 Go 的作用都是相同的,但是需要注意一点的是,go
关键字可以用来启动 yak 的闭包函数
在 Golang 中,我们启动一个 Goroutine 通过以下操作启动
go func(){ // ...do some codes}()
在 yak 中,我们不仅兼容了上述写法,我们执行如下命令,都是等效的
// goroutine 启动来函数异步调用go fn(){}()go func(){}() // 兼容 Go 的写法go def(){}() // 兼容 Python 定义方法的关键字
// 定义闭包,执行 Goroutinego fn{/*do sth*/}go func{/*do sth*/}go def{/*do sth*/}
#
并发控制用例一个比较复杂的例子:
wg = sync.NewWaitGroup()wg.Add(2)
go func { defer wg.Done() println("in goroutine1")}
go func { defer wg.Done() println("in goroutine2")}
wg.Wait()
我们执行上述代码,程序将会等待直到两个 Goroutine 都执行完才会退出,这属于比较经典的并发案例。
#
Defer 机制与 Golang 的 Defer基本和 Golang 的 defer 用法类似
但是,由于匿名函数存在,所以 yak defer 常见有两种写法:
defer fn() {} ()defer fn{}
值得注意的是:
在细节上 yak 的 defer 和 Golang 处理并不一致,那就是 defer 表达式中的变量值。
在 Golang 中,当 defer 被声明的时候,如果直接使用函数传参的形式(非指针引用),此时的参数就会使用参数当前值,在整个生命周期内不会变化。 golang 示例
func dfp1() { var a = 1 defer func(t int) { fmt.Println(t) //result 1 }(a) a = 2 return}
如果直接使用了变量。则修改会改变 defer 使用的值。
golang 示例
func dfp2() { var a = 1 defer func() { fmt.Println(a) //result 2 }() a = 2 return}
但 yak 不论何种方式都是会受到影响。 yak 示例
f = {"ccc": 1}dump(f.ccc)defer func{ println("准备开始执行 defer func") println(f.ccc) // 等到执行这里的时候,就会报错}println("设置 f 变量为空")f = nil // 在这里设置 f 为空
例如,假设你在 defer 之后,调用 f = nil 把 f 变量改为 nil,那么后面执行 f.Close() 时就会发生错误。
上述代码段执行结果为:
设置 f 变量为空准备开始执行 defer func[ERRO] 2021-05-26 00:27:59 +0800 [default:yak.go:100] reflect: call of reflect.Value.MethodByName on zero Value在 defer 中修改函数返回值,如果是使用了变量,golang 会改变返回值。yak 不会受到影响golang 示例
在 defer 中修改返回值,如果是直接修改了返回值的值,golang 会改变返回的值。yak 不会受到影响 golang 示例
func dfr1() (r int) { r = 5 defer func() { r++ fmt.Println("dfr1@", r) // 3 }() return 2 //3}
func dfr2() (r int) { r = 5 defer func(r int) { r++ fmt.Println("dfr2@", r) // 6 }(r) //3
return 2 //2}
yak 示例
fn test(){ a := 2 defer fn{ a++ } a = 3 return a //3}fn test(){ a := 2 defer fn(a){ a++ }(a) a = 3 return a //3}
print(test())
defer 的执行顺序跟 Golang 中的一致
defer fn { dump(11111)}
defer fn(){ dump(111)}()
// 输出结果为// (int) 111// (int) 11111
#
流程控制if/elif/else
条件分支#
if expr {
} elif expr2 {
} else {
}
switch/case
语句#
yak switch 语句和 Golang 的有共同点也有不同点;
- yak 的 swtich 没有 break / fallthrough 的特性支持
- yak switch 后的表达式只能是表达式,不能像 Golang 一样承载
赋值语句; 表达式
// switch expr1 {case: expr2; default}a = 5switch a - 3 {case 2: println("case first")case 3: println("case second")}
switch {case true: println("true case ")case false: println("false case")}
注意差异 yak 的 switch/case/default
只能算简易版的 if/elif/else
,并不支持 fallthrough
和 break
for
语句 与 for range
语句#
for
类似 Golang 的 for 语句,同时支持 continue break 这些常规操作。
#
无限循环for { // 无限循环,需要在中间 break 或 return 结束 ...}
for booleanExpr { // 类似很多语言的 while 循环 ...}
#
基础使用/*for initExpr; conditionExpr; stepExpr { ...}*/
for i = 0; i < 10; i ++ { println("element: ", i)}
/*OUTPUT:
element: 0element: 1element: 2element: 3element: 4element: 5element: 6element: 7element: 8element: 9*/
这种用法我想大家都很熟悉了,我们不需要过多叙述。
for range
来遍历一个 slice / list#
for range
是 Golang 特有的形式,yak 对这种形式进行了保留
// 声明一个最基础的 slice / lista = [1,2,3,4]
// 遍历这个 slice / list,第一个参数为 index,第二个参数为具体的 slice 中的元素for index, element = range a { println(str.f("a[%v]: %v", index, element))}println("-----------------")
// 可以只去 index,continue 是for index = range a { println(str.f("a[%v]", index)) continue}println("-----------------")for _, element = range a { println(str.f("first element: %v", element)) break}
for range
来遍历一个 map / dict#
b = {"abc": "123", "bcd": "bcd", "cde": 512}for key, value = range b { println(str.f("b[%v]: %v", key, value))}
/**OUTPUT:
b[abc]: 123b[bcd]: bcdb[cde]: 512*/
上述脚本很容易猜到,结果如下
a[0]: 1a[1]: 2a[2]: 3a[3]: 4-----------------a[0]a[1]a[2]a[3]-----------------current element: 1
for range
同样可以操作 chan#
ch := make(chan var, 2)ch <- 1ch <- 2close(ch)
for result = range ch { println("fetch chan var [ch] element: ", result)}
/*OUTPUT:
fetch chan var [ch] element: 1fetch chan var [ch] element: 2*/
#
模块化/多文件编程在模块化与多文件编程中,我们通常需要根据相对位置定位文件或者资源目录
为此我们准备有三个常见全局变量方便用户操作
YAK_MAIN
: boolean 类型,如果为 false 说明这个文件是主文件,通过动态引入的文件将会为 falseYAK_FILENAME
: 当前执行的脚本文件的具体文件名YAK_DIR
: 当前脚本文件所在的路径位置
YAK_MAIN
:等价于 Python 中的 __main__
#
使用 如果你调用一个 yak 脚本,通过 yak [your-yak-script].yak
来调用,那么脚本中的 YAK_MAIN
将会设置为 true
。
相反,如果是通过 dyn
中的包来调用,那么,YAK_MAIN
的结果则会是 false
。
所以我们在看下面代码:
func aaa(caller) { println("aaa is called by", caller)}
if YAK_MAIN { println("i am in main.... block") aaa("main")}
当我们调用 yak main.yak
的时候,界面展示
i am in main.... blockaaa is called by main
我们发现,我们定义的函数执行了,YAK_MAIN
的值为 true
。
作为对比,我们在同一个目录下,编写一个 foo.yak
,内容如下
res, err := dyn.LoadVarFromFile("main.yak", "aaa")die(err)
res[0].Value("foo.yak")
我们执行 yak foo.yak
之后,发现屏幕打印出:
aaa is called by foo.yak
我们发现:
if YAK_MAIN { println("i am in main.... block") aaa("main")}
这段代码并没有被执行,这是因为 main.yak
第二次执行的时候,并不是主函数。所以 YAK_MAIN
为 false
。
import
:加载另外的脚本中的变量。#
内置函数 #
定义func import(modName: string, varName: string) (*yak.yakVariable, error)
note
*yak.yakVariable
定义如下:
type palm/common/yak.(yakVariable) struct { Fields(可用字段): FilePath: string YakMod: string Value: interface {} StructMethods(结构方法/函数): PtrStructMethods(指针结构方法/函数): func Callable() return(bool)}
我们执行完上述函数之后,将会把一个本地 modName
单独加载到我们新的脚本中,并把变量名为 varName
的变量导出。
如果执行失败,返回值将会为 空+err
。
#
Quick Start我们先创建一个 a1.yak
脚本
func poc() { println("I am in `poc` function....")}
if YAK_MAIN { println("poc function is called by a1") poc()}
然后我们创建一个 main.yak
脚本
pocVar, err = import("a1", "poc")if err != nil { die(err)}
if YAK_MAIN { println("poc function is called by main") pocVar.Value()}
如果我们单独执行 main.yak
我们发现输出为
poc function is called by mainI am in `poc` function....
如果我们单独执行 a1.yak
我们发现输出为:
poc function is called by a1I am in `poc` function....
显然的我们从 main.yak
调用到了 a1.yak
中的函数(实际上是变量)。非常简单实用!
include
像 PHP include 另一个脚本#
include
只有在脚本执行之前进行执行,本质相当于把一个新的文件 "复制" 一个当前脚本中执行了。
作为对比,我们继续使用上一节的 main.yak
函数。通过 include
来执行。
include "main.yak"
if YAK_MAIN { aaa("foo2.yak")}
我们执行上述代码之后,发现结果如下
i am in main.... blockaaa is called by mainaaa is called by foo2.yak
main.yak
中的 if YAK_MAIN{}
分支下的内容被执行了。