技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 编程语言 --> 脚本语言ymd:介绍

脚本语言ymd:介绍

浏览:2846次  出处信息

脚本语言ymd


    ymd全称yamada script,是某一淘数据部员工业余时间完成的一个玩具脚本语言,其语法类似lua和javascript。代码托管在github

     目前只支持Linux x86_64,预计未来会支持Windows/Mac OS。

     yamada名称由来是动画《Working!!》角色:山田 葵(Yamada Aoi)

    

1.特点


  • 动态类型
  • 简单、轻量
  • 函数是第一类值,支持闭包
  • 与C轻松绑定
  • 垃圾回收
  •     一句话概括: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种:

  • nil:nil类型只有一个值nil
  • int:64位的有符号整数
  • bool:有两个值true/false
  • string:字符串的内容是不能改变的
  • function:函数也是一个值
  • array:动态数组
  • hashmap:哈希表
  • skiplist:跳表
  • lite:C/C++指针
  • managed:被托管的C/C++对象,能被垃圾回收器处理,这是lite类型不能做到的
  •     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语言实现的。

  • Builtin:语言的一部分
  • Test:单元测试库
  • Pickle:序列化、反序列化
  • OS:系统调用相关函数,未完成。
  • 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:语法分析》

    QQ技术交流群:445447336,欢迎加入!
    扫一扫订阅我的微信号:IT技术博客大学习
    © 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

    京ICP备15002552号-1