脚本语言ymd:介绍
脚本语言ymd
ymd全称yamada script,是某一淘数据部员工业余时间完成的一个玩具脚本语言,其语法类似lua和javascript。代码托管在github
目前只支持Linux x86_64,预计未来会支持Windows/Mac OS。
yamada名称由来是动画《Working!!》角色:山田 葵(Yamada Aoi)
1.特点
一句话概括:Too Simple, Sometimes Naive !
2.如何使用
按照这篇wiki编译获得解释器ymd_main
执行一段代码:
$ ./ymd_main foo.ymd
执行一段代码并打印出bytecode:
$ ./ymd_main -d foo.ymd
执行一个单元测试文件:
$ ./ymd_main --test testing/builtin_test.yut
源代码目录下testing/下有一些单元测试文件,可供参考语法。
3.语法
3.1变量
ymd语言的变量没有类型,值才有类型,可以给同一个变量赋任何值。
值类型共有10种:
managed类型还能拥有metatable。
nil、int、bool、lite是值类型,其他的都是引用类型,变量只会保存引用类型的引用。
变量按作用域和生存期可分为三种:
变量和值的生存期不是同一概念,如果变量保存了一个引用类型的引用,在变量结束生存期后,引用的值会生存到被垃圾回收为止。但在此期间,你无法引用到它:)
示例代码:
a = nil // 从此处开始,生成了一个全局变量a
a = 0
a = [0, 1, 2, 3]
remove(__g__, "a") // 全局变量a被动态删除。
var a = {name:"John"} // 此处的a是一个局部变量。
var fn = func [a] (b, c, d) {
var f = 0xff
// a是绑定变量,b,c,d,f都是局部变量。
}
3.2复杂类型构造子
构造数组:
element ::= element `,` expression
| expression
|
array_constructor ::= `[` element `]`
构造哈希表:
pair ::= `[` expression `]` `=` expression
| identifier `:` expression
pairs ::= pairs `,` pair
|
hashmap_constructor ::= `{` pairs `}`
构造跳表:
skiplist_constructor ::= `@` `{` pairs `}`
示例代码:
var array = [0, 1, "Hello", func [range] () {}, []] // 构造一个包含5个元素的数组
var map = {name:"Johnson", [0] = "Zero"} // 哈希表
var list = @{name:"Johnson", [0] = "Zero"} // 跳表
print (map["name"])
print (map.name) // map.name等同于map["name"]
print (list["name"])
print (list.name) // 跳表和哈希表是可以互换的。
数组、哈希表、跳表的键和值可以是除了nil外的任何类型,他们的大小是没有限制的。
3.3表达式
unary_operator ::= `-` | `~` | `not` | `typeof`
binary_operator ::= `+` | `-` | `*` | `/` | `%` | `&` | `|` | `^` | `>>` | `|>` | `<` | `>=` | `<` | `<=` | `!=` | `~=` | `and` | `or`
simple ::= `nil`
| `true`
| `false`
| integer_literal
| string_literal
| closure_literal
| array_constructor
| hashmap_constructor
| skiplist_constructor
| suffixed
primary ::= `(` expression `)`
| identifier
postfix_partial ::= `.` identifier
| `:` identifier `(` arguments `)`
| `[` expression `]`
| `(` arguments `)`
postfix ::= postfix_partial postfix
| postfix_partial
suffixed ::= primary postfix
expression ::= unary_operator expression
| expression binary_operator expression
| simple
与C/C++不同的是,赋值不是表达式,而是一条语句,且表达式不能作为语句。
代码示例:
var a = 1
var b = 2
var c = a + b / 2 % 1 + 4 * b
var f = a and b or c // 类似于C/C++的 a ? b : c
var s = "Hello, " + "World"
if "999" ~= "[0-9]*" { // 正则表达式匹配。
print "Number"
}
// typeof 运算符与类型:
print (typeof nil) // "nil"
print (typeof 0) // "int"
print (typeof false) // "bool"
print (typeof "Hello, World") "string"
print (typeof print) "function"
print (typeof []) "array"
print (typeof {}) "hashmap"
print (typeof @{}) "skiplist"
print (typeof __g__) "hashmap"
print (typeof open("/dev/random")) "managed"
print (typeof typeof nil) "string"
// 移位运算符:
print (1 << 2) // “4″ 左移运算符
print (1 << 63) // “-9223372036854775808″
print (1 <> 1) // “-1″ 算术右移
print (-1 |> 1) // “9223372036854775807″ 逻辑右移
`and` `or`操作符的行为可能与你想的不一致,and返回两个操作数中的一个为假的,or返回两个操作数中的一个为真的。他们都是短路操作,遇到符合条件的值就会返回。
3.4条件语句
if_prefix ::= `if` expression `{` body `}`
elif_partial ::= `elif` expression chunk
elif_body ::= elif_partial elif_body
| elif_partial
|
else_postifx ::= elif_body `else` `{` body `}`
| `else` `{` body `}`
|
if_statement ::= if_prefix elif_body else_postfix
在条件判断中,nil和false值被认为“假”,其他值被认为“真”。
代码示例:
if nil {
print "Never to here!"
} else {
print "Should be false!"
}
if 0 {
print "0 is true."
}
3.5循环语句
目前只提供for循环语法,foreach是最常见的循环场景,提供foreach已足够。
for_variable ::= identifier
| declare
for_statement ::= `for` for_variable `,` integer `{` body `}`
| `for` for_variable `,` integer `,` integer `{` body `}`
for_statement ::= `for` for_variable `in` expression `{` body `}`
| `for` `{` body `}`
代码示例:
var i = 0
for { // 死循环
i = i + 1
if i > 99 { break }
}
for i in range([9, 8, 7, 6, 5, 4, "3rd"]) { // 遍历数组的每一个值
}
for i in range(__g__) { // 遍历全局变量的每一个值
}
for i in range(0, 100) { // 循环100次,i的值[0, 100)
}
for语句的实现,需要2个变量,for_variable,iterator。iterator匿名局部变量,由编译器生成,类型为"function",它通常实现为闭包,每次调用产生一个新值,并赋值给for_variable,当调用iterator的返回值为nil时,循环结束,这就是为什么数组、哈希表、跳表的键和值不能为nil的原因。
伪装代码表示for i in range(0, 100) {}
iterator = range(0, 100);
entry:
i = iterator();
if i == nil then goto out;
// loop body
// ...
// ...
goto entry
out:
// ...
3.6函数
从实现细节上看,函数只是个壳,真正被执行的是原生C/C++函数或chunk,函数包装了这两者和绑定变量。
在ymd语言中,函数没有什么特殊,它是一个引用类型的值,可被任意赋值,也能被垃圾回收器回收,也就是说,函数也有生存期。
每个脚本文件会被编译成一个chunk,每个脚本函数内部代码也被编译为chunk。关于函数和chunk的实现细节将会在后续文章中介绍。
param ::= identifier
|
params ::= params `,` param
| param
func_name ::= identifier
| identifier `.` identifer
func_define ::= `func` func_name `(` params `)` `{` body `}`
closure_define ::= `func` `[` binder `]` `(` params `)` `{` body `}`
示例代码:
func foo(name) { // 定义一个全局函数
print (name)
}
var func bar(name) { // bar是一个局部函数,即"bar"是一个局部变量
foo(name)
}
var func baz(name) {
var func foo(name) { // foo与全局函数foo不相关
__gc__.foo(name) // 显式指定调用全局foo
}
return func [foo] (name) { // 构造了一个匿名函数,并绑定局部变量foo
foo(name) // 引用绑定变量
}
}
var i = 0
var iter = func [i] () { // 构造一个匿名函数,每次调用返回一个整数。
i = i + 1
return i
}
print (iter()) // 打印 1
print (iter()) // 打印 2
print (iter()) // 打印 3 绑定变量"i"的生存期与iter变量指向的匿名函数是一直的,直到此匿名函数被垃圾回收前,一直会存在。
在一个函数中可无限嵌套定义函数,每个函数作用域即使开放又是封闭的,在函数内可访问所有全局变量,但不能访问到上一层函数的局部变量,但通过`[` binder `]`语法可将上一层函数中的局部变量或全局变量绑定到函数中,成为一个绑定变量。
访问局部变量和绑定变量的速度非常快,虚拟机只需用下标就能获得他们。访问全局变量的速度稍慢些,所以提倡多使用局部变量。
4.库函数
目前yamada script拥有三个函数库,这三个函数库都是用C语言实现的。
4.1内建函数
print([[[arg0], arg1], ...])
打印一个或多个变量。
insert(array, [element]|[position, element)
insert(object, key, value)
插入元素到数组、哈希表、跳表中。
append(array, elem0 [[[, elem1], elem1], ...])
append(object, pair)
增加一个元素到数组、哈希表、跳表中。
remove(object, key)
从一个哈希表、跳表中删除一个键和这个键对应的值。
len(arg)
获取一个nil值、字符串、数组、哈希表、跳表的长度。nil的长度恒为0。
str(arg)
将一个变量转换为一个可打印的字符串。
range(min, max [, step])
返回一个闭包,每次调用会依次返回[min, max)区间内的整数,每次步进由step指定,默认为1。
range(arg)
当arg为数组、哈希表、跳表时,返回一个可遍历其值的闭包。
rank(arg)
与range(arg)类似,但返回的闭包遍历键和值,结果保存在一个数组中。
ranki(arg)
与range(arg)类似,但返回的闭包遍历键。
panic([[[arg0], arg1], ...])
抛出panic,虚拟机将会捕获到这个错误,并停止运行。
strbuf()
返回一个StringBuffer,相当与一个可修改其内容的字符串,为了防止字符串操作中产生过多中间值,加重垃圾回收器的负担。
StringBuffer是一个managed类型值。
示例代码:
s = strbuf()
s = s:cat("i="):cat(0):cat("\\n"):cat("j="):cat(-1)
print (s:get())
// 输出:
// i=0
// j=-1
open(name [, mode])
打开一个文件。
示例代码:
f = open("/dev/random")
buf = f:read(8) // 读取8个字节。
f:close() // 显式关闭文件,如不显式关闭文件,它也会在被垃圾回收时被自动关闭。
pattern(arg)
生成一个正则表达式对象。
match(regex, str)
用pattern生成的正则表达式对象,对字符串str进行匹配。
import(file)
编译并执行file指定的文件,对file,这个操作仅会被执行一次。
eval(literal)
编译并执行字符串literal,返回值就是执行结果。
compiler(literal)
编译字符串literal。
atoi(str)
将一个字符串转换为int。
exit()
使虚拟机停止执行。
rand()
rand(num)
rand(min, max)
返回一个随机数。
gc(command)
向垃圾回收器发送指令。
command:
"*pause" 暂停垃圾回收
"*resume" 重启垃圾回收
"*collect" 强制开始一次完整的垃圾回收过程。
error([[[arg0], arg1], ...])
抛出一个错误,这个错误将会被pcall函数捕获,如果没有任何pcall调用,错误将会被一直向上抛。如果没有任何pcall处理这个错误,虚拟机会停止运行。
pcall(function, [[[arg0], arg1], ...])
在保护模式下运行function,参数为arg0~argN,安全模式可捕获error函数抛出的错误,但不能捕获panic。
4.2序列化函数
pickle.dump(object)
序列化object,除lite、managed、natvie function不能被序列化外所有值都能被序列化。
"int"类型使用zigzag + varint算法,会以比较紧凑的方式序列化。函数序列化是能将绑定变量的状态也序列化。
这个函数可能抛出无法序列化的错误。
pickle.load(str)
反序列化str。
这个函数可能抛出无法反序列化的错误。
示例代码:
var func build(i) {
return func [i] () {
i = i + 1; return i
}
}
var iter = build(99)
print (iter()) // "100"
print (iter()) // "101"
var packed = pickle.dump(iter)
print (typeof packed)
var new_iter = pickle.load(packed)
print (new_iter()) // "102"
print (new_iter()) // "103"
结语
yamada script的语法非常简单,到此为止就基本介绍完了。yamada script主要参考lua的实现,略带现代动态语言特征的玩具级脚本语言;作者希望她能对
文本处理等琐碎问题的处理有所帮助:)
在未来的系列文章,会详细的讨论实现细节。下一章暂定名:《脚本语言ymd:语法分析》
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:wuke.cj 来源: 量子恒道官方博客
- 标签: ymd
- 发布时间:2012-08-20 23:40:24
-
[884] WordPress插件开发 -- 在插件使用 -
[136] 解决 nginx 反向代理网页首尾出现神秘字 -
[57] 整理了一份招PHP高级工程师的面试题 -
[54] Innodb分表太多或者表分区太多,会导致内 -
[54] 如何保证一个程序在单台服务器上只有唯一实例( -
[54] 分享一个JQUERY颜色选择插件 -
[53] jQuery性能优化指南 -
[52] 用 Jquery 模拟 select -
[52] CloudSMS:免费匿名的云短信 -
[51] 全站换域名时利用nginx和javascri
