-
Notifications
You must be signed in to change notification settings - Fork 1
Language Specification
在行间任何位置加入井号 #
, 此后直到此行换行符均视作注释, 如
console.log(0) # log "0" in console
func ifnot if else return extern export typeof true false try catch throw
class this super ctor break continue for enum include
- 任意的单个大写字母
- 任意数量的连续下划线
from finally with yield while gen delete
标识符用于表示一个值的引用, 形式参数, 对象属性等.
标识符的构成是, 以下划线或字母开头, 后跟任意多个下划线或数字或字母的非保留字.
Flatscript 支持的字面常量包括
- 十进制整数, 表示为为至少 1 位 0-9 数字, 如
123456789
,42
; 在各位数字之间可以插入下划线来分隔数字, 使数字显得更加易读, 如1_234_567_890
- 十进制小数, 表示为任意多位 0-9 数字后 1 个小数点, 之后至少 1 位 0-9 数字, 如
3.14
,2.718
,.707
; 在各位数字之间可以插入下划线来分隔数字, 使数字显得更加易读, 如1_234_567.891_234
- bool 类型值, 字符串
true
或false
- 字符串, 以双引号 " 或单引号 ' 引起的字符序列, 字符序列中如果出现了引号, 反斜杠 \ 或其它需要转义实现的字符, 在该字符之前加上一个反斜杠. 可转义的字符包括: 单引号由
\'
转义, 双引号由\"
转义, 反斜杠由\\
转义, 换行符由\n
转义, 制表符由\t
转义, 如"Hello, World!"
,'Hello,\nWorld!'
; 或以三个双引号 """ 或三个单引号 ''' 引起的字符序列, 此时字符序列中如果出现单个或两个引号, 无须转义, 如'''<input type='text' value=''>'''
简单字面量的单目运算或简单字面量之间的双目运算将在编译期进行常量折叠. 如果发现不支持的运算将在编译时报错, 如整数与 bool 类型值进行减法运算等.
简单字面量和它们经过算数运算, 比较运算, 逻辑运算得到的结果均为 编译时可推导的.
表示为方括号 [ 开始, 反方括号 ] 结束, 其中任意多个表达式, 每两个表达式之间用逗号隔开, 如 [ 2, 3, 5, 7, 11, 13 ]
.
表示为大括号 { 开始, 反大括号 } 结束, 其中任意多个属性名-属性值对, 每两个名-值对之间用逗号隔开, 名-值对表示为冒号隔开的标识符 (或简单字面量) 与表达式, 如 { hello: 'world', good: 'day' }
; 最后一个名-值后可以添加一个额外的逗号, 如
{
hello: 'world',
'good': 'day',
}
表达式中的标识符表示对一个值的引用.
若标识符引用的是编译时可推导的, 则该标识符也视为编译时可推导的.
同 Javascript 中的正则表达式.
写作 $
, $i
, $k
的词法元素. 参见之后 "管道" 一节.
写作 $e
的词法元素, 参见之后 "异常处理" 一节.
包括一元正 (+), 一元负 (-), 加法 (+), 减法 (-), 乘法 (*), 除法 (/), 取模 (%), 书写方式与数学一致.
一元算术运算符优先级最高, 乘除取模次之, 加减最低.
包括取反 (~), 与 (&), 或 (|), 异或 (^), 左移 (<<), 右移 (>>), 无符号右移 (>>>).
其中取反优先级与一元算术运算符相同, 二元算术运算优先级低于加减运算符.
算符为 ++, 二元运算, 将两个列表中的元素顺序连接构成新的列表.
优先级等同于加减运算符.
包括等于 (=), 大于等于 (>=), 小于等于 (<=), 大于 (>), 小于 (<), 不等于 (!=).
比较运算符优先级低于二元位运算符.
包括非 (!), 与 (&&), 或 (||).
当与运算左参数值为假时, 表达式的值为假; 当或运算左参数值为真时, 表达式的值为真. (这些情况运行时均跳过右参数计算)
非优先级最高, 与次之, 或最低.
逻辑运算符优先级低于任何比较运算符.
条件选择运算语法形如
consequence if predicate else alternative
其中 predicate
为条件, 若条件为真值, 则表达式的值同 consequence
, 否则同 alternative
. 运行时仅计算 consequence
与 alternative
两者之一.
三个子表达式均不得直接递归地包含另一个条件选择表达式, 即下面的表达式不合法
a if b if c else d else e
a if b else c if d else e
但可以括号间接地包含另一个条件选择表达式, 如
a if (b if c else d) else e
(a if b else c) if d else e
a if b else (c if d else e)
条件运算符优先级低于任何逻辑运算符.
表达式之后通过方括号查找集合中的成员, 该表达式应该是列表或字典这样的集合类型. 如
x: ['a', 'b', 'c', 'd']
y: x[0]
m: { 'first': 0, 'second': 1, 'third': 2 }
n: m['first']
此时 y
的值为 'a', m
的值为 0.
Flatscript 提供了列表切片表达式, 在列表对象后方括号中放入 2 至 3 个由逗号隔开的值表示, 如
x: ['a', 'b', 'c', 'd']
a: x[2,]
b: x[,2]
c: x[,,2]
d: x[,,-1]
此时 c
的值为 ['c', 'd'], b
的值为 ['a', 'b'], c
的值为 ['a', 'c'], d
的值为 ['d', 'c', 'b', 'a']
其中, 第一个数值表示开始索引, 第二个表示结束索引 (不包括在内), 第三个表示步长, 若省略, 则
- 步长为正值时, 默认起始索引为 0, 否则为列表长度减 1 (即从最后开始)
- 步长为正值时, 默认结束索引为列表长度, 否则为 -1 (即到数组开头为止)
- 步长默认为 1, 如果省略, 则必须同时省去后一个逗号 (如
x[,,]
是不合法的)
管道是一系列表达式加上管道分隔符构成, 每一个表达式称为管道的 "节".
管道包括映射管道与过滤管道. 映射管道分隔符为 |:
, 过滤管道分隔符为 |?
.
管道的首节应该是一个类型为列表的对象.
映射管道将首节列表或对象中每个对象或属性按照指定表达式的映射方式, 生成一个新的列表, 此列表称为迭代结果, 是管道表达式的值. 如
[1, 3, 5, 7] |: $ * $
{
Monday: 1,
Tuesday: 2,
Wednesday: 3,
} |: $k + ' is workday.'
结果分别是
[1, 9, 25, 49]
[ "Monday is workday", "Tuesday is workday", "Wednesday is workday" ]
其中 $
表示每次迭代的对象, $k
表示属性名. 可用的管道对象包括
-
$
表示迭代对象 -
$i
或$index
表示迭代索引 -
$k
或$key
表示对象迭代的属性名 (在异步管道中, 该对象值将为null
) -
$r
或$result
表示迭代结果
过滤管道则是从列表中筛选出满足条件的对象, 构成新的列表, 如
[1, 3, 5, 7] |? $ % 3 != 0
则结果是
[1, 5, 7]
管道分隔符优先级低于条件选择运算.
另参见将语句块作为管道体.
当一个语句只含有一个管道时, 该管道为管道语句. 管道语句中可以使用 return
和 break
.
将表达式实参列表附加在引用之后表示调用函数, 如
nop()
fib(x + y)
add(1, 2, 3)
书写方式与数学一致. 可在最后一个实际参数之后多出 1 逗号.
在类的成员函数或构造函数的函数体内调用该类型的基类的指定成员函数. 形式必须是
super.BASE_CLASS_FUNCTION_NAME(ARGUMENTS)
BASE_CLASS_FUNCTION_NAME
必须是一个标识符, ARGUMENTS
部分同普通函数调用形式参数列表.
详见类和继承.
Lambda 即匿名函数, 行间形式以形式参数列表-冒号-表达式构成, 如
(x): x * x
该形式下, 表达式即视作函数的返回值, 如
[2, 3, 5, 7].map((x): x * x)
得到 [4, 9, 25, 49]
.
使用前置 * 操作符表示 Javascript 中的 new
操作符. 如
x: 'Javascript'.replace(*RegExp('Java', 'g'), 'Flatscript')
为了弥补行间匿名函数在表达力上的不足, Flatscript 允许定义多行 lambda. 在匿名函数的冒号后折行, 此后的若干缩进相同且缩进多于该表达式所属的行 (详情参见下面语句缩进规则) 的连续多行均视作该匿名函数的函数体. 如
[2, 3, 5, 7].map((x):
return x * x
)
或又如
setTimeout(():
do_a()
do_b()
, 2000)
缩进与多行 lambda 的进一步规则在下面折行规则之后会进一步详细说明.
在表达式之前如前置单目运算符一样放置 typeof
表示获取该对象类型表示的字符串.
对于简单字面量或其引用而言, typeof
能在编译时给出常量类型字符串.
this
关键字. 详见 This 引用.
见异步调用.
Flatscript 以缩进 (处于行首的空白) 标记函数或分支语句的开始与结束, 相同缩进的语句会被认为在同一个块中, 比如
func add(a, b)
return a + b
console.log(add(10, 5))
其中 func add(a, b)
是一个函数定义的开头, return a + b
为该定义的函数体中的语句. 而接下来的一句 console.log(add(10, 5))
缩进级别与 func
相同, 故不属于上述函数.
同一区块的语句缩进必须相同, 不能出现如下情况
console.log(0)
console.log(1)
缩进不能出现 tab 字符.
此语句用于单纯放置一个表达式, 该表达式中的值会被计算 (但不会被使用).
名字定义格式如下
标识符 : 表达式
表示用该标识符定义一个局部名字. 表达式中直接使用该名字, 即视为对冒号后表达式的值的引用.
名字一旦定义, 不能在相同空间内定义同样名字的其他值, 也不能修改此定义所引用的值.
使用关键字 enum
可以定义一系列递增的 编译时可推导的 常量, 如
enum X, Y, Z
则 X
, Y
, Z
的值分别是 0, 1, 2. 同一个 enum
内, 第一个定义的名字的值为 0, 之后每定义一个名字, 其相对于之前定义的一个名字的取值增加 1.
枚举语句支持在定义的名字后的逗号处折行, 如
enum A, B,
C, D
是一个完整的语句.
域级函数定义以关键字 func
开头, 后接函数名 (须是一个标识符), 之后为括号括起的形参列表, 如
func factorial(x)
接下来的语句为函数体. 函数体中每一条语句须多 1 级缩进, 若有子函数或分支语句, 则它们的子句继续缩进, 如
func factorial(x)
if x < 2
return 1
return factorial(x - 1) * x
函数定义不可以作为分支语句的字句.
在 Flatscript 语言中, 名字一旦定义就无法再次修改其引用值. 但可以通过属性设置的方式来修改该名字所引用的表达式的属性值
object.attribute: expression
object[attribute]: expression
此语法唯一区别于名字定义的是, 冒号左边的表达式不是一个单独的标识符. object
是任意的表达式; 第一种写法中 attribute
必须是标识符, 后一种写法中, attribute
可以是任意表达式.
类定义必须在域中, 不允许匿名类型. 定义了类之后, 该域空间内可以引用该类名. 但该类型定义之前, 其它类不能使用该类作为父类.
语法和规定详见类和继承.
函数计算得出结果, 或者需要终止时, 由返回语句交给函数调用者其结果. 返回语句有下面两种形式
return
return 表达式
前者返回空值到调用者, 而后者将表达式的值返回给调用者.
返回语句可以用于在同步函数调用中直接返回结果, 或在异步环境中以返回表达式作为参数值调用回调函数, 参考方法挑选.
判断当某条件成立时选择执行的代码, 如
if x < 0
return 0
分支语句的子句缩进要较 if
所在行多 1 级.
如果要表示在上述条件不满足时执行某些语句, 有如下写法
if x < 0
console.log(0)
else
console.log(x)
这表示当分支条件成立时执行某些子句, 而不成立时执行另一些子句; 或者, 用如下写法
ifnot x < 0
console.log(x)
这样表示分支条件不成立时执行某些子句 (这种形式不支持 else
分句).
基于一个数值范围的循环, 语法如
for NAME range BEGIN, END, STEP
LOOP_BODY
其中 for
是关键字, 但 range
不是关键字. NAME
必须是一个标识符, 指出循环变量名, BEGIN
是起始值, END
是阈值, 达到或超过则循环终止, STEP
是步长, LOOP_BODY
为一个语句块.
步长值 STEP
必须为一个可编译时推导的非零数值, 当其为正数时, NAME
对应的取值每次递增, 直到大于或等于 END
的值时循环结束; 当其为负数时, NAME
对应的值每次递减, 直到小于或等于 END
的值时循环结束.
BEGIN
, STEP
可以省略. 当 range
之后只有一个表达式时, 该表达式为 END
, BEGIN
默认为 0; 当 range
之后只有两个表达式时, 它们依次是 BEGIN
和 END
, STEP
默认取值为 1.
如
for i range 10
console.log(i) # 0 ... 9
for i range 2, 10
console.log(i) # 2 ... 9
s: -1
for i range 10, 0, s
console.log(i) # 10 ... 1
由于 range
并不是一个保留字, 在此语法中只起到标识作用, 故以下写法是合法的
range: 10
for i range range
console.log(i) # 0 ... 9
使用 break
中断循环, 使用 continue
进行下一次循环. 这两种语句应该以单一关键字占一行的形式给出.
如
for i range 10
if i % 3 = 0
continue
console.log(i)
['ab', 'cd', 'efg', 'hij'] |:
if $.length = 3
console.log($)
break
捕获指定语句块中的同步或异步语句产生异常, 如
try
any_statement
catch
do_something_with($e)
$e
表示此过程中被捕获的异常对象. $e
不可以在 catch
块外被使用.
详见异常处理.
产生一个异常, 终止执行流. 语法为
throw 表达式
在许多其他语言中支持的异常重抛出, 即类似 throw
关键字但不带表达式在 Flatscript 中不被支持, 因为在异步环境下调用栈很难被正确地体现.
抛出异常语句可以用于在同步函数调用中直接抛出异常, 或在异步环境中以异常表达式作为参数值调用回调函数, 参考方法挑选.
使用 try-catch 捕获任何发生在 try 直属语句块内的异常. 语法格式为
try
语句块
catch
语句块
在 直属语句块 中的语句指在 try 内的所有的变量声明, 属性设置, 分支子句, 或嵌套的 try-catch 子句, 返回值, 异常抛出, 同步或异步的函数调用, 管道, 但不包括内嵌的匿名函数的函数体. 在 samples/try-catch.stkn 中有许多关于捕获函数内抛出异常的例子, 而以下写法中, 匿名函数函数体中抛出的异常则不能被捕获
try
setTimeout(():
throw 'some exception'
, 1)
catch
console.log('caught')
在 catch
语句块中, 可以引用 $e
作为捕获的异常的值. 该异常可能由 throw
语句抛出, 也可能是正规异步调用的回调函数的错误参数值.
当文件中文件引用的名字的定义并不由该模块本身提供, 则应通过引入语句将名字添加到上下文
extern name
extern name_0, name_1, name_2
以上两个引入语句, 分别引入了名为 name
的名字, 与名为 name_0
, name_1
, name_2
3 个名字.
下面的名字会被默认定义在全局空间
console setTimeout setInterval clearTimeout parseInt parseFloat Number Date
Math Object Function escape unescape encodeURI encodeURIComponent decodeURI
decodeURIComponent JSON NaN null undefined isFinite isNaN RegExp
如果要在编译前引入其它名字, 可通过 -e
命令行参数, 如
$ flatsc -e document -e window < input.stkn > output.js
常见于浏览器脚本中, 如果要让某个值暴露到全局空间或暴露为模块接口, 需要导出该名字 (否则定义的名字均仅为当前文件空间可见)
export export_point: value
export ident_a.ident_b.ident_c: value
导出点必须是标识符, 或点号连接起来的多个标识符. 如 ab
, ab.cd.ef
都是合法的, 而 ab['cd'].ef
不合法.
对比于属性设置, 导出语句会协助确保导出点的合法性. 生成的 Javascript 代码不是一个单一的赋值. 如
export util.strings.escape: escaping_func
所生成的代码将类似
$export.util = $export.util || {};
$export.util.strings = $export.util.strings || {};
$export.util.strings.escape = escaping_func
其中 $export
将会根据情况设置为 window
或 module.exports
.
参见文件加载语句.
在 Flatscript 语句中, 任何引用都会在编译时检查其是否在上下文中有定义. 如下面的完整程序编译时会产生名字查找错误
http: require('http')
因为 require
并没有在上下文中定义. 若要修改, 可改为
extern require
http: require('http')
这样将在名字空间中引入外部定义的 require
, 在其后的代码中方可引用之.
一组缩进相同语句, 从名字定义的角度上称为 名字空间, 在名字空间中定义或导入的名字可以在该定义或导入语句之后被引用, 如
func echo()
hello: 'Hello, world!'
console.log(hello)
但在该语句之前不应引用, 如
func echo()
console.log(hello)
hello: 'Hello, world!'
在 console.log(hello)
一句会提示 console
未定义.
函数定义不受此限制, 函数可以在定义处前被使用, 如
func echo()
hello: 'Hello, world!'
console.log(hello)
echo()
等价于
echo()
func echo()
hello: 'Hello, world!'
console.log(hello)
但是, 若无特殊需求, 最好将函数定义置于名字空间最开始处.
而通过名字定义引入的匿名函数则例外, 如上述功能可作如下改写
echo: ():
hello: 'Hello, world!'
console.log(hello)
echo()
但不能改写成
echo()
echo: ():
hello: 'Hello, world!'
console.log(hello)
在名字空间中, 产生可以引用的名字共有下面方法
- 定义名字语句 (定义的名字)
- 名字导入语句 (导入的名字)
- 域级函数定义 (函数名, 将在整个名字空间中可见, 无论对该函数的引用在函数定义之前还是之后)
- 函数形参列表 (中的每个参数名)
- 异步占位符中的形式参数列表 (中的每个参数名)
- 加载文件作为模块
同一名字空间内, 相同名字不得重复这册.
语句除了可以引用当前名字空间中的名字, 还可以引用其所在名字空间外层名字空间中的名字, 如
func external_space(a)
func internal_space(b)
return a + b
return internal_space(42)
在 internal_space
中, 可以引用 external_space
中的名字 a
(以参数形式注册).
当内层空间注册了与外层空间相同的名字时, 内层空间中的名字会覆盖外层空间中的名字, 如
func external_space(a)
func internal_space(b)
a: 0
return a + b
return internal_space(42)
console.log(external_space(1))
这段代码执行的结果将是 42 而不是 43, 因为 internal_space
中定义的 a
覆盖了 external_space
形参 a
.
如果在内层空间先引用了外层空间的名字, 然后内层空间又定义了该名字, 则编译时会产生一个错误, 如
func external_space(a)
func internal_space(b)
x: a
a: 0
return x + b
return internal_space(42)
在编写程序时, 如果一行内容过于长, 建议将一行拆分为多行书写. 如
long_statement: expression_0 * expression_1 + expression_2 * expression_3
可写作
long_statement: expression_0 * expression_1 +
expression_2 * expression_3
但由于 Flatscript 的缩进语法特性, 折行受到一些语法限制, 如下面的写法
if expression_0
+ expression_1 * expression_2
无法被正确地解释语法; 否则, 可将 expression_0 + expression_1 * expression_2
解释为 if
的条件, 另外可以将 expression_0
解释为 if
的条件而 +expression_1 * expression_2
被解释为第一条语句. 下面将描述 Flatscript 中折行的规则
折行均应用于表达式中.
表达式中, 在任何双目运算符 (四则运算符, 比较运算符等) 后可以折行, 如
long_statement: expression_0 * expression_1 + expression_2 * expression_3
等价于
long_statement: expression_0 * expression_1 +
expression_2 * expression_3
也等价于
long_statement: expression_0 * expression_1 + expression_2 *
expression_3
但不可写为
long_statement: expression_0 * expression_1
+ expression_2 * expression_3
成员访问 (点号 .) 与管道操作符在折行发生时均视作双目运算符.
函数调用的实参之前, 或列表字面常量的每个表项表达式之前都可以折行, 如
Math.max(expression_0, expression_1)
等价于
Math.max(expression_0,
expression_1)
也等价于
Math.max(
expression_0, expression_1)
或如
[expression_0] ++ [expression_1, expression_2, expression_2]
等价于
[expression_0] ++ [expression_1,
expression_2, expression_2]
凡是括号未配对时, 表达式中任何一处均可折行. (函数定义头除外, 但匿名函数的形参列表中可以折行)
折行后, 开头的空格将被忽略, 此行的内容将视作上一行的延续.
此情况并不视作折行. 此处换行后, 其后一行缩进满足较上一行语句增加, 则从折行处开始, 所有与折行处语句缩进相同的语句会计入该匿名函数的函数体. 如
x: (a, b):
document.getElementById('a').innerHTML: a
document.getElementById('b').innerHTML: b
y: x('shinto', 'shrine')
其中两个名字定义语句是同一空间的, 而两个属性设置语句是匿名函数的函数体.
函数体中的语句必须满足
- 各语句缩进相同
- 它们的缩进必须多于匿名函数形参列表所属的语句的缩进
在函数体开始之后, 当接下来的语句 (或片段) 的缩进少于匿名函数体中语句的缩进时, 匿名函数的函数体立即视作结束. 此时的语句将根据上一行结束与否, 作为独立语句编译, 或作为上一行的延续. 如上面的例子, 因为名字 x
的定义语法上可视作正常结束, 因此下面出现的 y: x('shinto', 'shrine')
作为独立的语句编译.
而下面的例子则相对地, 后续的内容作为前一个语句的延续
setTimeout(():
document.getElementById('a').innerHTML: a
document.getElementById('b').innerHTML: b
, 2000)
setTimeout(():
document.getElementById('a').innerHTML: a
document.getElementById('b').innerHTML: b
, 2000)
setTimeout(():
一行在匿名函数函数体结束后并不能结束, 因此下面出现的 , 2000)
将作为这个语句的后续部分编译.
或下面这个例子
operators: {
plus: (a, b):
return a + b
,
minus: (a, b):
return a - b
,
}
其中的逗号缩进少于匿名函数体, 表示匿名函数的结束, 而此逗号将被对象常量解释, 视作属性名-属性值对的分隔符.