ObjectScript 1.5 编程
ObjectScript 是一种新的嵌入式编程语言,它融合了 JavaScript、Lua、Ruby、Python 和 PHP 的优点。ObjectScript 具有 JavaScript 的语法、Lua 的多重结果、Ruby 的语法糖、Python 的魔术方法等等。
ObjectScript 是一种新的嵌入式编程语言,它融合了 JavaScript、Lua、Ruby、Python 和 PHP 的优点。ObjectScript 具有 JavaScript 的语法、Lua 的多重结果、Ruby 的语法糖、Python 的魔术方法等等。
开始
ObjectScript 是一种动态类型语言。这意味着您在声明变量时不必指定数据类型,并且数据类型在脚本执行过程中会根据需要自动转换。因此,例如,您可以如下定义一个变量
var a = 12;
稍后,您可以为同一变量赋一个字符串值,例如
a = "Hello World!";
因为 ObjectScript 是动态类型的,所以此赋值不会导致错误消息。
区分大小写
ObjectScript 区分大小写,null 与 Null、NULL 或任何其他变体都不同。通常,类的名称以大写字母开头,而函数或变量的名称以小写字母开头。
空白和分号
字符串常量之外的空格、制表符和换行符称为空白。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 识别以下类型的值- null,一个表示 null 值的特殊关键字
- boolean,可以是 true 或 false
- number,例如 10、0x123、2.3、5.7e23
- string,例如 "Hello World!"
- object,例如 {"one", "two", 12:"at index 12"}
- array,例如 ["one", 31]
- function,例如 function(){ print "Hello World!" }
- userdata,允许将任意 C 数据存储在 ObjectScript 变量中
null、boolean、number 和 string 是原始类型。
object、array、function 和 userdata 不是原始类型,可以作为值的命名容器使用。
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
布尔值
布尔类型有两个值,false 和 true,它们代表传统的布尔值。然而,它们并不垄断条件值:在 ObjectScript 中,任何值都可以代表一个条件。条件语句(例如控制结构中的条件语句)将 false、null 和 NaN(非数字)视为 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
注意:参见 printf、sprintf、print、echo 函数。
数字具有 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 具有以下类型的运算符
- 赋值运算符
- 比较运算符
- 算术运算符
- 位运算符
- 逻辑运算符
- 字符串运算符
- 特殊运算符
赋值和多重赋值运算符
赋值是更改变量或对象属性值的主要手段
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
所有其他比较运算符对以下类型使用快速实现:null、boolean、number。在这种情况下,参数被转换为数字(null 转换为 0,true 转换为 1,false 转换为 0),然后用于进行实际比较(不使用 __cmp 方法)。
如果其中一个参数不是以下类型之一:null、boolean、number,则比较运算符处理规范的 __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
算术运算符
所有算术运算符对以下类型使用快速实现:null、boolean、number。在这种情况下,参数被转换为数字(null 转换为 0,true 转换为 1,false 转换为 0),然后用于进行算术运算(不使用魔术方法)。
如果其中一个参数不是以下类型:null、boolean、number,则算术运算返回调用的魔术方法的结果。
+ 加法或调用魔术 __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
位运算符
所有位运算符对以下类型使用快速实现:null、boolean、number。在这种情况下,参数被转换为数字(null 转换为 0,true 转换为 1,false 转换为 0),然后用于进行位运算(不使用魔术方法)。
如果其中一个参数不是以下类型:null、boolean、number,则位运算返回调用的魔术方法的结果。
& 是按位 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 的核心中包含几个对象,它们是名为 Object、Array、String、Number、Boolean、Function 和 Userdata 的全局变量。
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,其中 exp1 和 exp2 是任何有效的表达式,它等价于
(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 参考
- ObjectScript 仓库
- ObjectScript fastcgi 守护进程
- objectscript.org 是 ObjectScript 语言 fastcgi 守护进程的使用演示
- cocos2d-os 是一个使用 ObjectScript 和 Marmalade SDK 制作的 2d 游戏框架
ObjectScript 是通用的脚本语言,不再有任何妥协。
感谢您的阅读。期待您的回复。
Evgeniy Golovin