65.9K
CodeProject 正在变化。 阅读更多。
Home

ObjectScript 1.5 编程

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2013年3月25日

MIT

22分钟阅读

viewsIcon

20133

downloadIcon

186

ObjectScript 是一种新的嵌入式编程语言,它融合了 JavaScript、Lua、Ruby、Python 和 PHP 的优点。ObjectScript 具有 JavaScript 的语法、Lua 的多重结果、Ruby 的语法糖、Python 的魔术方法等等。

欢迎来到 ObjectScript 1.5

ObjectScript 是一种新的嵌入式编程语言,它融合了 JavaScript、Lua、Ruby、Python 和 PHP 的优点。ObjectScript 具有 JavaScript 的语法、Lua 的多重结果、Ruby 的语法糖、Python 的魔术方法等等。

开始 

ObjectScript 是一种动态类型语言。这意味着您在声明变量时不必指定数据类型,并且数据类型在脚本执行过程中会根据需要自动转换。因此,例如,您可以如下定义一个变量

    var a = 12;

稍后,您可以为同一变量赋一个字符串值,例如

    a = "Hello World!";

因为 ObjectScript 是动态类型的,所以此赋值不会导致错误消息。

区分大小写

ObjectScript 区分大小写,nullNullNULL 或任何其他变体都不同。通常,类的名称以大写字母开头,而函数或变量的名称以小写字母开头。

空白和分号

字符串常量之外的空格、制表符和换行符称为空白。ObjectScript 源代码中的空白不影响语义。ObjectScript 会自动检测语句,格式正确的语句将被视为完整(如同在语句之间插入了分号一样)。程序员可以显式提供语句终止分号。

在 return 语句之后和嵌套块之前显式使用分号,以避免意外影响。

    return;
    return a, b;
    ;{ // nested block
        var c = a; // c is block scoped variable
    }

注释

注释语法与 C++ 和许多其他语言相同。

    // a short, one-line comment

    /* this is a long, multi-line comment
       about my script. May it one day
       be great. */

    /* Comments /* may not be nested */ Syntax error */

变量

变量没有附加类型,任何值都可以存储在任何变量中。变量使用 var 语句声明,可以一次声明多个变量。标识符必须以字母、下划线 (_) 或美元符号 ($) 开头;后续字符也可以是数字 (0-9) 和 at 符号 (@)。变量是块作用域的。声明的变量可以由子块和函数访问。变量值在初始化之前为 null。未声明的变量是环境变量(参见**环境和全局环境**)。

    x = 0 // environment variable, because it is not declared using var statement
    var function test(){ return 1 } // local function
    function f() // environment function
    {
      var z, r = 'foxes', 'birds' // initialized 2 local variables
      m = 'fish' // environment because it wasn't declared anywhere before
      var function child()  // local function
      {
         var r = 'monkeys'  // this variable is local and does not affect the "birds" r of the parent function
         z = 'penguins'     // the child function is able to access the variables of the parent function, this is called closure
      }
      ;{ // nested block
          twenty = 20       // environment variable, because it is not declared using var statement
          var twenty = twenty   // local variable initialized by environment variable
          var z = z         // initialize local z variable using z of parent
          z = 'bears'       // assign local z variable
          child()           // call child function of parent block
      }
      return x;             // we can use x here because it is environment
    }
    f()

当 ObjectScript 尝试解析标识符时,它会在局部块作用域中查找(块作用域可以是函数)。如果未找到此标识符,它会在声明局部作用域的外部块中查找,并沿着作用域链一直向上,直到到达环境变量所在的环境作用域。如果仍然未找到,ObjectScript 将使用环境魔术 getter 或 setter 方法(参见**属性、Getter 和 Setter**)。

当赋值给标识符时,ObjectScript 执行完全相同的过程来检索此标识符,只不过如果它在环境作用域中未找到,它将把“变量”创建为环境对象的属性(参见**环境和全局环境**)。因此,如果赋值给一个从未声明的变量,它将成为环境变量。在环境代码中(即在任何函数体之外)声明变量(使用关键字 var)将声明一个新的局部变量,因为 ObjectScript 使用包装函数来执行代码。

请注意,您可以通过实现魔术 __get 方法来禁止读取未声明的环境变量

    function __get(name){
        throw "read undeclared '${name}' variable"
    }

您可以使用相同的技术实现类的自动加载(模块)

    var checked = {}
    function __get(name){
        if(!(name in checked)){
            checked[name] = true
            require(name)
            if(name in this){
                return this[name]
            }
        }
        throw "unknown class or global variable ${name}"
    }

函数调用语法

当您调用函数时,可以向其传递一些值,这些值称为参数。这些参数可以在函数内部使用。您可以发送任意数量的参数,用逗号 (,) 分隔

    printf("My name is %s, I'm %d age old", "Ivan", 19) // calls printf with 3 arguments

在声明函数时,将参数声明为变量

    function myFunction(var1, var2)
    {
        return var1 + var2
    }
    print(myFunction(2, 3)) // outputs: 5

如果函数调用没有参数,则必须写入一个空列表 () 以指示调用

    function test(){ print("Hello world!") }
    test() // outputs: Hello world!

此规则有一些特殊情况。如果函数有一个单一的 object 参数,则括号是可选的

    print { firstname = "Ivan", lastname = "Petrov" }
    // it's equivalent to
    print({ firstname = "Ivan", lastname = "Petrov" })

如果函数有一个单一参数(不是对象),并且函数调用位于块语句的顶层(不在表达式中),则括号是可选的

    function test(a){
        print a // it's OK
        ;{
            print a // it's OK
        }
        // it's incorrect to call math.round with 'a' argument here, parentheses required
        // print(2 + math.round a)
        print(2 + math.round(a)) // it's OK
    }
    test 10 // it's OK

参见 函数

魔术常量

ObjectScript 中有几个魔术常量

__FILE__ - 当前文件的文件名,__LINE__ - 当前行号

    print("filename: ", __FILE__)
    print("line: ", __LINE__)

类型和值

ObjectScript 识别以下类型的值
  1. null,一个表示 null 值的特殊关键字
  2. boolean,可以是 truefalse
  3. number,例如 100x1232.35.7e23
  4. string,例如 "Hello World!"
  5. object,例如 {"one", "two", 12:"at index 12"}
  6. array,例如 ["one", 31]
  7. function,例如 function(){ print "Hello World!" }
  8. userdata,允许将任意 C 数据存储在 ObjectScript 变量中

nullbooleannumberstring 是原始类型。

objectarrayfunctionuserdata 不是原始类型,可以作为值的命名容器使用。

Null (空值)

Null 是一种只有一个值 null 的类型。所有变量默认情况下都有一个 null 值,在第一次赋值之前,您可以将 null 赋值给一个变量以将其删除。ObjectScript 将 null 用作一种非值。

如果要检查某个值是否为 null,应使用 ===!== 运算符,例如

    val = null
    print(typeOf(val))  // outputs: null
    print(val === null) // outputs: true
    print(val === 0)    // outputs: false
    print(val == 0)     // outputs: true

布尔值

布尔类型有两个值,falsetrue,它们代表传统的布尔值。然而,它们并不垄断条件值:在 ObjectScript 中,任何值都可以代表一个条件。条件语句(例如控制结构中的条件语句)将 falsenullNaN(非数字)视为 false,将其他任何值视为 true

请注意,ObjectScript 在条件测试中将零和空字符串都视为 true

    print(typeOf(true))         // outputs: boolean
    print(null ? true : false)  // outputs: false
    print(1 > 2 ? true : false) // outputs: false
    print(1 ? true : false)     // outputs: true
    print(0 ? true : false)     // outputs: true
    print("0" ? true : false)   // outputs: true
    print("" ? true : false)    // outputs: true
    print([1,2] ? true : false) // outputs: true

数字

您可以编写带有可选小数部分和可选十进制指数的数字常量。有效的数字常量示例有
    12      // decimal, base 10
    3.14    // 3.14, floating-point number
    3.14f   // 3.14, floating-point, C++ compatibility
    2.5e3   // 2500, exponent format number
    0xfe    // 254, hexadecimal, "hex" or base 16
    0123    // 83, octal, base 8
    0b110   // 6, binary, base 2

数字在二进制中以 IEEE-754 双精度浮点数(默认)表示,提供近 16 位有效数字的精度。因为它们是浮点数,所以它们并不总是精确地表示实数,包括分数

    printf("%.20f\n", 0.94 - 0.01)  // outputs: 0.93000000000000005000
    printf("%f\n", 0.94 - 0.01)     // outputs: 0.930000

    // use human friendly number format
    printf("%n\n", 0.94 - 0.01)     // outputs: 0.93

    // numbers are converted to string using human friendly format
    print(0.94 - 0.01)              // outputs: 0.93

    print(typeOf(10.5))             // outputs: number

注意:参见 printfsprintfprintecho 函数。

数字具有 Number 类的原型

    print(10.prototype === Number) // outputs: true

转换为数字

将原始类型转换为数字

  • null 转换为 0
  • true 转换为 1
  • false 转换为 0
  • "12" 转换为 12
  • "0x12" 转换为 18
  • "0123" 转换为 83
  • "0b10" 转换为 2 
  • "10hello" 转换为 0
  • 12 值已经是数字

所有其他类型都通过调用 valueOf 方法转换为字符串,该方法必须返回原始类型的值。

您可以使用 numberOf 函数检查数字,并使用 toNumber 转换为数字

    printf("        %9s %9s\n", 'numberOf', 'toNumber')
    printf("---------------------------\n")
    printf("  null: %9s %9s\n", numberOf(null), toNumber(null))
    printf("  true: %9s %9s\n", numberOf(true), toNumber(true))
    printf(" false: %9s %9s\n", numberOf(false), toNumber(false))
    printf("  \"12\": %9s %9s\n", numberOf("12"), toNumber("12"))
    printf("\"0x12\": %9s %9s\n", numberOf("0x12"), toNumber("0x12"))
    printf("\"0123\": %9s %9s\n", numberOf("0123"), toNumber("0123"))
    printf("\"0b10\": %9s %9s\n", numberOf("0b10"), toNumber("0b10"))
    printf("\"12lo\": %9s %9s\n", numberOf("12lo"), toNumber("12lo"))
    printf("    12: %9s %9s\n", numberOf(12), toNumber(12))

    var obj = {
        valueOf = function(){
            return 10
        }
    }
    printf("   obj: %9s %9s\n", numberOf(obj), toNumber(obj))

    var arr = [1,2,3]
    printf(" array: %9s %9s\n", numberOf(arr), toNumber(arr))

输出

             numberOf  toNumber
    ---------------------------
      null:      null         0
      true:         1         1
     false:         0         0
      "12":        12        12
    "0x12":        18        18
    "0123":        83        83
    "0b10":         2         2
    "12lo":      null         0
        12:        12        12
       obj:      null        10
     array:      null         0

字符串

ObjectScript 中的字符串是字符序列。字符串是不可变的,这意味着您不能更改对象本身。所有相同的字符串都引用内存中的同一位置(包括在运行时计算)。因此,当 ObjectScript 比较字符串是否相等时,它只比较引用指针。

    print(typeOf("0123456789"))     // outputs: string
    print("0123456789".sub(2, 4))   // outputs: 2345

字符串具有 String 类的原型

    print("".prototype === String) // outputs: true

字符串字面量可以通过四种不同的方式指定

  • 单引号
  • 双引号
  • 原始 heredoc
  • heredoc

单引号

指定字符串的最简单方法是将其用单引号字符 (') 括起来。

要指定文字单引号,请用反斜杠 (\) 转义它。要指定文字反斜杠,请将其加倍 (\\)。反斜杠的所有其他实例都将视为文字反斜杠:这意味着您可能习惯的其他转义序列,例如 \r\n,将按指定文字输出,而没有任何特殊含义。

    print 'this is a simple string'

    // outputs: Arnold once said: "I'll be back"
    print 'Arnold once said: "I\'ll be back"'

    // outputs: You deleted C:\*.*?
    print 'You deleted C:\\*.*?'

    // outputs: You deleted C:\*.*?
    print 'You deleted C:\*.*?'

    // outputs: This will not expand: \n a newline
    print 'This will not expand: \n a newline'

    // outputs: Expressions do not ${expand} ${either}
    print 'Expressions do not ${expand} ${either}'

双引号

如果字符串用双引号 (") 括起来,ObjectScript 将解释更多特殊字符的转义序列

  • \n - 换行符 (LF 或 ASCII 中的 0x0A (10))
  • \r - 回车符 (CR 或 ASCII 中的 0x0D (13))
  • \t - 水平制表符 (HT 或 ASCII 中的 0x09 (9))
  • \" - 双引号
  • \\ - 反斜杠
  • \$ - 美元符号
  • \[1-9]\d{0,2} - 匹配正则表达式的字符序列是十进制表示的字符
  • \0[0-7]{1,3} - 匹配正则表达式的字符序列是八进制表示的字符
  • \x[0-9A-Fa-f]{1,2} - 匹配正则表达式的字符序列是十六进制表示的字符

与单引号字符串一样,转义任何其他字符也会导致反斜杠被打印出来。

双引号字符串最重要的特性是可以在其中执行字符串表达式。

字符串表达式的语法

字符串表达式以 ${ 开头,以 } 结尾。要指定文字 ${,请用反斜杠 \${ 转义它。

    print "this is a string"

    // outputs: three plus three is 6
    print "three plus three is ${3+3}"

    foobar = "blah"
    // outputs: the value of foobar is blah
    print "the value of foobar is ${foobar}"

    // outputs: the value of foobar is ${foobar}
    print "the value of foobar is \${foobar}"

    function factorial(a){
        return a <= 1 ? 1 : a*factorial(a-1)
    }
    var p = 10
    // outputs: factorial of 10 is 3628800
    print "factorial of ${p} is ${factorial(p)}"

原始 heredoc

原始 heredoc 以 <<<MARKER'(<<< + MARKER + ' - 单引号)开头,以 MARKER 结尾。字符串本身紧随其后,然后是相同的标记以关闭字符串。原始 heredoc 文本的行为就像单引号字符串一样。原始 heredoc 内部不进行解析。这种构造非常适合嵌入代码或其他大块文本,而无需转义。它与 SGML 的 <![CDATA[ ]]> 构造有一些共同的特点,因为它声明了一块不进行解析的文本。

原始 heredoc 会删除第一行带空格和换行符。如果结束标记位于行首位置,它还会删除最后一个换行符。

    // outputs: this is a string
    print <<<~~~'this is a string~~~

    // outputs: true
    print 'this is a string' == <<<~~~'
    this is a string
    ~~~

    print <<<==='

    Harry Potter is due to start
    his fifth year at Hogwarts School
    of Witchcraft and Wizardry.

    ===

    print 'end'

Heredoc

Heredoc 以 <<<MARKER"(<<< + MARKER + " - 双引号)开头,以 MARKER 结尾。字符串本身紧随其后,然后是相同的标记以关闭字符串。Heredoc 的指定方式类似于原始 heredoc,但 heredoc 内部会进行解析。参见**字符串表达式语法**。

ObjectScript 会将转义序列 \$ 解释为 $ (美元符号)。

转换为字符串

将原始类型转换为字符串

  • null 转换为 "null" 字符串
  • true 转换为 "true" 字符串
  • false 转换为 "false" 字符串
  • 0.3 转换为 "0.3" 字符串,数字使用人类友好的数字格式 %n 转换为字符串(参见 printf 函数)
  • string 值已经是字符串

所有其他类型都通过调用 valueOf 方法转换为字符串,该方法必须返回原始类型的值。

您可以使用 stringOf 函数检查字符串,并使用 toString 转换为字符串

    printf("        %9s %9s\n", 'stringOf', 'toString')
    printf("---------------------------\n")
    printf("  null: %9s %9s\n", stringOf(null), toString(null))
    printf("  true: %9s %9s\n", stringOf(true), toString(true))
    printf(" false: %9s %9s\n", stringOf(false), toString(false))
    printf("  \"12\": %9s %9s\n", stringOf("12"), toString("12"))
    printf("\"0x12\": %9s %9s\n", stringOf("0x12"), toString("0x12"))
    printf("\"0123\": %9s %9s\n", stringOf("0123"), toString("0123"))
    printf("\"0b10\": %9s %9s\n", stringOf("0b10"), toString("0b10"))
    printf("    12: %9s %9s\n", stringOf(12), toString(12))

    var obj = {
        valueOf = function(){
            return 10
        }
    }
    printf("   obj: %9s %9s\n", stringOf(obj), toString(obj))

    var arr = [1,2,3]
    printf(" array: %9s %9s\n", stringOf(arr), toString(arr))

输出

             stringOf  toString
    ---------------------------
      null:      null      null
      true:      true      true
     false:     false     false
      "12":        12        12
    "0x12":      0x12      0x12
    "0123":      0123      0123
    "0b10":      0b10      0b10
        12:        12        12
       obj:      null        10
     array:      null   [1,2,3]

注意:字符串不实现字符访问,但您可以这样实现(参见**属性、Getter 和 Setter**)

    function String.__get(i){
        return this.sub(i, 1)
    }

    function String.__getdim(i, count){
        return this.sub(i, count)
    }

    var str = "0123456789"
    print(str.sub(2, 4))    // outputs: 2345
    print(str[2])           // outputs: 2
    print(str[2, 3])        // outputs: 234

对象

一个 object 是零个或多个属性名及其关联值对的列表

    {"one", "two", 12:"at index 12", ["user" .. "name"]: "at index username", some = "extended syntax"}

最简单的 object 构造函数是空构造函数

    a = {}; // create an empty object and store its reference in a

您不应在语句的开头使用对象字面量 {。这会导致行为不符合您的预期,因为 { 将被解释为块的开头

    {
        var a = 12;
    }

让我们看一些例子

    days = {"Sunday", "Monday", "Tuesday", "Wednesday",
            "Thursday", "Friday", "Saturday"}

将用字符串 "Sunday" 初始化 days[0](第一个元素始终索引为 0),用 "Monday" 初始化 days[1],依此类推

    print(days[4])  // outputs: Thursday

构造函数不需要只使用常量表达式。您可以使用任何类型的表达式作为值

    tab = {sin(1), sin(2), sin(3), sin(4)}

为了初始化一个用作记录的 object,ObjectScript 提供了以下语法

    a = {x=0, y=0}

它等价于

    a = {x=0, y=0,}
    a = {["x"]=0, ["y"]=0}
    a = {x:0, y:0}
    a = {x:0; y:0;}

因此,您始终可以在最后一个条目后放置逗号,可以使用 ":" 或 "=" 来创建属性对,您始终可以使用分号 (;) 代替逗号 (,)。

    a = { one = "great", two = "awesome", 1 = "value at index 1" };
    print(typeOf(a))        // outputs: object

    b = "one";
    a = { [b] = "great" };  // it's the same as: a = {}; a[b] = "great"
    print(a[b])             // outputs: great
    print(a["one"])         // outputs: great
    print(a.one)            // outputs: great

    a = { one = "great" };  // it's the same as: a = { "one": "great" }
    print(a[b])             // outputs: great
    print(a["one"])         // outputs: great
    print(a.one)            // outputs: great

    a = { one = "great", two = "awesome" };
    a = { one: "great", two: "awesome" }; // JavaScript object notation is fully supported
    a = { one: "great", two: "awesome", }; // a comma after the last entry is valid

    a = {"zero", "one", "two"}
    print(a) // outputs: {0:"zero", 1:"one", 2:"two"}
    delete a[1]
    print(a) // outputs: {0:"zero", 2:"two"}

参见 面向对象编程 (OOP)

一个对象具有 Object 类的原型

    print({}.prototype === Object) // outputs: true

数组

一个 array 是一个索引值的列表

    a = ["zero", "one", "two"]
    print(typeOf(a))    // outputs: array
    print(a.length)     // outputs: 3
    print(a[1])         // outputs: one
    delete a[1]
    print(a.length)     // outputs: 2
    print(a[1])         // outputs: two

数组是一种非常紧凑的结构,因此如果您不需要属性容器,那么使用 array 而不是 object 是一个很好的理由。

数组具有 Array 类的原型

    print([].prototype === Array) // outputs: true

函数

您可以将函数视为应用程序可以执行的过程

    var max = function(a, b){ return a > b ? a : b }
    print(max(10, 3)) // outputs: 10

函数在 ObjectScript 中是第一类值。这意味着函数可以存储在变量中,作为参数传递给其他函数,并作为结果返回。

ObjectScript 的一个非传统但非常方便的特性是函数可以返回多个结果

    var func = function(){ return 1, 2 }
    var x, y = func()
    print x // outputs: 1
    print y // outputs: 2 

函数本身就是对象。因此,它们具有属性和方法。对函数的任何引用都允许使用 () 运算符调用它

    var func = function(){ print arguments }
    var func2 = function(f){ f.apply(null, ...) }
    func2(func,1,2,3)   // outputs: [1,2,3]

三个点 (...) 是剩余参数,它返回传递给函数的未声明参数的数组,arguments 返回传递给函数的所有参数的数组。

此外,ObjectScript 支持嵌套函数和闭包。嵌套函数是在另一个函数内部定义的函数。它们在每次调用外部函数时创建。除此之外,每个创建的函数都形成一个词法闭包:外部函数的词法作用域,包括任何局部变量和参数,即使在外部函数执行结束后,也成为每个嵌套函数对象的内部状态的一部分

    var a = function(){
        var x = 2
        return function(){ retutn x++ }
    }
    var b = a()
    print b() // outputs: 2
    print b() // outputs: 3
    print b() // outputs: 4

函数返回计算的最后一个表达式

    print({|a, b| a + b}(2, 3)) // outputs: 5

函数具有 Function 类的原型

    print((function(){}).prototype === Function) // outputs: true

链式函数调用

    var obj = {
        do = function(a){
            print a;
            return this;
        }
    }
    obj.do{
        1: "one"
    }.do{
        2: "two"
    }.do{
        3: "three"
    }

输出

    {1:"one"}
    {2:"two"}
    {3:"three"}

标准函数变量

任何函数都包含以下局部变量

  • this - 方法可以通过 this 变量访问实例变量
  • _F - 当前函数的别名
  • _E - 函数环境(参见 全局变量
  • _G - 全局变量(参见 全局变量

替代函数语法

您可以使用 {|params|body} 语法声明函数,例如

    3.times{|i| print i}

输出

    0
    1
    2

它如何在 ObjectScript 中工作?

首先,让我们为数字声明 times 方法。数字具有 Number 原型,因此我们应该为 Number 原型添加新方法

    function Number.times(func){ // it's equal to Number.times = function(func) ...
        for(var i = 0; i < this; i++){
            func(i)
        }
    }

一切都完成了,让我们使用它

    5.times(function(i){ print i })

或者只使用语法糖

    3.times{|i| print i}

再举一个例子

    print "factorial(20) = " .. {|a| a <= 1 ? 1 : a*_F(a-1)}(20)

输出

    factorial(20) = 2432902008176640000

C++ 中的函数

ObjectScript 可以调用用 ObjectScript 编写的函数以及用 C++ 或 C 编写的函数。ObjectScript 中的所有标准库都用 C++ 编写。应用程序可以定义 C 中的其他函数

    #include "objectscript.h"

    int test(OS * os, int, int, int, void*)
    {
        os->pushNumber(123);
        return 1; // number of values that function returns
    }

    int main()
    {
        OS * os = OS::create();     // create OS instance
        os->pushCFunction(test);
        os->setGlobal("test");
        os->eval("print(test())");  // outputs: 123
        os->release();              // reelease OS instance
        return 0;
    }

ObjectScript 中还包含绑定器,因此您可以像这样声明 C++ 函数

    #include "objectscript.h"
    #include "os-binder.h"

    float test(float a, float b){ return a + b; }

    int main()
    {
        OS * os = OS::create();
        os->setGlobal(def("test", test));   // use binder
        os->eval("print(test(2, 3))");      // outputs: 5
        os->release();
        return 0;
    }

请参阅 osbind MSVC 示例项目和存储库中包含的更多示例。

用户数据

一个 userdata 允许将任意 C++ 或 C 数据存储在 ObjectScript 变量中。它用于表示由应用程序或用 C++ 或 C 编写的库创建的新类型。

运算符

ObjectScript 具有以下类型的运算符

  1. 赋值运算符
  2. 比较运算符
  3. 算术运算符
  4. 位运算符
  5. 逻辑运算符
  6. 字符串运算符
  7. 特殊运算符

赋值和多重赋值运算符

赋值是更改变量或对象属性值的主要手段

    a = "Hello" .. " world!"
    t.n = t.n + 1
    a = b = 2

ObjectScript 允许多重赋值,其中值列表在一步中赋值给变量列表。两个列表的元素都用逗号分隔

    a, b = 1, 2

变量 a 得到值 1,b 得到值 2。在多重赋值中,ObjectScript 首先计算所有值,然后才执行赋值。因此,您可以使用多重赋值来交换两个值,如下所示

    x, y = y, x                // swap x for y
    a[i], a[j] = a[j], a[i]    // swap a[i] for a[j]

ObjectScript 总是将值的数量调整为变量的数量。当值列表短于变量列表时,额外的变量接收 null 作为它们的值;当值列表长于变量列表时,额外的值会被静默丢弃

    a, b, c = 0, 1
    print(a, b, c)         // 0   1   null
    a, b = a+1, b+1, b+2   // value of b+2 is ignored
    print(a,b)             // 1   2
    a, b, c = 0
    print(a,b,c)           // 0   null   null

上面的最后一个赋值显示了一个常见错误。要初始化一组变量,您必须为每个变量提供一个值 

    a, b, c = 0, 0, 0
    print(a, b, c)           // 0   0   0

您可以使用多重赋值简单地在一行中编写多个赋值。但通常您确实需要多重赋值,例如,交换两个值。更常见的使用是收集函数调用的多个返回值

    a, b = f()

f() 返回两个结果:a 得到第一个,b 得到第二个。

要用一个值初始化一组变量,请使用以下语法

    a = b = c = 0

比较运算符

=== 完全等于(值和类型)

    print(2 === "5") // outputs: false
    print(2 === "2") // outputs: false
    print(2 === 2) // outputs: true

!== 不完全等于(值和类型)

    print(2 !== "5") // outputs: true
    print(2 !== "2") // outputs: true
    print(2 !== 2) // outputs: false

所有其他比较运算符对以下类型使用快速实现:nullbooleannumber。在这种情况下,参数被转换为数字(null 转换为 0true 转换为 1false 转换为 0),然后用于进行实际比较(不使用 __cmp 方法)。

如果其中一个参数不是以下类型之一:nullbooleannumber,则比较运算符处理规范的 __cmp 方法的结果

    function __cmp(b){
        // returns negative number if this < b
        // returns positive number if this > b
        // returns 0 if this == b
    }

<=> 使用 __cmp 方法的规范比较参数

    print(2 <=> 3) // outputs: -1
    print(2 <=> 2) // outputs: 0

== 等于

    print(2 == 3) // outputs: false
    print(2 == 2) // outputs: true

!= 不等于

    print(2 != 3) // outputs: true
    print(2 != 2) // outputs: false

> 大于

    print(2 > 3) // outputs: false
    print(2 > 2) // outputs: false

< 小于

    print(2 < 3) // outputs: true
    print(2 < 2) // outputs: false

>= 大于或等于

    print(2 >= 3) // outputs: false
    print(2 >= 2) // outputs: true

<= 小于或等于

    print(2 <= 3) // outputs: true
    print(2 <= 2) // outputs: true

算术运算符

所有算术运算符对以下类型使用快速实现:nullbooleannumber。在这种情况下,参数被转换为数字(null 转换为 0true 转换为 1false 转换为 0),然后用于进行算术运算(不使用魔术方法)。

如果其中一个参数不是以下类型:nullbooleannumber,则算术运算返回调用的魔术方法的结果。

+ 加法或调用魔术 __add 方法

    print(2 + 3) // outputs: 5

- 减法或调用魔术 __sub 方法

    print(2 - 3) // outputs: -1

* 乘法或调用魔术 __mul 方法

    print(2 * 3) // outputs: 6

/ 除法或调用魔术 __div 方法

    print(2 / 4) // outputs: 0.5

% 取模(除法余数)或调用魔术 __mod 方法

    print(5 % 2) // outputs: 1

** 将第一个参数提升到第二个参数的幂,或调用魔术 __pow 方法

    print(25 ** 0.5) // outputs: 5

一元 + 不改变数字参数的符号,或调用魔术 __plus 方法

    print(+(5))     // outputs: 5
    print(+(-5))    // outputs: -5

一元 - 改变数字参数的符号,或调用魔术 __neg 方法

    print(-(+5))    // outputs: -5
    print(-(-5))    // outputs: 5

++ 递增(此运算符仅允许用于局部变量)

    var a = 5
    print(++a)      // expanded to (a = a + 1), outputs: 6
    print(a)        // outputs: 6
    print(a++)      // expanded to (temp = a; a = a + 1; temp), outputs: 6
    print(a)        // outputs: 7

-- 递减(此运算符仅允许用于局部变量)

    var a = 5
    print(--a)      // outputs: 4
    print(a)        // outputs: 4
    print(a--)      // outputs: 4
    print(a)        // outputs: 3

注意:字符串没有 + 运算符,但您可以将其实现为连接,例如

    function String.__add(b){
        return this .. b
    }

    print "foo" + 5 // outputs: foo5

位运算符

所有位运算符对以下类型使用快速实现:nullbooleannumber。在这种情况下,参数被转换为数字(null 转换为 0true 转换为 1false 转换为 0),然后用于进行位运算(不使用魔术方法)。

如果其中一个参数不是以下类型:nullbooleannumber,则位运算返回调用的魔术方法的结果。

& 是按位 AND,对于两个操作数的相应位都为一的每个位位置,返回一(或调用魔术 __bitand 方法)

    a = 6
    b = 3
    print(a & b) // outputs: 2

| 是按位 OR,对于两个操作数中任一或两者都为一的每个位位置,返回一(或调用魔术 __bitor 方法)

    a = 6
    b = 3
    print(a | b) // outputs: 7

^ 是按位 XOR,对于两个操作数中任一但不都为一的每个位位置,返回一(或调用魔术 __bitxor 方法)

    a = 6
    b = 3
    print(a ^ b) // outputs: 5

一元 ~ 是按位 NOT,反转其操作数的位(或调用魔术 __bitnot 方法)

    a = 6
    print(~a) // outputs: -7

<< 是左移,将 a 在二进制表示中向左移动 b 位,从右侧移入零(或调用魔术 __lshift 方法)

    a = 6
    b = 1
    print(a << b) // outputs: 12

>> 是右移,将 a 在二进制表示中向右移动 b 位,丢弃移出的位(或调用魔术 __rshift 方法)

    a = 6
    b = 1
    print(a >> b) // outputs: 3

逻辑运算符

&& 是逻辑 AND,如果 a 可以转换为 false,则返回 a;否则,返回 b。因此,当与布尔值一起使用时,如果两个操作数都为 true,则 && 返回 true;否则,返回 false

    a = 6
    b = null
    print(a && b) // outputs: null

|| 是逻辑 OR,如果 a 可以转换为 true,则返回 a;否则,返回 b。因此,当与布尔值一起使用时,如果任一操作数为 true,则 || 返回 true;如果两者都为 false,则返回 false

    a = 6
    b = null
    print(a || b) // outputs: 6

?: 是条件运算符,如果条件可以转换为 true,则返回 a;否则,返回 b

    a = 6
    b = 3
    print(a < b ? a : b) // outputs: 3

一元 ! 是逻辑 NOT,如果其单个操作数可以转换为 true,则返回 false;否则,返回 true

    a = 6
    print(!a)     // outputs: false

注意:使用 !! 将值转换为布尔类型。

字符串运算符

.. 是字符串连接(参见 转换为字符串

    a = 5
    b = true
    print(a .. b) // outputs: 5true

特殊运算符

in 返回一个布尔值,具体取决于第二个参数的类型。

如果第二个参数是对象,则 in 返回 true,如果指定属性在指定对象中。

如果第二个参数是数组,则 in 返回 true,如果指定值包含在指定数组中。

    var a = {1: "one", second: "two"}
    print(1 in a)           // outputs: true
    print("second" in a)    // outputs: true
    print("one" in a)       // outputs: false

    var b = [1, "one", "two"]
    print(1 in b)           // outputs: true
    print("second" in b)    // outputs: false
    print("one" in b)       // outputs: true

实际上,此运算符返回全局 __in 函数的结果。

extends 运算符通过另一个对象扩展一个对象。它通常用于面向对象编程。

    var IvanPerson = extends Person {
        __construct: function(){
            super("Ivan", "Petrov")
        }
    }

实际上,此运算符返回全局 __extends 函数的结果

    function __extends(obj, obj2){
        obj2.prototype = obj
        return obj2
    }

参见 面向对象编程 (OOP)

delete 删除属性或数组索引(参见 属性、Getter 和 Setter

    a = [1, 2, 3, 4, 5]
    delete a[1]
    print(a)    // outputs: [1,3,4,5]

    a = {one=1, two=2]
    delete a.one
    print(a)    // outputs: {"two":2}

实际上,此运算符调用全局 __delete 函数。

is 返回一个布尔值,指示一个对象是否是特定类的实例。

    classA = {}
    classB = extends classA {}
    a = classA()        // create new inctance of classA
    b = classB()        // create new inctance of classB
    print(a is classA)  // outputs: true
    print(b is classA)  // outputs: true
    print(a is classB)  // outputs: false
    print(b is classB)  // outputs: true
    print(classA is classA)     // outputs: false
    print(classB is classA)     // outputs: true
    print(classA is classB)     // outputs: false
    print(classB is classB)     // outputs: false

实际上,此运算符返回全局 __is 函数的结果。

isprototypeof 返回一个布尔值,指示一个对象是否是特定类或特定类的实例。

    classA = {}
    classB = extends classA {}
    a = classA()        // create new inctance of classA
    b = classB()        // create new inctance of classB
    print(a isprototypeof classA)   // outputs: true
    print(b isprototypeof classA)   // outputs: true
    print(a isprototypeof classB)   // outputs: false
    print(b isprototypeof classB)   // outputs: true
    print(classA isprototypeof classA)      // outputs: true
    print(classB isprototypeof classA)      // outputs: true
    print(classA isprototypeof classB)      // outputs: false
    print(classB isprototypeof classB)      // outputs: true

实际上,此运算符返回全局 __isprototypeof 函数的结果。

一元 # 返回魔术 __len 方法的结果。__len 方法已为以下类型声明

  • array - 返回元素数量
  • string - 返回字符串的字节长度
  • object - 返回属性数量

例如

    a = "abc"
    print(#a)   // outputs: 3
    a = [1, 2, 3, 4, 5]
    print(#a)   // outputs: 5

ObjectScript 中也有 length 属性

    function Object.__get@length(){ return #this }

因此,您可以使用 length 属性作为 # 运算符的别名

    a = "abc"
    print(a.length)     // outputs: 3
    a = [1, 2, 3, 4, 5]
    print(a.length)     // outputs: 5

... 是剩余参数,返回传递给函数的未声明参数的数组

    function test(a, b){
        print(...)
        print(arguments)
    }

    test(1, 2, 3, 4, 5)

输出

    [3,4,5]
    [1,2,3,4,5]

全局变量

全局变量不需要声明。您只需为全局变量赋值即可创建它。访问未初始化的变量不是错误,您只会得到特殊值 null 作为结果

    print(a)  // outputs: null
    a = 10
    print(a)  // outputs: 10

通常,您不需要删除全局变量,如果您的变量生命周期很短,您应该使用局部变量。但是,如果您需要删除全局变量,只需将其赋值为 null

    a = 10
    a = null
    print(a)  // outputs: null

环境和全局环境

对全局名称 v 的任何引用在语法上都转换为 _E.v。此外,每个函数都在一个名为 _E 的外部局部变量的作用域内编译,因此 _E 本身在函数中从不是全局名称。

任何用作 _E 值​​的对象都称为环境。

ObjectScript 保留一个称为全局环境的特殊环境。此值保存在 C 注册表中的一个特殊对象中。在 ObjectScript 中,变量 _G 初始化为相同的值。

当 ObjectScript 编译函数时,它会使用全局环境初始化其 _E 上值。因此,默认情况下,ObjectScript 代码中的全局变量引用全局环境中的条目。此外,所有标准库都加载在全局环境中,并且那里的几个函数都在该环境上操作。

您可以使用 Function.applyEnv 方法执行带有不同环境的任何函数。例如,让我们查看 std.os 文件中 evalEnv 函数的代码

    function evalEnv(str, env){
        return compileText(str).applyEnv(env || _G, null, ...)
    }

If ... Else 语句

条件语句用于根据不同的条件执行不同的操作。

在编写代码时,您经常需要根据不同的决策执行不同的操作。您可以在代码中使用条件语句来完成此操作。

ObjectScript 中有以下条件语句

if 语句 - 使用此语句仅在指定条件为 true 时执行某些代码

    a = 2
    b = 5
    if(a < b){ print 'true' }   // outputs: true

if...else 语句 - 使用此语句在条件为 true 时执行某些代码,在条件为 false 时执行其他代码

    a = 7
    b = 5
    if(a < b){
        print 'true'
    }else{
        print 'false'
    }
    // outputs: false

if...elseif....else 语句 - 使用此语句选择要执行的多个代码块之一

    a = 5
    b = 5
    if(a < b){
        print 'true'
    }elseif(a == b){
        print 'equal'
    }else{
        print 'false'
    }
    // outputs: equal

注意:如果您愿意,可以使用 else if 代替 elseif

For 循环

ObjectScript 中有两种 for 循环。第一种与 C++ 和其他一些语言相同

    for(var i = 0; i < 3; i++){
        print i
    }

输出

    0
    1
    2

第二种用于迭代过程

    for(var i, v in ["zero", "one", "two"]){
        print("${i}: ${v}")
    }

输出

    0: zero
    1: one
    2: two

参见 迭代器

Break 和 Continue

break 语句“跳出”循环。

    for(var i = 0; i < 10; i++){
        print i
        if(i == 3) break
    }

输出

    0
    1
    2
    3

continue 语句“跳过”循环中的一次迭代。

    for(var i = 0; i < 5; i++){
        if(i % 2 == 0) continue
        print i
    }

输出

    1
    3

Try, Catch, Throw

try 语句允许您测试一段代码是否有错误。

catch 语句允许您处理错误。

throw 语句允许您创建自定义错误。

    function printException(e){
        print e.message
        for(var i, t in e.trace){
            printf("#${i} ${t.file}%s: %s, args: ${t.arguments}\n",
                t.line > 0 ? "(${t.line},${t.pos})" : "",
                t.object && t.object !== _G ? "<${typeOf(t.object)}#${t.object.id}>.${t.name}" : t.name)
        }
    }

    function testFunction(){
        try{
            var a = 1
            var b = 0
            var c = a / b
        }catch(e){
            print "\ncatch exception #1"
            printException(e)
            throw "error message"
        }
    }

    try{
        testFunction()
    }catch(e){
        print "\ncatch exception #2"
        printException(e)
    }

输出

    catch exception #1
    division by zero
    #0 ../../examples-os/try.os(14,13): testFunction, args: {}
    #1 ../../examples-os/try.os(23,14): {{main}}, args: {}
    #2 {{CORE}}: require, args: {"filename":"../../examples-os/try.os","required":true,"source_code_type":0,"check_utf8_bom":true}

    catch exception #2
    error message
    #0 ../../examples-os/try.os(18,3): testFunction, args: {}
    #1 ../../examples-os/try.os(23,14): {{main}}, args: {}
    #2 {{CORE}}: require, args: {"filename":"../../examples-os/try.os","required":true,"source_code_type":0,"check_utf8_bom":true}

迭代器

迭代器允许您遍历集合的元素。例如

    a = { null, true, 12, "0" }
    for(k, v in a){
        print("${k} --> ${v}")
    }

输出

    0 --> null
    1 --> true
    2 --> 12
    3 --> 0

ObjectScript 将上述程序编译为

    a = { null, true, 12, "0" }
    {
        var iter_func = a.__iter();
        for(var iter_valid;;){ // infinite loop
            iter_valid, k, v = iter_func();
            if(!iter_valid) break;
            print("${k} --> ${v}")
        }
    }

iter_func 的第一个返回值指示当前步骤是否有效,第二个和其他返回值是用户使用的值。

任何迭代器都需要在连续调用之间保留一些状态,以便它知道它在哪里以及如何从那里继续。闭包为此任务提供了出色的机制。请记住,闭包是一个访问其封闭函数的局部变量的函数。

例如,array 迭代器声明为

    Array.__iter = function(){
        var i, self = 0, this
        return function(){
            if(i < #self){
                return true, i, self[i++]
            }
        }
    }

注意,函数的迭代器是它本身。它声明为

    Function.__iter = function(){ return this }

所以我们可以这样编写迭代器

    var range = function(a, b){
        return function(){
            if(a <= b){
                return true, a++
            }
        }
    }

    for(var i in range(10, 13)){
        print( "i = ", i )
    }

输出

    i = 10
    i = 11
    i = 12
    i = 13

另一个例子

    Range = {
        __construct = function(a, b){
            if(b){
                this.a, this.b = a, b
            }else{
                this.a, this.b = 0, a - 1
            }
        },
        __iter = function(){
            var a, b = this.a, this.b
            return a <= b
                ? {|| a <= b && return true, a++ }
                : {|| a >= b && return true, a-- }
        },
    }

    for(var i in Range(-2, -4))
        print i

输出

    -2
    -3
    -4

再举一个例子

    function Number.to(b){
        return Range(this, b)
    }

    for(var i in 5.to(7))
        print i

输出

    5
    6
    7

属性、Getter 和 Setter

getter 是一种获取特定属性值的方法。setter 是一种设置特定属性值的方法。

    a = {
        __color: "red",
        __get@color = function(){ return this.__color },
        __set@color = function(v){ this.__color = v },
    }

    print a.color       // outputs: red
    a.color = "blue"
    print a.color       // outputs: blue

请注意,@ 不是一个特殊符号,任何函数和变量都可以包含 @。

另一个例子

    a = {
        __color: "red",
        __get = function(name){
            if(name == "color")
                return this.__color
        },
        __set = function(name, v){
            if(name == "color")
                this.__color = v
        },
        __del = function(name){
            if(name == "color")
                delete this.__color
        },
    }

    print a.color       // outputs: red
    a.color = "blue"
    print a.color       // outputs: blue
    delete a.color
    print a.color       // outputs: null

多维属性

ObjectScript 支持 a.color (或 a["color"]) 语法。那么这个呢?

    a[x, y] = 12

是的,ObjectScript 支持上述语法,这被称为多维属性

    a = {
        __matrix = {},
        __getdim = function(x, y){
            return this.__matrix[y*4 + x]
        },
        __setdim = function(value, x, y){
            this.__matrix[y*4 + x] = value
        },
        __deldim = function(x, y){
            delete this.__matrix[y*4 + x]
        },
    }
    // it's compiled to a.__setdim(5, 1, 2)
    a[1, 2] = 5         
    // it's compiled to print(a.__getdim(1, 2)
    print a[1, 2]                               // outputs: 5
    // it's compiled to a.__deldim(1, 2)
    delete a[1, 2] 
    print a[1, 2]                               // outputs: null

请注意,__setdim 方法接收第一个参数作为新属性值,其他参数作为属性的维度属性。例如

    a = {
        __matrix = {},
        __getdim = function(x, y, z){
            return this.__matrix[z*16 + y*4 + x]
        },
        __setdim = function(value, x, y, z){
            this.__matrix[z*16 + y*4 + x] = value
        },
    }
    a[1, 2, 3] = 5
    print a[1, 2, 3] // outputs: 5

空维属性

那么这个呢?

    b = a[]
    a[] = 2
    delete a[]

ObjectScript 提供了以下特殊方法 __getempty__setempty__delempty,您可以根据需要使用它们。

面向对象编程 (OOP)

面向对象编程是一种编程范式,它使用抽象来创建基于真实世界的模型。它使用多种技术和范式,包括模块化、多态性和封装。

ObjectScript 是一种 OOP 语言,您还可以重写算术、位运算、连接、比较和一元运算符。

核心对象

ObjectScript 的核心中包含几个对象,它们是名为 ObjectArrayStringNumberBooleanFunctionUserdata 的全局变量。

ObjectScript 中的每个对象都是 Object 的实例,因此继承其所有属性和方法。

自定义对象

    var a = {
        num: 1,
        __get@number: function(){
            return this.num
        },
        __add = function(b){
            return this.number + b.number
        },
    }

    var b = extends a {
        num: 2,
    }

    print a + b     // outputs: 3

ObjectScript 是一种基于原型的语言,不包含类语句。任何对象都可以用作类。

    Person = {
        __construct = function(firstname, lastname){
            this.firstname = firstname
            this.lastname = lastname
        },
        __get@fullname = function(){
            return this.firstname .. " " .. this.lastname
        },
        walk = function(){
            print this.fullname .. " is walking!"
        },
    }   

对象(类实例)

要创建对象的新实例,我们对类变量使用 () 运算符

    var p = Person("James", "Bond")

等同于

    var p = {}
    p.prototype = Person
    p.__construct("James", "Bond")

run

    p.walk()    // outputs: James Bond is walking!
    print p     // outputs: {"firstname":"James","lastname":"Bond"}

继承

继承是一种将类创建为类的专门版本的方法。您需要使用 extends 运算符

    var IvanPerson = extends Person {
        __construct: function(){
            super("Ivan", "Petrov")
        },
    }
    var p = IvanPerson()
    p.walk()    // outputs: Ivan Petrov is walking!
    print p     // outputs: {"firstname":"Ivan","lastname":"Petrov"}

extends 运算符的语法是 extends exp1 exp2,其中 exp1exp2 是任何有效的表达式,它等价于

    (function(exp1, exp2){
        exp2.prototype = exp1
        return exp2
    })()

封装

在前面的示例中,IvanPerson 不需要知道 Person 类的 walk() 方法是如何实现的,但仍然可以使用该方法。IvanPerson 类不需要显式定义该方法,除非我们想改变它。这称为封装,通过它,每个类都继承其父类的方法,并且只需要定义它希望更改的内容。

默认实例属性

ObjectScript 中有一个名为 __object 的特殊属性,用于描述默认实例属性

    Test = {
        __object = {
            a = 0,
            b = [1, 2],
        }
    }

    Test2 = extends Test {
        __object = {
            a = 12345
        }
    }

    var v1 = Test()         // create new instance of Test class
    v1.b[0] = 10
    var v2 = Test2()        // create new instance of Test2 class
    print "v1: "..v1        // outputs: v1: {"a":0,"b":[10,2]}
    print "v2: "..v2        // outputs: v2: {"a":12345,"b":[1,2]}
    print "class: "..Test   // outputs: class: {"__object":{"a":0,"b":[1,2]}}

替代 'this' 语法

ObjectScript 中有另一种语法用于读取和写入实例属性

    __construct = function(firstname, lastname){
        @firstname = firstname  // it's the same this.firstname = firstname
        @lastname = lastname    // it's the same this.lastname = lastname
    }   

简单的 OOP 示例

    var vec3 = {
        __construct = function(x, y, z){
            @z, @y, @x = z, y, x
        },
        __add = function(b){
            vec3(@x + b.x, @y + b.y, @z + b.z)
        },
        __mul = function(b){
            vec3(@x * b.x, @y * b.y, @z * b.z)
        },
    }

    var v1 = vec3(10, 20, 30)
    var v2 = vec3(1, 2, 3)
    var v3 = v1 + v2 * v2
    print v3

输出

    {"x":11,"y":24,"z":39}

从源代码编译 ObjectScript

克隆仓库

    git clone git://github.com/unitpoint/objectscript.git
    cd objectscript

为 Linux 编译 ObjectScript

    mkdir build && cd build
    cmake -DCMAKE_INSTALL_PREFIX=$(pwd)/../ ..
    make 
    make install

结果文件(os、oscript)将位于 bin 文件夹中。运行 ObjectScript 程序

    oscript script.os

注意:ObjectScript 使用外部安装的 PCRE(Perl 兼容正则表达式库)和 cURL(客户端 URL 传输库)。

为 Windows 编译 ObjectScript

打开 proj.win32\examples.sln,MSVC 解决方案中包含有用的项目。

资源

ObjectScript 是通用的脚本语言,不再有任何妥协。

感谢您的阅读。期待您的回复。 

Evgeniy Golovin   

© . All rights reserved.