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

ObjectScript:一种新的编程语言

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.24/5 (6投票s)

2012年9月27日

MIT

9分钟阅读

viewsIcon

84987

downloadIcon

123

ObjectScript 是一种新的编程语言,它融合了 JavaScript、Lua 和 PHP 的优点。ObjectScript 拥有 JavaScript 的语法,Lua 的多重返回值,PHP 的面向对象特性,以及更多功能。

欢迎来到 ObjectScript!

ObjectScript 是一种新的嵌入式编程语言,它融合了 JavaScript、Lua 和 PHP 的优点。ObjectScript 拥有 JavaScript 的语法,Lua 的多重返回值,PHP 的面向对象特性,以及更多功能。

ObjectScript 的源代码 小于 500 KB,您可以在这里下载:https://github.com/unitpoint/objectscript

入门

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

var a = 12;
之后,您可以为同一个变量赋一个字符串值,例如:
a = "Hello World!";

由于 ObjectScript 是动态类型的,因此此赋值不会引发错误消息。ObjectScript 是区分大小写的,**null** 与 **Null**、**NULL** 或其他任何变体都不同。

类型和值 

ObjectScript 识别以下类型的值:

  1. **null**,一个表示 `null` 值的特殊关键字。
  2. **boolean**,即 `true` 或 `false`。
  3. **number**,例如 `10`、`0x123`、`2.3`、`5.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 变量中。

**null**、**boolean**、**number** 和 **string** 是基本类型。

**object**、**array**、**function** 和 **userdata** 不是基本类型,可以作为命名容器来存储值。

Nulls

Null 是一种类型,只有一个值 **null**。所有变量在第一次赋值之前默认都具有 **null** 值,您也可以将 **null** 赋给变量来删除它。ObjectScript 将 **null** 用作一种“非值”。

布尔值

布尔类型有两个值:**false** 和 **true**,它们代表传统的布尔值。然而,它们并不拥有条件值的“垄断权”:在 ObjectScript 中,任何值都可以代表一个条件。条件(如控制结构中的条件)会将 **false**、**null** 和 **NaN**(非数字)视为 **false**,而其他所有值都视为 **true**。

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

数字

数字类型代表实数(双精度浮点数)。您可以编写带有可选小数点和可选科学计数法的数字常量。有效的数字常量示例如下:

12 // decimal, base 10
3.14
2e10
0xfe // hexadecimal, "hex" or base 16
0123 // octal, base 8

字符串

字符串字面量是由零个或多个字符组成的,并用双引号(")括起来。以下是字符串字面量的示例。

"Hello World!"
"one line \n another line"

您可以调用 **String** 对象的任何方法,例如,您可以使用 String.length 属性来获取字符串字面量的长度。

"Hello World!".length

或者直接使用 length 运算符:

#"Hello World!"

对象

**Object** 是零个或多个属性名与其关联值配对的列表。

{"one", "two", 12:"at index 12", ["on" .. "e"]: "at index one", 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"}

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

print(days[4])  --> 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 = {x=0, y=0}
a = {x:0, y=0}

所以你总可以在最后一个条目后加上逗号,你可以使用 ":" 或 "=" 来配对属性。你也可以始终使用分号 (;) 代替逗号 (,)

a = {x=1, y=3; "one", "two"}

最后,你可以避免使用逗号和分号。

    
a = {x=1 y=3 "one" "two"}

b = "one";
a = { [b] = "great" }; // the same as { [b]: "great" }
print(a[b]) --> great
print(a["one"]) --> great
print(a.one) --> great

a = { one = "great" }; // the same as { "one": "great" }
print(a[b]) --> great
print(a["one"]) --> great
print(a.one) --> 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 = { one: "great" two="awesome" 1="value at index 1" }; // syntax without "," is also valid

a = {"one", "two"};
print(#a) --> 2

数组

**Array** 是一个索引值列表。

a = ["zero", "one", "two"]
print(a[1]) --> one
print(#a) --> 3

函数

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

var max = function(a, b){ return a < b ? a : b }

函数是 ObjectScript 中的一等值。这意味着函数可以存储在变量中,作为参数传递给其他函数,或者作为结果返回。ObjectScript 的一个非传统但非常方便的特性是函数可以返回多个结果。

var func = function(){ return 1, 2 }
var x, y = func()
print x --> 1
print y --> 2

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

var func = function(){ print arguments }
var func2 = function(f){ f.apply(null, ...) }
func2(func,1,2,3) --> {1,2,3}

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

var a = function(){
    var x = 2
    return function(){ retutn x++ }
}
var b = a()
print b() --> 2
print b() --> 3
print b() --> 4

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

int test(OS * os, int, int, int, void*)
{
    os->pushNumber(123);
    return 1;
}

int main(int argc, char* argv[])
{
    OS * os = OS::create();

    os->pushCFunction(test);
    os->setGlobal("test");
    
    os->eval("print(test())"); // outputs 123

    os->release();
    return 0;
}

Userdata

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

赋值和多重赋值运算符

赋值是改变变量或对象属性值的基本方式。

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

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** 获得第二个。

全局变量

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

print(a)  --> null
a = 10
print(a)  --> 10

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

a = null
print(a)  --> null

在此之后,就好像该变量从未被使用过一样。换句话说,一个全局变量是存在的(并且仅当)它有一个非 null 值。

环境和全局环境

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

尽管存在这个外部 **_E** 变量并且全局名称被翻译,**_E** 是一个完全规则的名称。特别是,您可以使用该名称定义新的变量和参数。对全局名称的每次引用都使用在程序中该点可见的 **_E**,遵循 ObjectScript 的常规可见性规则。

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

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

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

您可以使用 Function.applyEnv 方法用不同的环境执行任何函数。例如,让我们看看 **core.os** 文件中的 **eval** 函数的代码:

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

迭代器

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

a = { null true 12 "0" } // is equivalent to { null, true, 12, "0" }
for(k, v in a){
    print( k " --> " v ) // is equivalent to 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

属性、Getter 和 Setter

Getter 是一个获取特定属性值的函数。Setter 是一个设置特定属性值的函数。

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

print a.color --> red
print a["color"] --> red
a.color = "blue"
print a.color --> blue
print a["color"] --> 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 --> red
a.color = "blue"
print a.color --> blue
delete a.color
print a.color --> 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]
    }
}
a[1, 2] = 5 // is equivalent to a.__setdim(5, 1, 2)
print a[1, 2] --> 5 // print(a.__getdim(1, 2)
delete a[1, 2] // a.__deldim(1, 2)
print a[1, 2] --> 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] --> 5

空维度属性

那么呢?

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

ObjectScript 提供了以下特殊方法 `__getempty`、`__setempty` 和 `__delempty`,您可以在需要时使用它们。

面向对象编程 (OOP)

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

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

核心对象

ObjectScript 在其核心包含多个对象,有全局变量 Object、Array、String、Number、Boolean 和 Function。

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

自定义对象

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

var b = extends a {
    num: 2
}

print a + b     --> 3

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

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!"
    }
}

Object(类实例)

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

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

等同于

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

Run

p.walk()    --> James Bond is walking!
print p        --> {firstname:James,lastname:Bond}

继承

继承是一种创建类作为另一个类的专门版本的方式。您需要使用 **extends** 运算符。

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

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

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

封装

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

简单而强大的示例

var vec3 = {
    __construct = function(x, y, z){
        this.x = x
        this.y = y
        this.z = z
    }
    __add = function(a, b){
        return vec3(a.x + b.x, a.y + b.y, a.z + b.z)
    }
    __mul = function(a, b){
        return vec3(a.x * b.x, a.y * b.y, a.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 的完整源代码: 

© . All rights reserved.