脚本语言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
- [53] IOS安全–浅谈关于IOS加固的几种方法
- [52] 如何拿下简短的域名
- [51] 图书馆的世界纪录
- [50] Oracle MTS模式下 进程地址与会话信
- [50] android 开发入门
- [49] Go Reflect 性能
- [46] 【社会化设计】自我(self)部分――欢迎区
- [46] 读书笔记-壹百度:百度十年千倍的29条法则
- [36] 程序员技术练级攻略
- [29] 视觉调整-设计师 vs. 逻辑