Update Cobalt to 0.8.0

- Update Cobalt to 0.8.0, switching our Lua version to 5.2(ish).

 - Remove our `load` wrapper, as we no longer need to inject _ENV into
   the enviroment table.

 - Update the parser to handle labels and goto. This doesn't check that
   gotos are well formed, but at least means the parser doesn't fall
   over on them.

 - Update our docs to reflect the changes to Cobalt.
This commit is contained in:
Jonathan Coates 2023-11-08 18:42:17 +00:00
parent bcb3e9bd53
commit 784e623776
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
15 changed files with 429 additions and 352 deletions

View File

@ -57,7 +57,6 @@ repositories {
filter {
includeGroup("cc.tweaked")
includeModule("org.squiddev", "Cobalt")
// Things we mirror
includeGroup("commoble.morered")
includeGroup("dev.architectury")

View File

@ -21,6 +21,17 @@ # Incompatibilities between versions
However, some changes to the underlying game, or CC: Tweaked's own internals may break some programs. This page serves
as documentation for breaking changes and "gotchas" one should look out for between versions.
## CC: Tweaked 1.109.0 {#cct-1.109}
- Update to Lua 5.2:
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
- Environments are no longer baked into the runtime, and instead use the `_ENV` local or upvalue. `getfenv`/`setfenv`
now only work on Lua functions with an `_ENV` upvalue. `getfenv` will return the global environment when called
with other functions, and `setfenv` will have no effect.
- `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
environment.
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
## Minecraft 1.13 {#mc-1.13}
- The "key code" for [`key`] and [`key_up`] events has changed, due to Minecraft updating to LWJGL 3. Make sure you're
using the constants provided by the [`keys`] API, rather than hard-coding numerical values.

View File

@ -9,17 +9,19 @@
-->
# Lua 5.2/5.3 features in CC: Tweaked
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.1. However, Cobalt and CC:T implement additional features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 features) that are not available in base 5.1. This page lists all of the compatibility for these newer versions.
CC: Tweaked is based off of the Cobalt Lua runtime, which uses Lua 5.2. However, Cobalt and CC:T implement additional
features from Lua 5.2 and 5.3 (as well as some deprecated 5.0 and 5.1 features). This page lists all of the
compatibility for these newer versions.
## Lua 5.2
| Feature | Supported? | Notes |
|---------------------------------------------------------------|------------|-------------------------------------------------------------------|
| `goto`/labels | | |
| `_ENV` | 🔶 | The `_ENV` global points to `getfenv()`, but it cannot be set. |
| `goto`/labels | | |
| `_ENV` | ✔ | |
| `\z` escape | ✔ | |
| `\xNN` escape | ✔ | |
| Hex literal fractional/exponent parts | ✔ | |
| Empty statements | | |
| Empty statements | | |
| `__len` metamethod | ✔ | |
| `__ipairs` metamethod | ❌ | Deprecated in Lua 5.3. `ipairs` uses `__len`/`__index` instead. |
| `__pairs` metamethod | ✔ | |
@ -27,12 +29,12 @@ ## Lua 5.2
| `collectgarbage` isrunning, generational, incremental options | ❌ | `collectgarbage` does not exist in CC:T. |
| New `load` syntax | ✔ | |
| `loadfile` mode parameter | ✔ | Supports both 5.1 and 5.2+ syntax. |
| Removed `loadstring` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `getfenv`, `setfenv` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `loadstring` | ❌ | |
| Removed `getfenv`, `setfenv` | 🔶 | Only supports closures with an `_ENV` upvalue. |
| `rawlen` function | ✔ | |
| Negative index to `select` | ✔ | |
| Removed `unpack` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Arguments to `xpcall` | ✔ | |
| Removed `unpack` | ❌ | |
| Arguments to `xpcall` | ✔ | |
| Second return value from `coroutine.running` | ✔ | |
| Removed `module` | ✔ | |
| `package.loaders` -> `package.searchers` | ❌ | |
@ -40,14 +42,14 @@ ## Lua 5.2
| `package.config` | ✔ | |
| `package.searchpath` | ✔ | |
| Removed `package.seeall` | ✔ | |
| `string.dump` on functions with upvalues (blanks them out) | ✔ | |
| `string.rep` separator | ✔ | |
| `string.dump` on functions with upvalues (blanks them out) | ❌ | `string.dump` is not supported |
| `string.rep` separator | ✔ | |
| `%g` match group | ❌ | |
| Removal of `%z` match group | ❌ | |
| Removed `table.maxn` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `table.maxn` | ❌ | |
| `table.pack`/`table.unpack` | ✔ | |
| `math.log` base argument | ✔ | |
| Removed `math.log10` | 🔶 | Only if `disable_lua51_features` is enabled in the configuration. |
| Removed `math.log10` | ❌ | |
| `*L` mode to `file:read` | ✔ | |
| `os.execute` exit type + return value | ❌ | `os.execute` does not exist in CC:T. |
| `os.exit` close argument | ❌ | `os.exit` does not exist in CC:T. |
@ -61,7 +63,7 @@ ## Lua 5.2
| Tail call hooks | ❌ | |
| `=` prefix for chunks | ✔ | |
| Yield across C boundary | ✔ | |
| Removal of ambiguity error | | |
| Removal of ambiguity error | | |
| Identifiers may no longer use locale-dependent letters | ✔ | |
| Ephemeron tables | ❌ | |
| Identical functions may be reused | ❌ | Removed in Lua 5.4 |

View File

@ -19,8 +19,8 @@ parchmentMc = "1.20.1"
asm = "9.5"
autoService = "1.1.1"
checkerFramework = "3.32.0"
cobalt = "0.7.3"
cobalt-next = "0.7.4" # Not a real version, used to constrain the version we accept.
cobalt = "0.8.0"
cobalt-next = "0.8.1" # Not a real version, used to constrain the version we accept.
commonsCli = "1.3.1"
fastutil = "8.5.9"
guava = "31.1-jre"
@ -51,7 +51,7 @@ jqwik = "1.7.4"
junit = "5.10.0"
# Build tools
cctJavadoc = "1.8.0"
cctJavadoc = "1.8.1"
checkstyle = "10.12.3"
curseForgeGradle = "1.0.14"
errorProne-core = "2.21.1"
@ -68,7 +68,7 @@ mixinGradle = "0.7.+"
nullAway = "0.9.9"
spotless = "6.21.0"
taskTree = "2.1.1"
teavm = "0.9.0-SQUID.1"
teavm = "0.10.0-SQUID.1"
vanillaGradle = "0.2.1-SNAPSHOT"
vineflower = "1.11.0"
@ -78,7 +78,7 @@ asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
autoService = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
checkerFramework = { module = "org.checkerframework:checker-qual", version.ref = "checkerFramework" }
cobalt = { module = "org.squiddev:Cobalt", version.ref = "cobalt" }
cobalt = { module = "cc.tweaked:cobalt", version.ref = "cobalt" }
commonsCli = { module = "commons-cli:commons-cli", version.ref = "commonsCli" }
fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" }
forgeSpi = { module = "net.minecraftforge:forgespi", version.ref = "forgeSpi" }

View File

@ -73,20 +73,20 @@ public CobaltLuaMachine(MachineEnvironment environment, InputStream bios) throws
.build();
// Set up our global table.
var globals = state.getMainThread().getfenv();
CoreLibraries.debugGlobals(state);
Bit32Lib.add(state, globals);
globals.rawset("_HOST", ValueFactory.valueOf(environment.hostString()));
globals.rawset("_CC_DEFAULT_SETTINGS", ValueFactory.valueOf(CoreConfig.defaultComputerSettings));
// Add default APIs
for (var api : environment.apis()) addAPI(globals, api);
// And load the BIOS
try {
var globals = state.globals();
CoreLibraries.debugGlobals(state);
Bit32Lib.add(state, globals);
globals.rawset("_HOST", ValueFactory.valueOf(environment.hostString()));
globals.rawset("_CC_DEFAULT_SETTINGS", ValueFactory.valueOf(CoreConfig.defaultComputerSettings));
// Add default APIs
for (var api : environment.apis()) addAPI(globals, api);
// And load the BIOS
var value = LoadState.load(state, bios, "@bios.lua", globals);
mainRoutine = new LuaThread(state, value, globals);
} catch (CompileException e) {
mainRoutine = new LuaThread(state, value);
} catch (LuaError | CompileException e) {
throw new MachineException(Nullability.assertNonNull(e.getMessage()));
}
@ -171,7 +171,7 @@ private LuaTable wrapLuaObject(Object object) {
return found ? table : null;
}
private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Object, LuaValue> values) {
private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Object, LuaValue> values) throws LuaError {
if (object == null) return Constants.NIL;
if (object instanceof Number num) return ValueFactory.valueOf(num.doubleValue());
if (object instanceof Boolean bool) return ValueFactory.valueOf(bool);
@ -235,7 +235,7 @@ private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Obje
return Constants.NIL;
}
Varargs toValues(@Nullable Object[] objects) {
Varargs toValues(@Nullable Object[] objects) throws LuaError {
if (objects == null || objects.length == 0) return Constants.NONE;
if (objects.length == 1) return toValue(objects[0], null);

View File

@ -18,33 +18,6 @@ do
expect = f().expect
end
if _VERSION == "Lua 5.1" then
-- If we're on Lua 5.1, install parts of the Lua 5.2/5.3 API so that programs can be written against it
local nativeload = load
function load(x, name, mode, env)
expect(1, x, "function", "string")
expect(2, name, "string", "nil")
expect(3, mode, "string", "nil")
expect(4, env, "table", "nil")
local ok, p1, p2 = pcall(function()
local result, err = nativeload(x, name, mode, env)
if result and env then
env._ENV = env
end
return result, err
end)
if ok then
return p1, p2
else
error(p1, 2)
end
end
loadstring = function(string, chunkname) return nativeload(string, chunkname) end
end
-- Inject a stub for the old bit library
_G.bit = {
bnot = bit32.bnot,

View File

@ -535,6 +535,28 @@ function errors.unexpected_end(start_pos, end_pos)
}
end
--[[- A label statement was opened but not closed.
@tparam number open_start The start position of the opening label.
@tparam number open_end The end position of the opening label.
@tparam number tok_start The start position of the current token.
@return The resulting parse error.
]]
function errors.unclosed_label(open_start, open_end, token, start_pos, end_pos)
expect(1, open_start, "number")
expect(2, open_end, "number")
expect(3, token, "number")
expect(4, start_pos, "number")
expect(5, end_pos, "number")
return {
"Unexpected " .. token_names[token] .. ".",
annotate(open_start, open_end, "Label was started here."),
annotate(start_pos, end_pos, "Tip: Try adding " .. code("::") .. " here."),
}
end
--------------------------------------------------------------------------------
-- Generic parsing errors
--------------------------------------------------------------------------------

View File

@ -32,12 +32,12 @@ local tokens = require "cc.internal.syntax.parser".tokens
local sub, find = string.sub, string.find
local keywords = {
["and"] = tokens.AND, ["break"] = tokens.BREAK, ["do"] = tokens.DO, ["else"] = tokens.ELSE,
["elseif"] = tokens.ELSEIF, ["end"] = tokens.END, ["false"] = tokens.FALSE, ["for"] = tokens.FOR,
["function"] = tokens.FUNCTION, ["if"] = tokens.IF, ["in"] = tokens.IN, ["local"] = tokens.LOCAL,
["nil"] = tokens.NIL, ["not"] = tokens.NOT, ["or"] = tokens.OR, ["repeat"] = tokens.REPEAT,
["return"] = tokens.RETURN, ["then"] = tokens.THEN, ["true"] = tokens.TRUE, ["until"] = tokens.UNTIL,
["while"] = tokens.WHILE,
["and"] = tokens.AND, ["break"] = tokens.BREAK, ["do"] = tokens.DO, ["else"] = tokens.ELSE,
["elseif"] = tokens.ELSEIF, ["end"] = tokens.END, ["false"] = tokens.FALSE, ["for"] = tokens.FOR,
["function"] = tokens.FUNCTION, ["goto"] = tokens.GOTO, ["if"] = tokens.IF, ["in"] = tokens.IN,
["local"] = tokens.LOCAL, ["nil"] = tokens.NIL, ["not"] = tokens.NOT, ["or"] = tokens.OR,
["repeat"] = tokens.REPEAT, ["return"] = tokens.RETURN, ["then"] = tokens.THEN, ["true"] = tokens.TRUE,
["until"] = tokens.UNTIL, ["while"] = tokens.WHILE,
}
--- Lex a newline character
@ -292,12 +292,15 @@ local function lex_token(context, str, pos)
local next_pos = pos + 1
if sub(str, next_pos, next_pos) == "=" then return tokens.LE, next_pos end
return tokens.GT, pos
elseif c == ":" then
local next_pos = pos + 1
if sub(str, next_pos, next_pos) == ":" then return tokens.DOUBLE_COLON, next_pos end
return tokens.COLON, pos
elseif c == "~" and sub(str, pos + 1, pos + 1) == "=" then return tokens.NE, pos + 1
-- Single character tokens
elseif c == "," then return tokens.COMMA, pos
elseif c == ";" then return tokens.SEMICOLON, pos
elseif c == ":" then return tokens.COLON, pos
elseif c == "(" then return tokens.OPAREN, pos
elseif c == ")" then return tokens.CPAREN, pos
elseif c == "]" then return tokens.CSQUARE, pos

View File

@ -10,6 +10,8 @@
import it.unimi.dsi.fastutil.ints.IntSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.squiddev.cobalt.LuaError;
import org.squiddev.cobalt.LuaState;
import org.squiddev.cobalt.Prototype;
import org.squiddev.cobalt.compiler.CompileException;
import org.squiddev.cobalt.compiler.LuaC;
@ -108,9 +110,9 @@ private static IntSet getActiveLines(File file) throws IOException {
Queue<Prototype> queue = new ArrayDeque<>();
try (InputStream stream = new FileInputStream(file)) {
var proto = LuaC.compile(stream, "@" + file.getPath());
var proto = LuaC.compile(new LuaState(), stream, "@" + file.getPath());
queue.add(proto);
} catch (CompileException e) {
} catch (LuaError | CompileException e) {
throw new IllegalStateException("Cannot compile", e);
}

View File

@ -7,6 +7,7 @@
import dan200.computercraft.api.lua.LuaException;
import org.junit.jupiter.api.Test;
import org.squiddev.cobalt.Constants;
import org.squiddev.cobalt.LuaError;
import org.squiddev.cobalt.LuaTable;
import org.squiddev.cobalt.ValueFactory;
@ -18,7 +19,11 @@
class VarargArgumentsTest {
private static LuaTable tableWithCustomType() {
var metatable = new LuaTable();
metatable.rawset(Constants.NAME, ValueFactory.valueOf("some type"));
try {
metatable.rawset(Constants.NAME, ValueFactory.valueOf("some type"));
} catch (LuaError e) {
throw new IllegalStateException("Cannot create metatable", e);
}
var table = new LuaTable();
table.setMetatable(null, metatable);

View File

@ -188,6 +188,9 @@ local function format(value)
-- TODO: Look into something like mbs's pretty printer.
if type(value) == "string" and value:find("\n") then
return "<<<\n" .. value .. "\n>>>"
elseif type(value) == "string" then
local escaped = value:gsub("[^%g ]", function(x) return ("\\x%02x"):format(x:byte()) end)
return "\"" .. escaped .. "\""
else
local ok, res = pcall(textutils.serialise, value)
if ok then return res else return tostring(value) end

View File

@ -79,20 +79,6 @@ describe("The Lua base library", function()
end)
describe("load", function()
it("validates arguments", function()
load("")
load(function()
end)
load("", "")
load("", "", "")
load("", "", "", _ENV)
expect.error(load, nil):eq("bad argument #1 (function or string expected, got nil)")
expect.error(load, "", false):eq("bad argument #2 (string expected, got boolean)")
expect.error(load, "", "", false):eq("bad argument #3 (string expected, got boolean)")
expect.error(load, "", "", "", false):eq("bad argument #4 (table expected, got boolean)")
end)
local function generator(parts)
return coroutine.wrap(function()
for i = 1, #parts do

View File

@ -8,32 +8,6 @@
generate for each one. This is _not_ a complete collection of all possible
errors, but is a useful guide for where we might be providing terrible messages.
```lua
break while
-- Line 1: unexpected symbol near <eof> (program)
```
```txt
Unexpected while. Expected a statement.
|
1 | break while
| ^^^^^
```
```lua
do end true
-- Line 1: unexpected symbol near 'true' (program)
```
```txt
Unexpected true. Expected a statement.
|
1 | do end true
| ^^^^
```
```lua
do until
-- Line 1: 'end' expected near 'until' (program)
@ -63,6 +37,35 @@
```
```lua
:: xyz while
-- Line 1: '::' expected near 'while' (program)
```
```txt
Unexpected while.
|
1 | :: xyz while
| ^^ Label was started here.
|
1 | :: xyz while
| ^^^^^ Tip: Try adding :: here.
```
```lua
:: while
-- Line 1: <name> expected near 'while' (program)
```
```txt
Unexpected while.
|
1 | :: while
| ^^^^^
```
```lua
for xyz , xyz while
-- Line 1: 'in' expected near 'while' (program)
@ -849,6 +852,20 @@
```
```lua
xyz while
-- Line 1: syntax error near 'while' (program)
```
```txt
Unexpected symbol after name.
|
1 | xyz while
| ^
Did you mean to assign this or call it as a function?
```
```lua
if xyz then else until
-- Line 1: 'end' expected near 'until' (program)
@ -1059,22 +1076,6 @@ # while
```
```lua {repl_exprs}
{ xyz , while
-- Line 1: unexpected symbol near 'while' (repl_exprs)
```
```txt
Unexpected while. Are you missing a closing bracket?
|
1 | { xyz , while
| ^ Brackets were opened here.
|
1 | { xyz , while
| ^^^^^ Unexpected while here.
```
```lua {repl_exprs}
{ xyz = xyz while
-- Line 1: '}' expected near 'while' (repl_exprs)
@ -1104,6 +1105,22 @@ # while
```
```lua {repl_exprs}
{ xyz ; while
-- Line 1: unexpected symbol near 'while' (repl_exprs)
```
```txt
Unexpected while. Are you missing a closing bracket?
|
1 | { xyz ; while
| ^ Brackets were opened here.
|
1 | { xyz ; while
| ^^^^^ Unexpected while here.
```
```lua {repl_exprs}
{ xyz while
-- Line 1: '}' expected near 'while' (repl_exprs)

View File

@ -417,6 +417,49 @@ ## Unexpected `end`
Your program contains more ends than needed. Check each block (if, for, function, ...) only has one end.
```
## `goto` and labels
We `goto` the same as normal identifiers.
```lua
goto 2
```
```txt
Unexpected symbol after name.
|
1 | goto 2
| ^
Did you mean to assign this or call it as a function?
```
Labels have a basic closing check:
```lua
::foo
```
```txt
Unexpected end of file.
|
1 | ::foo
| ^^ Label was started here.
|
1 | ::foo
| ^ Tip: Try adding :: here.
```
But we do nothing fancy for just a `::`
```lua
::
```
```txt
Unexpected end of file.
|
1 | ::
| ^
```
# Function calls
## Additional commas