**This is an old revision of the document!**

非标准 Lua 库

大部分 Lua 标准库都是可用的,尽管其中的一些只是部分可用或重新实现的——这意味着表现可能会略有不同。请参阅 Lua 5.3 手册 中关于标准库的文档。

对“重新实现”的描述是基于 OpenOS 的,其他自定义的操作系统可能会有不同的细节。

本页试图列出所有与标准库的差异。

基本函数

基准库中的原函数是可用的,但有以下不同:

  • collectgarbage 可用.
  • dofileloadfile 被重新实现为从已挂载的文件系统组件内加载文件(使用文件系统 API 或重新实现过的 io 库)。
  • load 在默认情况下只能用来加载文本,不支持二进制(即编译过的)代码。注意,配置中可以启用字节码的加载,但非常不推荐这样做,因为这是一个重要安全风险
  • printio.writeio.stdout:writeterm.write 都向stdout(标准输出)推送字符串,这在大多数情况下都指gpu渲染,即打印到屏幕上。

协程操作

coroutine 库内的原函数均可用,并没有可见的差异。

注意,暴露给用户使用的 coroutine.resumecoroutine.yield 实现是封装过的,它们负责中止在一定时间后还没有 yield 的代码(见配置),还可以区分系统产生的 yield 和用户产生的 yield(系统会产生“bubble”,例如在关闭命令行或调用组件 API 时)。然而这不应该从用户代码层面被注意到。如果被注意到了,那这应该被认为是一个 bug。

模块

模块部分已针对 OpenComputer 重新实现。它应该跟原本的相同,但它没有以下函数:

  • package.config 没有用
  • package.cpath 没有用
  • package.loadlib 未实现

后两者不存在是因为OpenComputer不能加载C语言程序。

require 是一个在全局范围内可用的方法,也就是说,你不需要加载任何模块就能访问它,你可以在脚本的第一行就使用它(这是很常见的用法)。它的工作依赖于 package 库,因此我们就在这里对它进行介绍。

  • require(library: string): table

    返回名字对应的 library。首先,如果该库先前已被加载过,那么 package 库已经缓存过它了,require 会返回该库的缓存版本。如果想要卸载库,参见 package.loaded。如果库没有被缓存,就会搜索 package.path 直到找到匹配的库。

  • package.path 我们建议用户不要修改默认的 package.path,相反地,用户应该把自定义库放入 /usr/lib/ 中。

    定义了一个供 require 遍历寻找库的搜索路径列表。它是一个用分号分隔的路径列表,使用 ‘?’ 作为传递给 require 库名的占位符。下面的例子会使它更容易理解

    默认 package.path:

    /lib/?.lua;/usr/lib/?.lua;/home/lib/?.lua;./?.lua;/lib/?/init.lua;/usr/lib/?/init.lua;/home/lib/?/init.lua;./?/init.lua

    如果用户尝试导入 “foobar”

    local foobar = require("foobar")

    以下是 require 为了执行 require(“foobar”) 而会去查找的文件的顺序。我们假设当前工作目录是 /tmp/,因为这样很 Cool!

    • /lib/foobar.lua
    • /usr/lib/foobar.lua
    • /home/lib/foobar.lua
    • /tmp/foobar.lua
    • /lib/foobar/init.lua
    • /usr/lib/foobar/init.lua
    • /home/lib/foobar/init.lua
    • /tmp/foobar/init.lua
  • package.loaded 修改 package.loaded 的行为是不标准的

    一个包含已被缓存的库的table,键为库的名称 (即传入 require 的那个),值是缓存的库本身。将该表中的某个值设为 nil 会从缓存中实实在在地删除这个库。有些库会假定保持加载状态,为了操作系统的正常运行。

字符串操作

string 库内的原函数均可用,没有任何变化。

注意,GPU API 中的函数可以接收 UTF-8 字符串,进而 term.writeprint 也可以。为了帮助你处理 UTF-8 字符串,我们准备了一个额外的库,即 Unicode API

表操作

table 库内的原函数均可用,没有任何变化。

数学函数

math 库中的原函数均可用,但有一点改动。

  • math.random 为每个 Lua 状态和计算机使用一个单独的 java.util.Random 实例。
  • math.randomseed 也被应用于对应的实例。

位操作

bit32 库内的原函数均可用,没有任何变化。

输入输出设备

io 库中大部分原函数都被重新实现了,它们工作在已挂载的文件系统组件上。标准输入通过 io.read (同 term.readio.stdin:read)读取。 标准输出通过 io.write (同 term.writeio.stdout:writeprint)写入。标准错误通过 io.stderr:write 写入。

大多数情况下,它们应该与标准 Lua 实现在功能上等价。也许它们返回的错误信息与 vanilla Lua 不同,但大多数情况下vanilla Lua使用的也是与平台相关的 C 异常库,因此将这些用于程序逻辑无论如何都不是一个好主意。

  • io.open(path, mode) 支持 + 模式,即只支持 rwarbwbab 是允许的,但是二进制模式对写入来说没有意义)。 注意, io.open() 返回 缓冲流,有比较智能的 api,如 read("*a") 可以读取整个文件,或者 io.read("*l") 可以读取一行。注意,缓冲流(由 io.open返回的)可以是二进制模式或文本模式,而原始流(由 filesystem.open返回的)是且只能是二进制模式。 流是二进制模式还是文本模式只会影响到 read

    path 是你想打开的文件的相对路径或绝对路径。

    mode 可以是 nil 或一个字符串, nil 默认为“r”。

    • 二进制模式

      io.open(path, "rb")filesystem.open(path) 返回的流是二进制格式。 filesystem.open(path, "rb") 也可以,但从 filesystem.open 返回的流永远都是二进制模式。 stream:read(1) 在二进制模式下读取一个字节。通过 buffered_stream:read("*n") 读取一个数值会把数据读成单字节的字符。(缓冲流从 io.open 返回,并支持从流中解释数值)

    • 文本模式

      只有从 io.open 返回的,模式中没有使用 “b” 的流才是文本模式。例如 io.open(path)io.open(path, "r")filesystem.open 返回的任何句柄都不是文本模式下的流。文本模式下的 stream:read(1) 是一个单一字节码的 char。它可能是单字节的,或者甚至有3个字节——这取决于文本。 通过 buffered_stream:read("*n") 将一个数值读取成 unicode 字符。(缓冲流是由 io,open 返回的,支持从流中解释数值)

    • io.open(path, "r") 相当于 io.open(path),它以文本只读模式打开一个文件。
    • io.open(path, "rb") 以二进制只读模式打开一个文件。
    • io.open(path, "w") 截断文件的所有内容,以写模式打开文件。流是二进制模式还是文本模式并不影响写入。因此“w”在功能上等价于“wb”。
    • io.open(path, "a") 以写模式打开一个文件并将文件句柄位置放在文件的末尾,这意味着下一次写入会追加到文件末尾。
  • io.stdin 从模拟的 stdin 中读取数据,它默认为 openos shell 中的用户输入。注意 io.read()io.stdin:read() 的简称,它们会被解析为完全相同的操作。
snippet.lua
io.stdin:read() -- 从 std in 中读取
io.read() -- 也是从 std in 中读取
term.read() -- 也是从 std in 中读取
  • io.stdout 将数据写到模拟的 stdout,默认在 gpu 上渲染。注意 io.write()io.stdout:write() 的简称,它们会被解析为完全相同的操作。
snippet.lua
io.stdout:write("写入 stdout")
io.write("也是写入 stdout")
term.write("也是写入 stdout,但如果字符串超过屏幕分辨率允许的宽度,就会自动换行,就像这样--------------------------------------------------------------------------------------------------------------------------------#")
  • io.stderr 将数据写到模拟的 stderr,默认在 gpu 上渲染,但如果主 GPU 和屏幕支持的话,会尝试用红色渲染。没有像 stdin 和 stdout 那样的使用 stderr 的快捷方式。
snippet.lua
io.stderr:write("向 stderr 写入错误信息")

操作系统设备

os 库内的原函数已经被部分重新实现。

  • os.clock 被重新实现为返回一个近似的 CPU 时间,也就是计算机在一个执行器线程中实际运行的时间。这与计算机运行时间一样,详见 computer.uptime
  • os.date 被重新实现为游戏中的时间,并支持大多数格式。
  • os.execute 被重新实现,相当于从已挂载的文件系统中通过 shell.execute 启动程序。传入的字符串相当于在 shell 内输入的命令。
  • os.exit 抛出一个错误并试图终止当前的 coroutine。
  • os.setenv 添加 Lua shell 的环境变量。
  • os.removefilesystem.remove 的别名。
  • os.renamefilesystem.rename 的别名。
  • os.setlocale 可用。
  • os.time 被重新实现为返回自世界创建以来的游戏时间。 注意这个时间是以“游戏内的秒”为单位的。要获得世界创建起的时间刻(tick),需要将其乘以 1000/60/60 (因为一天有24000个时间刻) 然后减去 6000。这个 6000 的偏移量不是随便设计的,它可以确保早上 6 点确实是那个时候。我的世界莫名其妙地认为早上六点是零,可能是因为那是一个新游戏开始的时间……
  • os.tmpname 已被重新实现,可以创建一个未使用的名字挂载到 /tmp 下。

额外增加了一个函数:

  • os.sleep(seconds: number) 允许在指定的时间内暂停脚本。os.sleep 消耗事件,但注册的事件处理程序和线程仍在接收事件。换句话说,在睡眠状态下,信号仍然会被事件处理程序处理,也就是说你无法在睡眠结束后拉取睡眠期间堆积的信号,因为没有信号会留在队列中(或者说至少不是全部信号)。

计算机 API 提供了一些类似这个类别的函数。

调试

只实现了 debug.tracebackdebug.getinfo (1.5.9 起),后者仅限被动信息。

目录