跳转到内容

单页视图

欢迎

YueScript logo

欢迎来到 月之脚本(YueScript) 官方文档!
这里收录了语言特性、用法、参考示例和资源。
请选择导航中的章节索引或目录,开启你的月之脚本之旅 ☽

YueScript 介绍

月之脚本(YueScript)是一种动态语言,可以编译为 Lua。它是 MoonScript 的方言。用月之脚本编写的代码既有表现力又非常简洁。它适合编写一些更易于维护的代码,并在嵌入 Lua 的环境中运行,如游戏或网站服务器。

Yue(月)是中文中“月亮”的名称。

-- 导入语法
import p, to_lua from "yue"
-- 隐式对象
inventory =
equipment:
- "sword"
- "shield"
items:
- name: "potion"
count: 10
- name: "bread"
count: 3
-- 列表推导
map = (arr, action) ->
[action item for item in *arr]
filter = (arr, cond) ->
[item for item in *arr when cond item]
reduce = (arr, init, action): init ->
init = action init, item for item in *arr
-- 管道操作符
[1, 2, 3]
|> map (x) -> x * 2
|> filter (x) -> x > 4
|> reduce 0, (a, b) -> a + b
|> print
-- 元表操作
apple =
size: 15
<index>:
color: 0x00ffff
with apple
p .size, .color, .<index> if .<>?
-- 类似js的导出语法
export 🌛 = "月之脚本"

月之脚本是与开源游戏引擎 Dora SSR 一起开发和维护的。它已被用于创建引擎工具、游戏原型和演示,在实际的游戏项目中验证其能力,同时它也帮助增强了 Dora SSR 游戏引擎的开发体验。

安装

 安装 luarocks,一个 Lua 模块的包管理器。然后作为 Lua 模块和可执行文件安装它:

终端窗口
luarocks install yuescript

 或者你可以自己构建 yue.so 文件:

终端窗口
make shared LUAI=/usr/local/include/lua LUAL=/usr/local/lib/lua

 然后从路径 bin/shared/yue.so 获取二进制文件。

 克隆项目仓库,然后构建并安装可执行文件:

终端窗口
make install

 构建不带宏功能的月之脚本编译工具:

终端窗口
make install NO_MACRO=true

 构建不带内置Lua二进制文件的月之脚本编译工具:

终端窗口
make install NO_LUA=true

 你可以下载预编译的二进制程序,包括兼容不同 Lua 版本的二进制可执行文件和库文件。

 在这里下载预编译的二进制程序。

使用方法

  在 Lua 中使用月之脚本模块:

  • 用法 1

      在 Lua 中引入 “你的脚本入口文件.yue”。

    require("yue")("你的脚本入口文件")

      当你在同一路径下把 “你的脚本入口文件.yue” 编译成了 “你的脚本入口文件.lua” 时,仍然可以使用这个代码加载 .lua 代码文件。在其余的月之脚本文件中,只需正常使用 require 或 import 进行脚本引用即可。错误消息中的代码行号也会被正确处理。

  • 用法 2

      手动引入月之脚本模块并重写错误消息来帮助调试。

    local yue = require("yue")
    yue.insert_loaders()
    local success, result = xpcall(function()
    return require("yuescript_module_name")
    end, function(err)
    return yue.traceback(err)
    end)
  • 用法 3

      在 Lua 中使用月之脚本编译器功能。

    local yue = require("yue")
    local codes, err, globals = yue.to_lua([[
    f = ->
    print "hello world"
    f!
    ]],{
    implicit_return_root = true,
    reserve_line_number = true,
    lint_global = true,
    space_over_tab = false,
    options = {
    target = "5.4",
    path = "/script"
    }
    })

  使用月之脚本编译工具:

终端窗口
> yue -h
命令行用法: yue
[选项] [<文件/目录>] ...
yue -e <代码或文件> [参数...]
yue -w [<目录>] [选项]
yue -
说明:
- '-' 或 '--' 必须作为唯一且第一个参数,用于读取标准输入。
- '-o/--output' 不能与多个输入文件一起使用。
- '-w/--watch' 仅能用于目录,不能用于单个文件。
- 使用 '-e/--execute' 时,后续的参数将作为脚本参数传递。
选项:
-h, --help 显示帮助信息并退出
-e <字符串>, --execute <字符串> 执行文件或原始代码
-m, --minify 生成压缩(最小化)代码
-r, --rewrite 重写输出以匹配原始代码行号
-t <目标路径>, --output-to <目标路径>
指定编译后文件的输出路径
-o <文件>, --output <文件> 将输出写入文件
-p, --print 输出到标准输出
-b, --benchmark 输出编译耗时(不写入文件)
-g, --globals 显示用到的全局变量及其所在的名称、行号、列号
-s, --spaces 用空格代替制表符(tab)输出代码
-l, --line-numbers 输出源代码的行号
-j, --no-implicit-return 禁用文件末尾的隐式返回
-c, --reserve-comments 保留源代码中的注释
-w [<目录>], --watch [<目录>]
监视目录变化并自动编译
-v, --version 显示版本信息
- 从标准输入读取,输出到标准输出(仅能作为唯一参数)
-- 等同于 '-',为兼容旧版本保留
--target <版本> 指定生成代码的 Lua 版本 (只能为 5.1 ~ 5.5)
--path <路径字符串> 附加一个 Lua 搜索路径到 package.path
--<键>=<值> 以 key=value 形式传递编译器选项(保持已有用法)
不带选项直接运行可进入交互模式(REPL),在交互模式里输入单独的符号 '$'
可用于开始或结束多行模式。

  使用案例:

  递归编译当前路径下扩展名为 .yue 的每个月之脚本文件: yue .

  编译并将结果保存到目标路径: yue -t /target/path/ .

  编译并保留调试信息: yue -l .

  编译并生成压缩代码: yue -m .

  直接执行代码: yue -e ‘print 123’

  执行一个月之脚本文件: yue -e main.yue

空白

  月之脚本是一个对空白敏感的语言。你必须在相同的缩进中使用空格 ’ ‘ 或制表符 ’\t’ 来编写一些代码块,如函数体、值列表和一些控制块。包含不同空白的表达式可能意味着不同的事情。制表符被视为4个空格,但最好不要混合使用空格和制表符。

  一条语句通常以换行结束。你也可以使用分号 ; 显式结束一条语句,从而在同一行中编写多条语句:

a = 1; b = 2; print a + b

  你可以使用相同的缩进来编写多行链式函数调用。

Rx.Observable
.fromRange 1, 8
\filter (x) -> x % 2 == 0
\concat Rx.Observable.of 'who do we appreciate'
\map (value) -> value .. '!'
\subscribe print

注释

-- 我是一个注释
str = --[[
这是一个多行注释。
没问题。
]] strA \ -- 注释 1
.. strB \ -- 注释 2
.. strC
func --[[端口]] 3000, --[[ip]] "192.168.1.1"

字面量

  Lua 中的所有基本字面量都可以在月之脚本中使用。包括数字、字符串、布尔值和 nil。

  但与 Lua 不同的是,单引号和双引号字符串内部允许有换行:

some_string = "这是一个字符串
并包括一个换行。"
-- 使用#{}语法可以将表达式插入到字符串字面量中。
-- 字符串插值只在双引号字符串中可用。
print "我有#{math.random! * 100}%的把握。"

  你可以在数字字面量中使用下划线来增加可读性。

integer = 1_000_000
hex = 0xEF_BB_BF
binary = 0B10011

  使用 | 前缀标记一个多行 YAML 风格字符串:

str = |
key: value
list:
- item1
- #{expr}

  其效果类似于原生 Lua 的多行拼接,所有文本(含换行)将被保留下来,并支持 #{...} 语法,通过 tostring(expr) 插入表达式结果。

  YAML 风格的多行字符串会自动检测首行后最小的公共缩进,并从所有行中删除该前缀空白字符。这让你可以在代码中对齐文本,但输出字符串不会带多余缩进。

fn = ->
str = |
foo:
bar: baz
return str

  输出字符串中的 foo: 对齐到行首,不会带有函数缩进空格。保留内部缩进的相对结构,适合书写结构化嵌套样式的内容。

  支持自动处理字符中的引号、反斜杠等特殊符号,无需手动转义:

str = |
path: "C:\Program Files\App"
note: 'He said: "#{Hello}!"'

操作符

  Lua 的所有二元和一元操作符在月之脚本中都是可用的。此外,!= 符号是 ~= 的别名,而 \ 或 :: 均可用于编写链式函数调用,如写作 tb\func! 或 tb::func!。此外月之脚本还提供了一些其他特殊的操作符,以编写更具表达力的代码。

tb\func! if tb ~= nil
tb::func! if tb != nil

  你可以在月之脚本中进行比较表达式的链式书写:

print 1 < 2 <= 2 < 3 == 3 > 2 >= 1 == 1 < 3 != 5
-- 输出:true
a = 5
print 1 <= a <= 10
-- 输出:true

  可以注意一下链式比较表达式的求值行为:

v = (x) ->
print x
x
print v(1) < v(2) <= v(3)
--[[
输出:
2
1
3
true
]]
print v(1) > v(2) <= v(3)
--[[
输出:
2
1
false
]]

  在上面的例子里,中间的表达式 v(2) 仅被计算一次,如果把表达式写成 v(1) < v(2) and v(2) <= v(3) 的方式,中间的 v(2) 才会被计算两次。在链式比较中,求值的顺序往往是未定义的。所以强烈建议不要在链式比较中使用具有副作用(比如做打印操作)的表达式。如果需要使用有副作用的函数,应明确使用短路 and 运算符来做连接。

  [] = 操作符用于向 Lua 表的最后插入值。

tab = []
tab[] = "Value"

  你还可以使用展开操作符 ... 来将一个列表中的所有元素追加到另一个列表中:

tbA = [1, 2, 3]
tbB = [4, 5, 6]
tbA[] = ...tbB
-- tbA 现在为 [1, 2, 3, 4, 5, 6]

  你可以使用前置 ... 操作符在 Lua 表中插入数组表或哈希表。

  使用花括号字面量做表扩展(例如 {...other})时,会复制 Lua 表中的数组部分和哈希部分。

parts =
* "shoulders"
* "knees"
lyrics =
* "head"
* ...parts
* "and"
* "toes"
copy = {...other}
a = {1, 2, 3, x: 1}
b = {4, 5, y: 1}
merge = {...a, ...b}

  使用中括号字面量做表扩展(例如 [...other,])时,只会复制 Lua 表中的数组部分。

source = {1, 2, 3, name: "Yue"}
fullCopy = {...source}
listCopy = [...source,]
-- fullCopy => {1, 2, 3, name: "Yue"}
-- listCopy => [1, 2, 3]

  你可以使用 # 操作符来反向索引表中的元素。

last = data.items[#]
second_last = data.items[#-1]
data.items[#] = 1

  <> 操作符可提供元表操作的快捷方式。

  使用空括号 <> 或被 <> 包围的元方法键创建普通的 Lua 表。

mt = {}
add = (right) => <>: mt, value: @value + right.value
mt.__add = add
a = <>: mt, value: 1
-- 使用与临时变量名相同的字段名,将临时变量赋值给元表
b = :<add>, value: 2
c = <add>: mt.__add, value: 3
d = a + b + c
print d.value
close _ = <close>: -> print "超出范围"

  使用 <> 或被 <> 包围的元方法名或在 <> 中编写某些表达式来访问元表。

-- 使用包含字段 "value" 的元表创建
tb = <"value">: 123
tb.<index> = tb.<>
print tb.value
tb.<> = __index: {item: "hello"}
print tb.item

  使用被 <> 包围的元方法键解构元表。

{item, :new, :<close>, <index>: getter} = tb
print item, new, close, getter

  ? 运算符可以在多种上下文中用来检查存在性。

func?!
print abc?["你好 世界"]?.xyz
x = tab?.value
len = utf8?.len or string?.len or (o) -> #o
if print and x?
print x
with? io.open "test.txt", "w"
\write "你好"
\close!

  与其使用一系列嵌套的函数调用,你还可以考虑使用运算符 |> 来传递值。

"你好" |> print
1 |> print 2 -- 将管道项作为第一个参数插入
2 |> print 1, _, 3 -- 带有占位符的管道
-- 多行的管道表达式
readFile "example.txt"
|> extract language, {}
|> parse language
|> emit
|> render
|> print

  如果其左操作数不是 nil,则nil合并运算符 ?? 返回其左操作数的值;否则,它将计算右操作数并返回其结果。如果左操作数计算结果为非 nil 的值,?? 运算符将不再计算其右操作数。

local a, b, c, d
a = b ?? c ?? d
func a ?? {}
a ??= false

  你可以在表格块内使用符号 * 或是 - 开始编写一系列隐式结构。如果你正在创建隐式对象,对象的字段必须具有相同的缩进。

-- 赋值时使用隐式对象
list =
* 1
* 2
* 3
-- 函数调用时使用隐式对象
func
* 1
* 2
* 3
-- 返回时使用隐式对象
f = ->
return
* 1
* 2
* 3
-- 表格时使用隐式对象
tb =
name: "abc"
values:
- "a"
- "b"
- "c"
objects:
- name: "a"
value: 1
func: => @value + 1
tb:
fieldA: 1
- name: "b"
value: 2
func: => @value + 2
tb: { }

属性

  月之脚本现在提供了 Lua 5.4 新增的叫做属性的语法支持。在月之脚本编译到的 Lua 目标版本低于 5.4 时,你仍然可以同时使用const 和 close 的属性声明语法,并获得常量检查和作用域回调的功能。

const a = 123
close _ = <close>: -> print "超出范围。"

  你可以对进行解构得到的变量标记为常量。

const {:a, :b, c, d} = tb
-- a = 1

  你也可以声明全局变量为常量。

global const Constant = 123
-- Constant = 1

模块

  导入语句是一个语法糖,用于需要引入一个模块或者从已导入的模块中提取子项目。从模块导入的变量默认为不可修改的常量。

-- 用作表解构
do
import insert, concat from table
-- 当给 insert, concat 变量赋值时,编译器会报告错误
import C, Ct, Cmt from require "lpeg"
-- 快捷写法引入模块的子项
import x, y, z from 'mymodule'
-- 使用Python风格的导入
from 'module' import a, b, c
-- 快捷地导入一个模块
do
import 'module'
import 'module_x'
import "d-a-s-h-e-s"
import "module.part"
-- 导入模块后起一个别名使用,或是进行导入模块表的解构
do
import "player" as PlayerModule
import "lpeg" as :C, :Ct, :Cmt
import "export" as {one, two, Something:{umm:{ch}}}

  你可以使用 import 将指定的全局变量导入到本地变量中。当导入一系列对全局变量的链式访问时,最后一个访问的字段将被赋值给本地变量。

do
import tostring
import table.concat
print concat ["a", tostring 1]

  在一个代码块的顶部写 import global,会将当前作用域中尚未显式声明或赋值过的变量名,自动导入为本地常量,并在该语句的位置绑定到同名的全局变量。

  但是在同一作用域中被显式声明为全局的变量不会被自动导入,因此可以继续进行赋值操作。

do
import global
print "hello"
math.random 3
-- print = nil -- 报错:自动导入的全局变量为常量
do
-- 被显式声明为全局的变量不会被自动导入
import global
global FLAG
print FLAG
FLAG = 123

  导出语句提供了一种简洁的方式来定义当前的模块。

  带命名的导出将定义一个局部变量,并在导出的表中添加一个同名的字段。

export a, b, c = 1, 2, 3
export cool = "cat"
export What = if this
"abc"
else
"def"
export y = ->
hallo = 3434
export class Something
umm: "cool"

  使用解构进行命名导出。

export :loadstring, to_lua: tolua = yue
export {itemA: {:fieldA = '默认值'}} = tb

  从模块导出命名项目时,可以不用创建局部变量。

export.itemA = tb
export.<index> = items
export["a-b-c"] = 123

  未命名导出会将要导出的目标项目添加到导出表的数组部分。

d, e, f = 3, 2, 1
export d, e, f
export if this
123
else
456
export with tmp
j = 2000

  在导出语句中使用 default 关键字,来替换导出的表为一个目标的对象。

export default ->
print "你好"
123

赋值

  月之脚本中定义的变量是动态类型的,并默认为局部变量。但你可以通过 local 和 global 声明来改变声明变量的作用范围。

hello = "world"
a, b, c = 1, 2, 3
hello = 123 -- 访问现有的变量

  你可以使用各式二进制运算符执行更新赋值。

x = 1
x += 1
x -= 1
x *= 10
x /= 10
x %= 10
s ..= "world" -- 如果执行更新的局部变量不存在,将新建一个局部变量
arg or= "默认值"

  你可以进行链式赋值,将多个项目赋予相同的值。

a = b = c = d = e = 0
x = y = z = f!
do
local a = 1
local *
print "预先声明后续所有变量为局部变量"
x = -> 1 + y + z
y, z = 2, 3
global instance = Item\new!
do
local X = 1
local ^
print "只预先声明后续大写的变量为局部变量"
a = 1
B = 2
do
global a = 1
global *
print "预先声明所有变量为全局变量"
x = -> 1 + y + z
y, z = 2, 3
do
global x = 1
global ^
print "只预先声明大写的变量为全局变量"
a = 1
B = 2
local Temp = "一个局部值"

解构赋值

  解构赋值是一种快速从 Lua 表中按名称或基于数组中的位置提取值的方法。

  通常当你看到一个字面量的 Lua 表,比如 {1,2,3},它位于赋值的右侧,因为它是一个值。解构赋值语句的写法就是交换了字面量 Lua 表的角色,并将其放在赋值语句的左侧。

  最好是通过示例来解释。以下是如何从表格中解包前两个值的方法:

thing = [1, 2]
[a, b] = thing
print a, b

  在解构表格字面量中,键代表从右侧读取的键,值代表读取的值将被赋予的名称。

obj = {
hello: "world"
day: "tuesday"
length: 20
}
{hello: hello, day: the_day} = obj
print hello, the_day
:day = obj -- 可以不带大括号进行简单的解构

  这也适用于嵌套的数据结构:

obj2 = {
numbers: [1,2,3,4]
properties: {
color: "green"
height: 13.5
}
}
{numbers: [first, second], properties: {color: color}} = obj2
print first, second, color

  如果解构语句很复杂,也可以任意将其分散在几行中。稍微复杂一些的示例:

{
numbers: [first, second]
properties: {
color: color
}
} = obj2

  有时候我们会需要从 Lua 表中提取值并将它们赋给与键同名的局部变量。为了避免编写重复代码,我们可以使用 : 前缀操作符:

{:concat, :insert} = table

  这样的用法与导入语法有些相似。但我们可以通过混合语法重命名我们想要提取的字段:

{:mix, :max, random: rand} = math

  在进行解构时,你可以指定默认值,如:

{:name = "nameless", :job = "jobless"} = person

  在进行列表解构时,你可以使用_作为占位符:

[_, two, _, four] = items

  你可以使用展开运算符 ... 在列表解构中来捕获一个范围的值到子列表中。这在当你想要从列表的开头和结尾提取特定元素,同时收集中间的元素时非常有用。

orders = ["first", "second", "third", "fourth", "last"]
[first, ...bulk, last] = orders
print first -- 打印: first
print bulk -- 打印: {"second", "third", "fourth"}
print last -- 打印: last

  展开运算符可以用在不同的位置来捕获不同的范围,并且你可以使用 _ 作为占位符来表示你想跳过对应范围的捕获:

-- 捕获第一个元素之后的所有元素
[first, ...rest] = orders
-- 捕获最后一个元素之前的所有元素
[...start, last] = orders
-- 跳过中间的元素,只捕获第一个和最后一个元素
[first, ..._, last] = orders

  解构赋值也可以出现在其它隐式进行赋值的地方。一个例子是用在 for 循环中:

tuples = [
["hello", "world"]
["egg", "head"]
]
for [left, right] in *tuples
print left, right

  我们知道数组表中的每个元素都是一个两项的元组,所以我们可以直接在 for 语句的名称子句中使用解构来解包它。

If 赋值

  if 和 elseif 代码块可以在条件表达式的位置进行赋值。在代码执行到要计算条件时,会首先进行赋值计算,并使用赋与的值作为分支判断的条件。赋值的变量仅在条件分支的代码块内有效,这意味着如果值不是真值,那么它就不会被用到。注意,你必须使用“海象运算符” := 而不是 = 来做赋值。

if user := database.find_user "moon"
print user.name
if hello := os.getenv "hello"
print "你有 hello", hello
elseif world := os.getenv "world"
print "你有 world", world
else
print "什么都没有 :("

  使用多个返回值的 If 赋值。只有第一个值会被检查,其他值都有同样的作用域。

if success, result := pcall -> "无报错地获取结果"
print result -- 变量 result 是有作用域的
print "好的"

  你可以在 while 循环中同样使用赋值来获取循环条件的值。

while byte := stream\read_one!
-- 对 byte 做一些操作
print byte

可变参数赋值

  你可以将函数返回的结果赋值给一个可变参数符号 ...。然后使用 Lua 的方式访问其内容。

list = [1, 2, 3, 4, 5]
fn = (ok) -> ok, table.unpack list
ok, ... = fn true
count = select '#', ...
first = select 1, ...
print ok, count, first

使用 using 语句:防止破坏性赋值

  Lua 的变量作用域是降低代码复杂度的重要工具。然而,随着代码量的增加,维护这些变量可能变得更加困难。比如,看看下面的代码片段:

i = 100
-- 许多代码行...
my_func = ->
i = 10
while i > 0
print i
i -= 1
my_func!
print i -- 将打印 0

  在 my_func 中,我们不小心覆盖了变量 i 的值。虽然在这个例子中这个问题很明显,但在一个庞大的或者是由多人共同维护的代码库中,很难追踪每个变量的声明情况。

  如果我们可以明确指出哪些变量是我们想在当前作用域内修改的,并且防止我们不小心更改了其他作用域中同名的变量,那将大有裨益。

  using 语句就是为此而生。using nil 确保函数内部的赋值不会意外地影响到外部作用域的变量。我们只需将 using 子句放在函数的参数列表之后;若函数没有参数,则直接放在括号内即可。

i = 100
my_func = (using nil) ->
i = "hello" -- 这里创建了一个新的局部变量
my_func!
print i -- 打印 100,i 没有受到影响

  using子句中可以填写多个用逗号分隔名称。指定可以访问和修改的外部变量的名称:

tmp = 1213
i, k = 100, 50
my_func = (add using k, i) ->
tmp = tmp + add -- 创建了一个新的局部tmp
i += tmp
k += tmp
my_func(22)
print i, k -- 这些已经被更新

函数字面量

  所有函数都是使用月之脚本的函数表达式创建的。一个简单的函数可以用箭头表示为:->。

my_function = ->
my_function() -- 调用空函数

  函数体可以是紧跟在箭头后的一个语句,或者是在后面的行上使用同样缩进的一系列语句:

func_a = -> print "你好,世界"
func_b = ->
value = 100
print "这个值是:", value

  如果一个函数没有参数,可以使用 ! 操作符调用它,而不是空括号。使用 ! 调用没有参数的函数是推荐的写法。

func_a!
func_b()

  带有参数的函数可以通过在箭头前加上括号中的参数名列表来进行创建:

sum = (x, y) -> print "数字的和", x + y

  函数可以通过在函数名后列出参数来调用。当对函数做嵌套的调用时,后面列出的参数会应用于左侧最近的函数。

sum 10, 20
print sum 10, 20
a b c "a", "b", "c"

  为了避免在调用函数时产生歧义,也可以使用括号将参数括起来。比如在以下的例子中是必需的,这样才能确保参数被传入到正确的函数。

print "x:", sum(10, 20), "y:", sum(30, 40)

  注意:函数名与开始括号之间不能有任何空格。

  函数会将函数体中的最后一个语句强制转换为返回语句,这被称作隐式返回:

sum = (x, y) -> x + y
print "数字的和是", sum 10, 20

  如果你需要做显式返回,可以使用 return 关键字:

sum = (x, y) -> return x + y

  就像在Lua中一样,函数可以返回多个值。最后一个语句必须是由逗号分隔的值列表:

mystery = (x, y) -> x + y, x - y
a, b = mystery 10, 20

  因为在 Lua 中调用方法时,经常习惯将对象作为第一个参数传入,所以月之脚本提供了一种特殊的语法来创建自动包含 self 参数的函数。

func = (num) => @value + num

  可以为函数的参数提供默认值。如果参数的值为 nil,则确定该参数为空。任何具有默认值的 nil 参数在函数体运行之前都会被替换。

my_function = (name = "某物", height = 100) ->
print "你好,我是", name
print "我的高度是", height

  函数参数的默认值表达式在函数体中会按参数声明的顺序进行计算。因此,在默认值的表达式中可以访问先前声明的参数。

some_args = (x = 100, y = x + 1000) ->
print x + y

  由于月之脚本支持无需括号的表达式式函数调用,因此为了避免因空白字符造成的解析歧义,需要进行一些限制。

  减号(-)在表达式中既可以作为一元取反操作符,也可以作为二元减法操作符。请注意下面这些示例的编译方式:

a = x - 10
b = x-10
c = x -y
d = x- z

  当函数调用的第一个参数是字符串字面量时,可以通过空白控制其优先级。在 Lua 中,常见的写法是调用仅有一个字符串或表字面量参数的函数时省略括号。

  当变量名和字符串字面量之间没有空格时,函数的调用优先级高于后续表达式,因此此时无法再传入其他参数。

  当变量名和字符串字面量之间有空格时,字符串字面量会作为后续表达式(如果存在)的参数,这样可以传递参数列表。

x = func"hello" + 100
y = func "hello" + 100

  当调用接收大量参数的函数时,将参数列表分成多行是很方便的。由于月之脚本语言对空白字符的敏感性,做参数列表的分割时务必要小心。

  如果要将参数列表写到下一行,那么当前行必须以逗号结束。并且下一行的缩进必须比当前的缩进多。一旦做了参数的缩进,所有其他参数列表的行必须保持相同的缩进级别,以成为参数列表的一部分。

my_func 5, 4, 3,
8, 9, 10
cool_func 1, 2,
3, 4,
5, 6,
7, 8

  这种调用方式可以做嵌套。并通过缩进级别来确定参数属于哪一个函数。

my_func 5, 6, 7,
6, another_func 6, 7, 8,
9, 1, 2,
5, 4

  因为 Lua 表也使用逗号作为分隔符,这种缩进语法有助于让值成为参数列表的一部分,而不是 Lua 表的一部分。

x = [
1, 2, 3, 4, a_func 4, 5,
5, 6,
8, 9, 10
]

  有个不常见的写法可以注意一下,如果我们将在后面使用较低的缩进,我们可以为函数参数提供更深的缩进来区分列表的归属。

y = [ my_func 1, 2, 3,
4, 5,
5, 6, 7
]

  对于其它有代码块跟随的语句,比如条件语句,也可以通过小心安排缩进来做类似的事。比如我们可以通过调整缩进级别来控制一些值归属于哪个语句:

if func 1, 2, 3,
"你好",
"世界"
print "你好"
print "我在if内部"
if func 1, 2, 3,
"你好",
"世界"
print "hello"
print "我在if内部"

  月之脚本支持在函数形参位置对传入对象进行解构。适用两类解构表子面量:

  • 使用 {} 包裹的字面量/对象形参,支持提供获得空字段时的默认值(例如 {:a, :b}、{a: a1 = 123})。

  • 无 {} 包裹、以键值/简写键序列开头,直至遇到其它表达式终止(例如 :a, b: b1, :c),表示从同一个对象中解构多个字段。

f1 = (:a, :b, :c) ->
print a, b, c
f1 a: 1, b: "2", c: {}
f2 = ({a: a1 = 123, :b = 'abc'}, c = {}) ->
print a1, b, c
arg1 = {a: 0}
f2 arg1, arg2

  在深度嵌套的函数体中,为了提升返回值的可读性及编写便利性,我们新增了 “前置返回表达式” 语法。其形式如下:

findFirstEven = (list): nil ->
for item in *list
if type(item) == "table"
for sub in *item
if sub % 2 == 0
return sub

  这个写法等价于:

findFirstEven = (list) ->
for item in *list
if type(item) == "table"
for sub in *item
if sub % 2 == 0
return sub
nil

  唯一的区别在于:你可以将函数的返回值表达式提前写在 -> 或 => 前,用以指示该函数应隐式返回该表达式的值。这样即使在多层循环或条件判断的场景下,也无需编写尾行悬挂的返回表达式,逻辑结构会更加直观清晰。

  你可以使用 (...t) -> 语法来将变长参数自动存储到一个命名表中。这个表会包含所有传入的参数(包括 nil 值),并且会在表的 n 字段中存储实际传入的参数个数(包括 nil 值在内的个数)。

f = (...t) ->
print "参数个数:", t.n
print "表长度:", #t
for i = 1, t.n
print t[i]
f 1, 2, 3
f "a", "b", "c", "d"
f!
-- 处理包含 nil 的情况
process = (...args) ->
sum = 0
for i = 1, args.n
if args[i] != nil and type(args[i]) == "number"
sum += args[i]
sum
process 1, nil, 3, nil, 5

反向回调

  反向回调用于减少函数回调的嵌套。它们使用指向左侧的箭头,并且默认会被定义为传入后续函数调用的最后一个参数。它的语法大部分与常规箭头函数相同,只是它指向另一方向,并且后续的函数体不需要进行缩进。

x <- f
print "hello" .. x

  月之脚本也提供了粗箭头反向回调函数。

<= f
print @value

  你可以通过一个占位符指定回调函数的传参位置。

(x) <- map _, [1, 2, 3]
x * 2

  如果你希望在反向回调处理后继续编写更多其它的代码,可以使用 do 语句将不属于反向回调的代码分隔开。对于非粗箭头函数的反向回调,回调返回值的括号也是可以省略的。

result, msg = do
data <- readAsync "文件名.txt"
print data
info <- processAsync data
check info
print result, msg

函数存根

  在编程中,将对象的方法作为函数类型的值进行传递是一种常见做法,尤其是在将实例方法作为回调函数传递给其他函数的情形中。当目标函数需要将该对象作为其第一个参数时,我们需要找到一种方式将对象和函数绑定在一起,以便能够正确地调用该函数。

  函数存根(stub)语法提供了一种便捷的方法来创建一个新的闭包函数,这个函数将对象和原函数绑定在一起。这样,当调用这个新创建的函数时,它会在正确的对象上下文中执行原有的函数。

  这种语法类似于使用 \ 操作符调用实例方法的方式,区别在于,这里不需要在 \ 操作符后面附加参数列表。

my_object = {
value: 1000
write: => print "值为:", @value
}
run_callback = (func) ->
print "运行回调..."
func!
-- 这样写不起作用:
-- 函数没有引用my_object
run_callback my_object.write
-- 函数存根语法
-- 让我们把对象捆绑到一个新函数中
run_callback my_object\write

条件语句

have_coins = false
if have_coins
print "有硬币"
else
print "没有硬币"

  对于简单的语句,也可以使用简短的语法:

have_coins = false
if have_coins then print "有硬币" else print "没有硬币"

  因为 if 语句可以用作表达式,所以也可以这样写:

have_coins = false
print if have_coins then "有硬币" else "没有硬币"

  条件语句也可以作为表达式用在返回语句和赋值语句中:

is_tall = (name) ->
if name == "Rob"
true
else
false
message = if is_tall "Rob"
"我很高"
else
"我不是很高"
print message -- 打印: 我很高

  if 的反义词是 unless(相当于 if not,正如“如果”对应“除非”):

unless os.date("%A") == "Monday"
print "今天不是星期一!"
print "你真幸运!" unless math.random! > 0.1

  你可以使用范围表达式来编写进行范围检查的代码。

a = 5
if a in [1, 3, 5, 7]
print "检查离散值的相等性"
if a in list
print "检查`a`是否在列表中"

in 运算符也可以用于表,并支持 not in 变体来进行否定检查:

has = "foo" in {"bar", "foo"}
if a in {1, 2, 3}
print "a 在表中"
not_exist = item not in list
check = -> value not in table

单元素列表或表会检查与该元素的相等性:

-- [1,] 检查 value == 1
c = a in [1,]
-- {1} 也是检查 value == 1
c = a in {1}
-- 没有逗号,[1] 是索引访问(tb[1])
with tb
c = a in [1]

for 循环

  Lua 中有两种 for 循环形式,数字型和通用型:

for i = 10, 20
print i
for k = 1, 15, 2 -- 提供了一个遍历的步长
print k
for key, value in pairs object
print key, value

  可以使用切片和 * 操作符,就像在列表推导中一样:

for item in *items[2, 4]
print item

  当代码语句只有一行时,循环语句也都可以写作更短的语法:

for item in *items do print item
for j = 1, 10, 3 do print j

  for 循环也可以用作表达式。for 循环主体中的最后一条语句会被强制转换为一个返回值的表达式,并会将表达式计算结果的值追加到一个作为结果的数组表中。

  将每个偶数加倍:

doubled_evens = for i = 1, 20
if i % 2 == 0
i * 2
else
i

  此外,for 循环还支持带返回值的 break 语句,这样循环本身就可以作为一个表达式,在满足条件时提前退出并返回有意义的结果。for 循环表达式支持 break 返回多个值。

  例如,查找第一个大于 10 的数字:

first_large = for n in *numbers
break n if n > 10
key, score = for k, v in pairs data
break k, v * 10 if k == "target"

  你还可以结合 for 循环表达式与 continue 语句来过滤值。

  注意出现在函数体末尾的 for 循环,不会被当作是一个表达式并将循环结果累积到一个列表中作为返回值(相反,函数将返回 nil)。如果要函数末尾的循环转换为列表表达式,可以显式地使用返回语句加 for 循环表达式。

func_a = -> for i = 1, 10 do print i
func_b = -> return for i = 1, 10 do i
print func_a! -- 打印 nil
print func_b! -- 打印 table 对象

  这样做是为了避免在不需要返回循环结果的函数,创建无效的返回值表格。

while 循环

  在月之脚本中的 while 循环支持几种不同的写法:

i = 10
while i > 0
print i
i -= 1
while running == true do my_function!
i = 10
until i == 0
print i
i -= 1
until running == false do my_function!

  像 for 循环的语法一样,while 循环也可以作为一个表达式使用。while / until 循环表达式支持 break 返回多个值。

value, doubled = while true
n = get_next!
break n, n * 2 if n > 10

  为了使函数返回 while 循环的累积列表值,必须明确使用返回语句返回 while 循环表达式。

  repeat 循环是从 Lua 语言中搬过来的相似语法:

i = 10
repeat
print i
i -= 1
until i == 0

  repeat 循环表达式同样支持 break 返回多个值:

i = 1
value, scaled = repeat
break i, i * 100 if i > 3
i += 1
until false

continue 语句

  继续语句可以用来跳出当前的循环迭代。

i = 0
while i < 10
i += 1
continue if i % 2 == 0
print i

  继续语句也可以与各种循环表达式一起使用,以防止当前的循环迭代结果累积到结果列表中。以下示例将数组表过滤为仅包含偶数的数组:

my_numbers = [1, 2, 3, 4, 5, 6]
odds = for x in *my_numbers
continue if x % 2 == 1
x

Goto

YueScript 支持 goto 语句和标签语法来控制程序流程,该语法遵循与 Lua 的 goto 语句相同的规则。注意: goto 语句需要 Lua 5.2 或更高版本。当编译目标是 Lua 5.1 时,使用 goto 语法将导致编译错误。

标签使用双冒号定义:

::开始::
::结束::
::我的标签::

goto 语句可以跳转到指定的标签:

a = 0
::开始::
a += 1
goto 结束 if a == 5
goto 开始
::结束::
print "a 现在是 5"

goto 语句对于跳出深层嵌套循环非常有用:

for z = 1, 10
for y = 1, 10 do for x = 1, 10
if x^2 + y^2 == z^2
print '找到勾股数:', x, y, z
goto 完成
::完成::

你也可以使用标签跳转到特定的循环层级:

for z = 1, 10
for y = 1, 10
for x = 1, 10
if x^2 + y^2 == z^2
print '找到勾股数:', x, y, z
print '尝试下一个 z...'
goto 继续z
::继续z::
  • 标签在其作用域内必须唯一
  • goto 可以跳转到相同或外层作用域级别的标签
  • goto 不能跳转到内层作用域(如代码块或循环内部)
  • 谨慎使用 goto,因为它会使代码更难阅读和维护

switch 语句

  switch 语句是为了简化检查一系列相同值的if语句而提供的简写语法。要注意用于比较检查的目标值只会计算一次。和 if 语句一样,switch 语句在最后可以接一个 else 代码块来处理没有匹配的情况。在生成的 Lua 代码中,进行比较是使用 == 操作符完成的。switch 语句中也可以使用赋值表达式来储存临时变量值。

switch name := "Dan"
when "Robert"
print "你是Robert"
when "Dan", "Daniel"
print "你的名字是Dan"
else
print "我不认识你,你的名字是#{name}"

  switch 语句的 when 子句中可以通过使用逗号分隔的列表来匹配多个值。

  switch 语句也可以作为表达式使用,下面我们可以将 switch 语句返回的结果分配给一个变量:

b = 1
next_number = switch b
when 1
2
when 2
3
else
error "数字数得太大了!"

  我们可以使用 then 关键字在 when 子句的同一行上编写处理代码。else 代码块的后续代码中要写在同一行上不需要额外的关键字。

msg = switch math.random(1, 5)
when 1 then "你很幸运"
when 2 then "你差点很幸运"
else "不太幸运"

  如果在编写 switch 语句时希望少写一个缩进,那么你可以把第一个 when 子句放在 switch 开始语句的第一行,然后后续的子语句就都可以都少写一个缩进。

switch math.random(1, 5)
when 1
print "你很幸运" -- 两个缩进级别
else
print "不太幸运"
switch math.random(1, 5) when 1
print "你很幸运" -- 一个缩进级别
else
print "不太幸运"

  值得注意的是,在生成 Lua 代码时,我们要做检查的目标变量会放在 == 表达式的右侧。当你希望给 when 子句的比较对象定义一个 __eq 元方法来重载判断逻辑时,可能会有用。

  在 switch 的 when 子句中,如果期待检查目标是一个表格,且可以通过特定的结构进行解构并获得非 nil 值,那么你可以尝试使用表格匹配的语法。

items =
* x: 100
y: 200
* width: 300
height: 400
for item in *items
switch item
when :x, :y
print "Vec2 #{x}, #{y}"
when :width, :height
print "尺寸 #{width}, #{height}"

  你可以使用默认值来选择性地解构表格的某些字段。

item = {}
{pos: {:x = 50, :y = 200}} = item -- 获取错误:尝试索引nil值(字段'pos')
switch item
when {pos: {:x = 50, :y = 200}}
print "Vec2 #{x}, #{y}" -- 表格解构仍然会通过

  你也可以匹配数组元素、表格字段,甚至使用数组或表格字面量来匹配嵌套的结构。

  匹配数组元素。

switch tb
when [1, 2, 3]
print "1, 2, 3"
when [1, b, 3]
print "1, #{b}, 3"
when [1, 2, b = 3] -- 变量b有默认值
print "1, 2, #{b}"

  匹配表格字段。

switch tb
when success: true, :result
print "成功", result
when success: false
print "失败", result
else
print "无效值"

  匹配嵌套的表格结构。

switch tb
when data: {type: "success", :content}
print "成功", content
when data: {type: "error", :content}
print "失败", content
else
print "无效值"

  匹配表格数组。

switch tb
when [
{a: 1, b: 2}
{a: 3, b: 4}
{a: 5, b: 6}
fourth
]
print "匹配成功", fourth

  匹配一个列表并捕获特定范围内的元素。

segments = ["admin", "users", "logs", "view"]
switch segments
when [...groups, resource, action]
print "Group:", groups -- 打印: {"admin", "users"}
print "Resource:", resource -- 打印: "logs"
print "Action:", action -- 打印: "view"

表格字面量

  和 Lua 一样,表格可以通过花括号进行定义。

some_values = {1, 2, 3, 4}

  但与Lua不同的是,给表格中的键赋值是用 :(而不是 =)。

some_values = {
name: "Bill",
age: 200,
["favorite food"]: "rice"
}

  如果只分配一个键值对的表格,可以省略花括号。

profile =
height: "4英尺",
shoe_size: 13,
favorite_foods: ["冰淇淋", "甜甜圈"]

  可以使用换行符而不使用逗号(或两者都用)来分隔表格中的值:

values = {
1, 2, 3, 4
5, 6, 7, 8
name: "超人"
occupation: "打击犯罪"
}

  创建单行表格字面量时,也可以省略花括号:

my_function dance: "探戈", partner: "无"
y = type: "狗", legs: 4, tails: 1

  表格字面量的键可以使用 Lua 语言的关键字,而无需转义:

tbl = {
do: "某事"
end: "饥饿"
}

  如果你要构造一个由变量组成的表,并希望键与变量名相同,那么可以使用 : 前缀操作符:

hair = "金色"
height = 200
person = { :hair, :height, shoe_size: 40 }
print_table :hair, :height

  如果你希望表中字段的键是某个表达式的结果,那么可以用 [ ] 包裹它,就像在 Lua 中一样。如果键中有任何特殊字符,也可以直接使用字符串字面量作为键,省略方括号。

t = {
[1 + 2]: "你好"
"你好 世界": true
}

  Lua 的表同时具有数组部分和哈希部分,但有时候你会希望在书写 Lua 表时,对 Lua 表做数组和哈希不同用法的语义区分。然后你可以用 [ ] 而不是 { } 来编写表示数组的 Lua 表,并且不允许在数组 Lua 表中写入任何键值对。

some_values = [ 1, 2, 3, 4 ]
list_with_one_element = [ 1, ]

推导式

  推导式为我们提供了一种便捷的语法,通过遍历现有对象并对其值应用表达式来构造出新的表格。月之脚本有两种推导式:列表推导式和表格推导式。它们最终都是产生 Lua 表格;列表推导式将值累积到类似数组的表格中,而表格推导式允许你在每次遍历时设置新表格的键和值。

  以下操作创建了一个 items 表的副本,但所有包含的值都翻倍了。

items = [1, 2, 3, 4]
doubled = [item * 2 for i, item in ipairs items]

  可以使用 when 子句筛选新表中包含的项目:

slice = [item for i, item in ipairs items when i > 1 and i < 3]

  因为我们常常需要迭代数值索引表的值,所以引入了 * 操作符来做语法简化。doubled 示例可以重写为:

doubled = [item * 2 for item in *items]

  在列表推导式中,你还可以使用展开操作符 ... 来实现对列表嵌套层级进行扁平化的处理:

data =
a: [1, 2, 3]
b: [4, 5, 6]
flat = [...v for k,v in pairs data]
-- flat 现在为 [1, 2, 3, 4, 5, 6]

  for 和 when 子句可以根据需要进行链式操作。唯一的要求是推导式中至少要有一个 for 子句。

  使用多个 for 子句与使用多重循环的效果相同:

x_coords = [4, 5, 6, 7]
y_coords = [9, 2, 3]
points = [ [x, y] for x in *x_coords \
for y in *y_coords]

  在推导式中也可以使用简单的数值 for 循环:

evens = [i for i = 1, 100 when i % 2 == 0]

  表格推导式和列表推导式的语法非常相似,只是要使用 { 和 } 并从每次迭代中取两个值。

  以下示例生成了表格 thing 的副本:

thing = {
color: "red"
name: "fast"
width: 123
}
thing_copy = {k, v for k, v in pairs thing}
no_color = {k, v for k, v in pairs thing when k != "color"}

  * 操作符在表格推导式中能使用。在下面的例子里,我们为几个数字创建了一个平方根查找表。

numbers = [1, 2, 3, 4]
sqrts = {i, math.sqrt i for i in *numbers}

  表格推导式中的键值元组也可以来自单个表达式,在这种情况下,表达式在计算后应返回两个值。第一个用作键,第二个用作值:

  在下面的示例中,我们将一些数组转换为一个表,其中每个数组里的第一项是键,第二项是值。

tuples = [ ["hello", "world"], ["foo", "bar"]]
tbl = {unpack tuple for tuple in *tuples}

  当使用 * 操作符时,月之脚本还提供了一种特殊的语法来限制要遍历的列表范围。这个语法也相当于在 for 循环中设置迭代边界和步长。

  下面的案例中,我们在切片中设置最小和最大边界,取索引在 1 到 5 之间(包括 1 和 5)的所有项目:

slice = [item for item in *items[1, 5]]

  切片的任意参数都可以省略,并会使用默认值。在如下示例中,如果省略了最大索引边界,它默认为表的长度。使下面的代码取除第一个元素之外的所有元素:

slice = [item for item in *items[2,]]

  如果省略了最小边界,便默认会设置为 1。这里我们只提供一个步长,并留下其他边界为空。这样会使得代码取出所有奇数索引的项目:(1, 3, 5, …)

slice = [item for item in *items[,,2]]

  最小和最大边界都可以是负数,使用负数意味着边界是从表的末尾开始计算的。

-- 取最后4个元素
slice = [item for item in *items[-4,-1]]

  切片的步长也可以是负数,这意味着元素会以相反的顺序被取出。

reverse_slice = [item for item in *items[-1,1,-1]]

  切片也可以作为表达式来使用。可以用于获取一个表包含的子列表。

-- 取第2和第4个元素作为新的列表
sub_list = items[2, 4]
-- 取最后4个元素作为新的列表
last_four_items = items[-4, -1]

面向对象编程

  在以下的示例中,月之脚本生成的 Lua 代码可能看起来会很复杂。所以最好主要关注月之脚本代码层面的意义,然后如果你想知道关于面向对象功能的实现细节,再查看 Lua 代码。

  一个简单的类:

class Inventory
new: =>
@items = {}
add_item: (name) =>
if @items[name]
@items[name] += 1
else
@items[name] = 1

  在月之脚本中采用面向对象的编程方式时,通常会使用类声明语句结合 Lua 表格字面量来做类定义。这个类的定义包含了它的所有方法和属性。在这种结构中,键名为 “new” 的成员扮演了一个重要的角色,是作为构造函数来使用。

  值得注意的是,类中的方法都采用了粗箭头函数语法。当在类的实例上调用方法时,该实例会自动作为第一个参数被传入,因此粗箭头函数用于生成一个名为 “self” 的参数。

  此外,“@” 前缀在变量名上起到了简化作用,代表 “self”。例如,@items 就等同于 self.items。

  类定义块中也支持通过尖括号写元方法字段,例如 <tostring>。

class User
new: (@name) =>
<tostring>: => "User(#{@name})"
print tostring User "Yue"

  为了创建类的一个新实例,可以将类名当作一个函数来调用,这样就可以生成并返回一个新的实例。

inv = Inventory!
inv\add_item "t-shirt"
inv\add_item "pants"

  在月之脚本的类中,由于需要将类的实例作为参数传入到调用的方法中,因此使用了 \ 操作符做类的成员函数调用。

  需要特别注意的是,类的所有属性在其实例之间是共享的。这对于函数类型的成员属性通常不会造成问题,但对于其他类型的属性,可能会导致意外的结果。

  例如,在下面的示例中,clothes 属性在所有实例之间共享。因此,对这个属性在一个实例中的修改,将会影响到其他所有实例。

class Person
clothes: []
give_item: (name) =>
table.insert @clothes, name
a = Person!
b = Person!
a\give_item "pants"
b\give_item "shirt"
-- 会同时打印出裤子和衬衫
print item for item in *a.clothes

  避免这个问题的正确方法是在构造函数中创建对象的可变状态:

class Person
new: =>
@clothes = []

  extends 关键字可以在类声明中使用,以继承另一个类的属性和方法。

class BackPack extends Inventory
size: 10
add_item: (name) =>
if #@items > size then error "背包已满"
super name

  在这一部分,我们对月之脚本中的 Inventory 类进行了扩展,加入了对可以携带物品数量的限制。

  在这个特定的例子中,子类并没有定义自己的构造函数。因此,当创建一个新的实例时,系统会默认调用父类的构造函数。但如果我们在子类中定义了构造函数,我们可以利用 super 方法来调用并执行父类的构造函数。

  此外,当一个类继承自另一个类时,它会尝试调用父类上的 __inherited 方法(如果这个方法存在的话),以此来向父类发送通知。这个 __inherited 函数接受两个参数:被继承的父类和继承的子类。

class Shelf
@__inherited: (child) =>
print @__name, "被", child.__name, "继承"
-- 将打印: Shelf 被 Cupboard 继承
class Cupboard extends Shelf

  super 是一个特别的关键字,它有两种不同的使用方式:既可以当作一个对象来看待,也可以像调用函数那样使用。它仅在类的内部使用时具有特殊的功能。

  当 super 被作为一个函数调用时,它将调用父类中与之同名的函数。此时,当前的 self 会自动作为第一个参数传递,正如上面提到的继承示例所展示的那样。

  在将 super 当作普通值使用时,它实际上是对父类对象的引用。通过这种方式,我们可以访问父类中可能被子类覆盖的值,就像访问任何普通对象一样。

  此外,当使用 \ 操作符与 super 一起使用时,self将被插入为第一个参数,而不是使用 super 本身的值。而在使用.操作符来检索函数时,则会返回父类中的原始函数。

  下面是一些使用 super 的不同方法的示例:

class MyClass extends ParentClass
a_method: =>
-- 以下效果相同:
super "你好", "世界"
super\a_method "你好", "世界"
super.a_method self, "你好", "世界"
-- super 作为值等于父类:
assert super == ParentClass

  super 也可以用在函数存根的左侧。唯一的主要区别是,生成的函数不是绑定到 super 的值,而是绑定到 self。

  每个类的实例都带有它的类型。这存储在特殊的 __class 属性中。此属性会保存类对象。类对象是我们用来构建新实例的对象。我们还可以索引类对象以检索类方法和属性。

b = BackPack!
assert b.__class == BackPack
print BackPack.size -- 打印 10

  在月之脚本中,当我们编写类的定义语句时,实际上是在创建一个类对象。这个类对象被保存在一个与该类同名的变量中。

  类对象具有函数的特性,可以被调用来创建新的实例。这正是我们在之前示例中所展示的创建类实例的方式。

  一个类由两个表构成:类表本身和一个基表。基表作为所有实例的元表。在类声明中列出的所有属性都存放在基表中。

  如果在类对象的元表中找不到某个属性,系统会从基表中检索该属性。这就意味着我们可以直接从类本身访问到其方法和属性。

  需要特别注意的是,对类对象的赋值并不会影响到基表,因此这不是向实例添加新方法的正确方式。相反,需要直接修改基表。关于这点,可以参考下面的 “__base” 字段。

  此外,类对象包含几个特殊的属性:当类被声明时,类的名称会作为一个字符串存储在类对象的 “__name” 字段中。

print BackPack.__name -- 打印 Backpack

  基础对象被保存在一个名为 __base 的特殊表中。我们可以编辑这个表,以便为那些已经创建出来的实例和还未创建的实例增加新的功能。

  另外,如果一个类是从另一个类派生而来的,那么其父类对象则会被存储在名为 __parent 的地方。这种机制允许在类之间实现继承和功能扩展。

  我们可以直接在类对象中创建变量,而不是在类的基对象中,通过在类声明中的属性名前使用 @。

class Things
@some_func: => print "Hello from", @__name
Things\some_func!
-- 类变量在实例中不可见
assert Things().some_func == nil

  在表达式中,我们可以使用 @@ 来访问存储在 self.__class 中的值。因此,@@hello 是 self.__class.hello 的简写。

class Counter
@count: 0
new: =>
@@count += 1
Counter!
Counter!
print Counter.count -- 输出 2

  @@ 的调用语义与 @ 类似。调用 @@ 时,会使用 Lua 的冒号语法将类作为第一个参数传入。

@@hello 1,2,3,4

  在类声明的主体中,除了键/值对外,我们还可以编写普通的表达式。在这种类声明体中的普通代码的上下文中,self 等于类对象,而不是实例对象。

  以下是创建类变量的另一种方法:

class Things
@class_var = "hello world"

  这些表达式会在所有属性被添加到类的基对象后执行。

  在类的主体中声明的所有变量都会限制作用域只在类声明的范围。这对于放置只有类方法可以访问的私有值或辅助函数很方便:

class MoreThings
secret = 123
log = (msg) -> print "LOG:", msg
some_method: =>
log "hello world: " .. secret

  当 @ 和 @@ 前缀在一个名字前时,它们分别代表在 self 和 self.__class 中访问的那个名字。

  如果它们单独使用,它们是 self 和 self.__class 的别名。

assert @ == self
assert @@ == self.__class

  例如,使用 @@ 从实例方法快速创建同一类的新实例的方法:

some_instance_method = (...) => @@ ...

  为了减少编写简单值对象定义的代码。你可以这样简单写一个类:

class Something
new: (@foo, @bar, @@biz, @@baz) =>
-- 这是以下声明的简写形式
class Something
new: (foo, bar, biz, baz) =>
@foo = foo
@bar = bar
@@biz = biz
@@baz = baz

  你也可以使用这种语法为一个函数初始化传入对象的字段。

new = (@fieldA, @fieldB) => @
obj = new {}, 123, "abc"
print obj

  类声明的语法也可以作为一个表达式使用,可以赋值给一个变量或者被返回语句返回。

x = class Bucket
drops: 0
add_drop: => @drops += 1

  声明类时可以省略名称。如果类的表达式不在赋值语句中,__name 属性将为 nil。如果出现在赋值语句中,赋值操作左侧的名称将代替 nil。

BigBucket = class extends Bucket
add_drop: => @drops += 10
assert Bucket.__name == "BigBucket"

  你甚至可以省略掉主体,这意味着你可以这样写一个空白的匿名类:

x = class

  你可以通过使用 using 关键字来实现类混合。这意味着你可以从一个普通 Lua 表格或已定义的类对象中,复制函数到你创建的新类中。当你使用普通 Lua 表格进行类混合时,你有机会用自己的实现来重写类的索引方法(例如元方法 __index)。然而,当你从一个类对象做混合时,需要注意的是该类对象的元方法将不会被复制到新类。

MyIndex = __index: var: 1
class X using MyIndex
func: =>
print 123
x = X!
print x.var
class Y using X
y = Y!
y\func!
assert y.__class.__parent ~= X -- X 不是 Y 的父类

with 语句

在编写 Lua 代码时,我们在创建对象后的常见操作是立即调用这个对象一系列操作函数并设置一系列属性。

这导致在代码中多次重复引用对象的名称,增加了不必要的文本噪音。一个常见的解决方案是在创建对象时,在构造函数传入一个表,该表包含要覆盖设置的键和值的集合。这样做的缺点是该对象的构造函数必须支持这种初始化形式。

with 块有助于简化编写这样的代码。在 with 块内,我们可以使用以 . 或 \ 开头的特殊语句,这些语句代表我们正在使用的对象的操作。

例如,我们可以这样处理一个新创建的对象:

with Person!
.name = "Oswald"
\add_relative my_dad
\save!
print .name

with 语句也可以用作一个表达式,并返回它的代码块正在处理的对象。

file = with File "favorite_foods.txt"
\set_encoding "utf8"

with 表达式支持 break 返回一个值:

result = with obj
break .value

在 with 中使用 break value 后,with 表达式将不再返回其目标对象,而是返回 break 给出的值。

a = with obj
.x = 1
-- a 是 obj
b = with obj
break .x
-- b 是 .x,不是 obj

与 for / while / repeat / do 不同,with 只支持一个 break 返回值。

或者…

create_person = (name, relatives) ->
with Person!
.name = name
\add_relative relative for relative in *relatives
me = create_person "Leaf", [dad, mother, sister]

在此用法中,with 可以被视为K组合子(k-combinator)的一种特殊形式。

如果你想给表达式另外起一个名称的话,with 语句中的表达式也可以是一个赋值语句。

with str := "你好"
print "原始:", str
print "大写:", \upper!

你可以在 with 语句中使用 [] 访问特殊键。

with tb
[1] = 1
print [2]
with [abc]
[3] = [2]\func!
["key-name"] = value
[] = "abc" -- 追加到 "tb"

with? 是 with 语法的一个增强版本,引入了存在性检查,用于在不显式判空的情况下安全访问可能为 nil 的对象。

with? obj
print .name

宏

  宏函数用于在编译时执行一段代码来生成新的代码,并将生成的代码插入到最终编译结果中。

macro PI2 = -> math.pi * 2
area = $PI2 * 5
macro HELLO = -> "'你好 世界'"
print $HELLO
macro config = (debugging) ->
global debugMode = debugging == "true"
""
macro asserts = (cond) ->
debugMode and "assert #{cond}" or ""
macro assert = (cond) ->
debugMode and "assert #{cond}" or "#{cond}"
$config true
$asserts item ~= nil
$config false
value = $assert item
-- 宏函数参数传递的表达式会被转换为字符串
macro and = (...) -> "#{ table.concat {...}, ' and ' }"
if $and f1!, f2!, f3!
print "OK"

  宏函数可以返回一个包含月之脚本代码的字符串,或是一个包含 Lua 代码字符串的配置表。

macro yueFunc = (var) -> "local #{var} = ->"
$yueFunc funcA
funcA = -> "无法访问宏生成月之脚本里定义的变量"
macro luaFunc = (var) -> {
code: "local function #{var}() end"
type: "lua"
}
$luaFunc funcB
funcB = -> "无法访问宏生成 Lua 代码里定义的变量"
macro lua = (code) -> {
:code
type: "lua"
}
-- raw字符串的开始和结束符号会自动被去除了再传入宏函数
$lua[==[
-- 插入原始Lua代码
if cond then
print("输出")
end
]==]

  宏函数可以从一个模块中导出,并在另一个模块中导入。你必须将导出的宏函数放在一个单独的文件中使用,而且只有宏定义、宏导入和宏展开可以放入这个宏导出模块中。

-- 文件: utils.yue
export macro map = (items, action) -> "[#{action} for _ in *#{items}]"
export macro filter = (items, action) -> "[_ for _ in *#{items} when #{action}]"
export macro foreach = (items, action) -> "for _ in *#{items}
#{action}"
-- 文件 main.yue
import "utils" as {
$, -- 表示导入所有宏的符号
$foreach: $each -- 重命名宏 $foreach 为 $each
}
[1, 2, 3] |> $map(_ * 2) |> $filter(_ > 4) |> $each print _

  月之脚本中有一些内置可以直接使用的宏,但你可以通过声明相同名称的宏来覆盖它们。

print $FILE -- 获取当前模块名称的字符串
print $LINE -- 获取当前代码行数:2

  在月之脚本中,宏函数允许你在编译时生成代码。通过嵌套的宏函数,你可以创建更复杂的生成模式。这个特性允许你定义一个宏函数,用它来生成另一个宏函数,从而实现更加动态的代码生成。

macro Enum = (...) ->
items = {...}
itemSet = {item, true for item in *items}
(item) ->
error "got \"#{item}\", expecting one of #{table.concat items, ', '}" unless itemSet[item]
"\"#{item}\""
macro BodyType = $Enum(
Static
Dynamic
Kinematic
)
print "有效的枚举类型:", $BodyType Static
-- print "编译报错的枚举类型:", $BodyType Unknown

  当宏返回多行 Yue 代码时,不推荐使用带引号的多行字符串,建议优先使用 -> |。

  带引号的字符串会按字面保留文本;而 YAML 风格多行字符串会移除公共前导缩进。对宏生成的 Yue 代码来说,这通常能让代码块结构更稳定,尤其是在生成代码里包含注释或嵌套块时。

macro default_conf = (conf) -> "
-- useful; only set once
#{conf}.identity = 'LOVE'
#{conf}.version = \"11.5\"
"
love.conf = (t) ->
$default_conf t
macro default_conf = (conf) -> |
-- useful; only set once
#{conf}.identity = 'LOVE'
#{conf}.version = "11.5"
love.conf = (t) ->
$default_conf t

  可以直接在参数列表中声明期望的 AST 节点类型,并在编译时检查传入的宏参数是否符合预期。

macro printNumAndStr = (num `Num, str `String) -> |
print(
#{num}
#{str}
)
$printNumAndStr 123, "hello"

  如果需要做更加灵活的参数检查操作,可以使用内置的 $is_ast 宏函数在合适的位置进行手动检查。

macro printNumAndStr = (num, str) ->
error "expected Num as first argument" unless $is_ast Num, num
error "expected String as second argument" unless $is_ast String, str
"print(#{num}, #{str})"
$printNumAndStr 123, "hello"

  更多关于可用 AST 节点的详细信息,请参考 yue_parser.cpp 中大写的规则定义。

  注解语句会把一个宏应用到它后面的那条语句上。

  这等价于调用该宏,并把后面那条语句的源码作为最后一个参数附加进去。

macro ShowName = (code) -> |
print "#{code\match '^[%w_]*'}"
$[ShowName]
myFunc = ->
return

  如果注解宏返回的是配置表,可选字段 before 可以控制生成结果插入到被注解语句之前还是之后。

macro Tag = (tag, code) ->
tableName = code\match "^[%w_]+"
return
type: "text"
before: tag == "before"
code: "-- #{tag}:#{tableName}"
$[Tag before]
tableA = {}
$[Tag after]
tableB = {}
return

  由于后面的语句会作为额外的宏参数传入,注解也可以从类声明生成注册代码。它同样可以使用普通宏支持的 AST 参数检查。

macro Register = (registry, code`ClassDecl) ->
className = code\match "^class%s+(%w+)"
return |
#{registry}["#{className}"] = #{className}
registry = {}
$[Register(registry)]
class Worker
run: => "ok"
return

  注解也可以用来给函数注入包装代码。

macro ValidateNumberArgs = (code) ->
funcName = code\match "^(%w+)%s*="
return |
local __orig_#{funcName} = #{funcName}
#{funcName} = (...) ->
for i = 1, select "#", ...
assert type(select i, ...) == "number", "expected number for arg \#{i}"
__orig_#{funcName} ...
$[ValidateNumberArgs]
add = (a, b) -> a + b

  注解后面必须紧跟一条语句,而且不能作用在 return 语句上。如果被注解的语句正好位于代码块末尾,而你又需要拿到原始的语句 AST 形态,就需要额外补一个显式的 return,避免它被隐式返回表达式包起来。

代码行修饰

  为了方便编写代码,循环语句和 if 语句可以应用于单行代码语句的末尾:

print "你好,世界" if name == "Rob"

  修饰 for 循环的示例:

print "项目: ", item for item in *items

  修饰 while 循环的示例:

game\update! while game\isRunning!
reader\parse_line! until reader\eof!

do 语句

  当用作语句时,do 语句的作用就像在 Lua 中差不多。

do
var = "hello"
print var
print var -- 这里是nil

  月之脚本的 do 也可以用作表达式。允许你将多行代码的处理合并为一个表达式,并将 do 语句代码块的最后一个语句作为表达式返回的结果。

counter = do
i = 0
->
i += 1
i
print counter!
print counter!
tbl = {
key: do
print "分配键值!"
1234
}

  do 表达式支持通过 break 打断执行流并提前返回多个值。

status, value = do
n = 12
if n > 10
break "large", n
break "small", n

错误处理

  用于统一进行 Lua 错误处理的便捷语法。

try
func 1, 2, 3
catch err
print yue.traceback err
success, result = try
func 1, 2, 3
catch err
yue.traceback err
try func 1, 2, 3
catch err
print yue.traceback err
success, result = try func 1, 2, 3
try
print "尝试中"
func 1, 2, 3
-- 使用if赋值模式
if success, result := try func 1, 2, 3
catch err
print yue.traceback err
print result

  try? 是 try 的功能简化语法,它不再返回 try 语句的布尔状态,并在成功时直接返回 try 代码块的结果,失败时返回 nil 值而非错误对象。

a, b, c = try? func!
-- 与空值合并运算符一起使用
a = (try? func!) ?? "default"
-- 作为函数参数
f try? func!
-- 带 catch 块的 try!
f try?
print 123
func!
catch e
print e
e

月之脚本语言库

在 Lua 中使用 local yue = require("yue") 来访问。

描述:

月之脚本语言库。

类型: 成员变量。

描述:

月之脚本版本。

签名:

version: string

类型: 成员变量。

描述:

当前平台的文件分隔符。

签名:

dirsep: string

类型: 成员变量。

描述:

编译模块代码缓存。

签名:

yue_compiled: {string: string}

类型: 函数。

描述:

月之脚本的编译函数。它将 YueScript 代码编译为 Lua 代码。

签名:

to_lua: function(code: string, config?: Config):
--[[codes]] string | nil,
--[[error]] string | nil,
--[[globals]] {{string, integer, integer}} | nil

参数:

参数名 类型 描述
code string YueScript 代码。
config Config [可选] 编译器选项。

返回值:

返回类型 描述
string | nil 编译后的 Lua 代码,如果编译失败则为 nil。
string | nil 错误消息,如果编译成功则为 nil。
{{string, integer, integer}} | nil 代码中出现的全局变量(带有名称、行和列),如果编译器选项 lint_global 为 false 则为 nil。

类型: 函数。

描述:

检查源文件是否存在的函数。可以覆盖该函数以自定义行为。

签名:

file_exist: function(filename: string): boolean

参数:

参数名 类型 描述
filename string 文件名。

返回值:

返回类型 描述
boolean 文件是否存在。

类型: 函数。

描述:

读取源文件的函数。可以覆盖该函数以自定义行为。

签名:

read_file: function(filename: string): string

参数:

参数名 类型 描述
filename string 文件名。

返回值:

返回类型 描述
string 文件内容。

类型: 函数。

描述:

将 YueScript 加载器插入到 Lua 包加载器(搜索器)中。

签名:

insert_loader: function(pos?: integer): boolean

参数:

参数名 类型 描述
pos integer [可选] 要插入加载器的位置。默认为 3。

返回值:

返回类型 描述
boolean 是否成功插入加载器。如果加载器已经插入,则返回失败。

类型: 函数。

描述:

从 Lua 包加载器(搜索器)中移除 YueScript 加载器。

签名:

remove_loader: function(): boolean

返回值:

返回类型 描述
boolean 是否成功移除加载器。如果加载器未插入,则返回失败。

类型: 函数。

描述:

将 YueScript 代码字符串加载为一个函数。

签名:

loadstring: function(input: string, chunkname: string, env: table, config?: Config):
--[[loaded function]] nil | function(...: any): (any...),
--[[error]] string | nil

参数:

参数名 类型 描述
input string YueScript 代码。
chunkname string 代码块的名称。
env table 环境表。
config Config [可选] 编译器选项。

返回值:

返回类型 描述
function | nil 加载的函数,如果加载失败则为 nil。
string | nil 错误消息,如果加载成功则为 nil。

类型: 函数。

描述:

将 YueScript 代码字符串加载为一个函数。

签名:

loadstring: function(input: string, chunkname: string, config?: Config):
--[[loaded function]] nil | function(...: any): (any...),
--[[error]] string | nil

参数:

参数名 类型 描述
input string YueScript 代码。
chunkname string 代码块的名称。
config Config [可选] 编译器选项。

返回值:

返回类型 描述
function | nil 加载的函数,如果加载失败则为 nil。
string | nil 错误消息,如果加载成功则为 nil。

类型: 函数。

描述:

将 YueScript 代码字符串加载为一个函数。

签名:

loadstring: function(input: string, config?: Config):
--[[loaded function]] nil | function(...: any): (any...),
--[[error]] string | nil

参数:

参数名 类型 描述
input string YueScript 代码。
config Config [可选] 编译器选项。

返回值:

返回类型 描述
function | nil 加载的函数,如果加载失败则为 nil。
string | nil 错误消息,如果加载成功则为 nil。

类型: 函数。

描述:

将 YueScript 代码文件加载为一个函数。

签名:

loadfile: function(filename: string, env: table, config?: Config):
nil | function(...: any): (any...),
string | nil

参数:

参数名 类型 描述
filename string 文件名。
env table 环境表。
config Config [可选] 编译器选项。

返回值:

返回类型 描述
function | nil 加载的函数,如果加载失败则为 nil。
string | nil 错误消息,如果加载成功则为 nil。

类型: 函数。

描述:

将 YueScript 代码文件加载为一个函数。

签名:

loadfile: function(filename: string, config?: Config):
nil | function(...: any): (any...),
string | nil

参数:

参数名 类型 描述
filename string 文件名。
config Config [可选] 编译器选项。

返回值:

返回类型 描述
function | nil 加载的函数,如果加载失败则为 nil。
string | nil 错误消息,如果加载成功则为 nil。

类型: 函数。

描述:

将 YueScript 代码文件加载为一个函数并执行。

签名:

dofile: function(filename: string, env: table, config?: Config): any...

参数:

参数名 类型 描述
filename string 文件名。
env table 环境表。
config Config [可选] 编译器选项。

返回值:

返回类型 描述
any… 加载的函数执行后的返回值。

类型: 函数。

描述:

将 YueScript 代码文件加载为一个函数并执行。

签名:

dofile: function(filename: string, config?: Config): any...

参数:

参数名 类型 描述
filename string 文件名。
config Config [可选] 编译器选项。

返回值:

返回类型 描述
any… 加载的函数执行后的返回值。

类型: 函数。

描述:

将 YueScript 模块名解析为文件路径。

签名:

find_modulepath: function(name: string): string

参数:

参数名 类型 描述
name string 模块名。

返回值:

返回类型 描述
string 文件路径。

类型: 函数。

描述:

在保护模式下调用一个函数。 会捕获任何错误,执行成功则返回成功状态和结果,否则为失败状态和错误信息。 当发生错误时,将错误信息中的代码行号重写为 YueScript 代码中的原始行号。

签名:

pcall: function(f: function, ...: any): boolean, any...

参数:

参数名 类型 描述
f function 要调用的函数。
… any 要传递给函数的参数。

返回值:

返回类型 描述
boolean, … 状态码和函数结果或错误信息。

类型: 函数。

描述:

加载给定的模块。可以是 Lua 模块或 YueScript 模块。 如果模块是 YueScript 模块且加载失败,则将错误信息中的代码行号重写为 YueScript 代码中的原始行号。

签名:

require: function(name: string): any...

参数:

参数名 类型 描述
modname string 要加载的模块名。

返回值:

返回类型 描述
any 如果模块已经加载,则返回 package.loaded[modname] 中存储的值。否则,尝试查找加载器并返回 package.loaded[modname] 的最终值和加载器数据作为第二个结果。

类型: 函数。

描述:

检查传递的值的内部结构,并打印值出它的字符串表示。

签名:

p: function(...: any)

参数:

参数名 类型 描述
… any 要检查的值。

类型: 成员变量。

描述:

当前编译器选项。

签名:

options: Config.Options

类型: 函数。

描述:

重写堆栈跟踪中的行号为 YueScript 代码中的原始行号的 traceback 函数。

签名:

traceback: function(message: string): string

参数:

参数名 类型 描述
message string 堆栈跟踪消息。

返回值:

返回类型 描述
string 重写后的堆栈跟踪消息。

类型: 函数。

描述:

检查代码是否匹配指定的 AST。

签名:

is_ast: function(astName: string, code: string): boolean

参数:

参数名 类型 描述
astName string AST 名称。
code string 代码。

返回值:

返回类型 描述
boolean 代码是否匹配 AST。

类型: 成员变量。

描述:

AST 类型定义,带有名称、行、列和子节点。

签名:

type AST = {string, integer, integer, any}

类型: 函数。

描述:

将代码转换为 AST。

签名:

to_ast: function(code: string, flattenLevel?: number, astName?: string, reserveComment?: boolean):
--[[AST]] AST | nil,
--[[error]] nil | string

参数:

参数名 类型 描述
code string 代码。
flattenLevel integer [可选] 扁平化级别。级别越高,会消除更多的 AST 结构的嵌套。默认为 0。最大为 2。
astName string [可选] AST 名称。默认为 “File”。
reserveComment boolean [可选] 是否保留原始注释。默认为 false。

返回值:

返回类型 描述
AST | nil AST,如果转换失败则为 nil。
string | nil 错误消息,如果转换成功则为 nil。

类型: 函数。

描述:

格式化 YueScript 代码。

签名:

format: function(code: string, tabSize?: number, reserveComment?: boolean): string

参数:

参数名 类型 描述
code string 代码。
tabSize integer [可选] 制表符大小。默认为 4。
reserveComment boolean [可选] 是否保留原始注释。默认为 true。

返回值:

返回类型 描述
string 格式化后的代码。

类型: 元方法。

描述:

导入 YueScript 模块。 如果发生加载失败,则将错误信息中的代码行号重写为 YueScript 代码中的原始行号。

签名:

metamethod __call: function(self: yue, module: string): any...

参数:

参数名 类型 描述
module string 模块名。

返回值:

返回类型 描述
any 模块值。

描述:

编译器编译选项。

类型: 成员变量。

描述:

编译器是否应该收集代码中出现的全局变量。

签名:

lint_global: boolean

类型: 成员变量。

描述:

编译器是否应该对根层级的代码块进行隐式的表达式返回。

签名:

implicit_return_root: boolean

类型: 成员变量。

描述:

编译器是否应该在编译后的代码中保留原始行号。

签名:

reserve_line_number: boolean

类型: 成员变量。

描述:

编译器是否应该在编译后的代码中保留原始注释。

签名:

reserve_comment: boolean

类型: 成员变量。

描述:

编译器是否应该在编译后的代码中使用空格字符而不是制表符字符。

签名:

space_over_tab: boolean

类型: 成员变量。

描述:

编译器是否应该将要编译的代码视为当前正在编译的模块。仅供编译器内部使用。

签名:

same_module: boolean

类型: 成员变量。

描述:

编译器错误消息是否应该包含行号偏移量。仅供编译器内部使用。

签名:

line_offset: integer

类型: 枚举。

描述:

目标 Lua 版本枚举。

签名:

enum LuaTarget
"5.1"
"5.2"
"5.3"
"5.4"
"5.5"
end

类型: 成员变量。

描述:

要传递给编译函数的额外选项。

签名:

options: Options

描述:

额外编译器选项定义。

类型: 成员变量。

描述:

编译目标 Lua 版本。

签名:

target: LuaTarget

类型: 成员变量。

描述:

额外模块搜索路径。

签名:

path: string

类型: 成员变量。

描述:

是否在回溯错误消息中输出代码块的局部变量。默认为 false。

签名:

dump_locals: boolean

类型: 成员变量。

描述:

是否简化输出的错误消息。默认为 true。

签名:

simplified: boolean

吉祥物,小玉

机兔小玉

小玉(Xiaoyu,意为“小小的玉”)是一只赛博兔,也是 YueScript 项目的官方吉祥物。她坐在一弯新月上,用笔记本电脑写 YueScript。

她的全称是机兔小玉,体现了 YueScript 的追求:简洁、富有表现力、带着一点俏皮。

Tyson Tan 为本项目免费设计和绘制了小玉,我们对此十分感谢。

点击任意图片可查看原始尺寸。

小玉,含背景的完整画作

下载完整 PNG

小玉,透明背景

下载透明背景 PNG

下载 .kra 压缩文件

小玉采用 MIT 许可证 和知识共享 CC-BY-SA 4.0 双重许可。

MIT 许可证

版权 (c) 2017-2026 李瑾 <dragon-fly@qq.com>

特此免费授予任何获得本软件副本和相关文档文件(下称“软件”)的人不受限制地处置该软件的权利,包括不受限制地使用、复制、修改、合并、发布、分发、转授许可和/或出售该软件副本,以及再授权被配发了本软件的人如上的权利,须在下列条件下: 上述版权声明和本许可声明应包含在该软件的所有副本或实质成分中。 本软件是“如此”提供的,没有任何形式的明示或暗示的保证,包括但不限于对适销性、特定用途的适用性和不侵权的保证。在任何情况下,作者或版权持有人都不对任何索赔、损害或其他责任负责,无论这些追责来自合同、侵权或其它行为中,还是产生于、源于或有关于本软件以及本软件的使用或其它处置。

月之脚本的在线编译器

在这里试试 WASM 版的 YueScript 吧。

YueScript编译器

正在加载编译器…