1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-27 11:57:38 +00:00

Merge branch 'mc-1.20.x' into mc-1.21.x

This commit is contained in:
Jonathan Coates
2024-08-19 20:54:03 +01:00
76 changed files with 1609 additions and 245 deletions

View File

@@ -101,7 +101,7 @@ public class FSAPI implements ILuaAPI {
* }</pre>
*/
@LuaFunction
public final String[] list(String path) throws LuaException {
public final List<String> list(String path) throws LuaException {
try (var ignored = environment.time(Metrics.FS_OPS)) {
return getFileSystem().list(path);
} catch (FileSystemException e) {

View File

@@ -122,7 +122,7 @@ public class OSAPI implements ILuaAPI {
}
private static long getEpochForCalendar(Calendar c) {
return c.getTime().getTime();
return c.getTimeInMillis();
}
/**
@@ -298,7 +298,7 @@ public class OSAPI implements ILuaAPI {
* textutils.formatTime(os.time())
* }</pre>
* @cc.since 1.2
* @cc.changed 1.80pr1 Add support for getting the local local and UTC time.
* @cc.changed 1.80pr1 Add support for getting the local and UTC time.
* @cc.changed 1.82.0 Arguments are now case insensitive.
* @cc.changed 1.83.0 {@link #time(IArguments)} now accepts table arguments and converts them to UNIX timestamps.
* @see #date To get a date table that can be converted with this function.

View File

@@ -177,9 +177,9 @@ public abstract class AbstractHandle {
/**
* Read the remainder of the file.
*
* @return The file, or {@code null} if at the end of it.
* @return The remaining contents of the file, or {@code null} in the event of an error.
* @throws LuaException If the file has been closed.
* @cc.treturn string|nil The remaining contents of the file, or {@code nil} if we are at the end.
* @cc.treturn string|nil The remaining contents of the file, or {@code nil} in the event of an error.
* @cc.since 1.80pr1
*/
@Nullable

View File

@@ -57,7 +57,7 @@ interface AddressPredicate {
prefixSize = Integer.parseInt(prefixSizeStr);
} catch (NumberFormatException e) {
throw new InvalidRuleException(String.format(
"Invalid host host '%s': Cannot extract size of CIDR mask from '%s'.",
"Invalid host '%s': Cannot extract size of CIDR mask from '%s'.",
addressStr + '/' + prefixSizeStr, prefixSizeStr
));
}

View File

@@ -270,7 +270,7 @@ final class Generator<T> {
}
// Fold over the original method's arguments, excluding the target in reverse. For each argument, we reduce
// a method of type type (target, args..., arg_n, context..., IArguments) -> _ to (target, args..., context..., IArguments) -> _
// a method of type (target, args..., arg_n, context..., IArguments) -> _ to (target, args..., context..., IArguments) -> _
// until eventually we've flattened the whole list.
for (var i = parameterTypes.size() - 1; i >= 0; i--) {
handle = MethodHandles.foldArguments(handle, i + 1, argSelectors.get(i));

View File

@@ -122,6 +122,10 @@ public class FileSystem {
}
var lastSlash = path.lastIndexOf('/');
// If the trailing segment is a "..", then just append another one.
if (path.substring(lastSlash < 0 ? 0 : lastSlash + 1).equals("..")) return path + "/..";
if (lastSlash >= 0) {
return path.substring(0, lastSlash);
} else {
@@ -145,7 +149,7 @@ public class FileSystem {
return getMount(sanitizePath(path)).getAttributes(sanitizePath(path));
}
public synchronized String[] list(String path) throws FileSystemException {
public synchronized List<String> list(String path) throws FileSystemException {
path = sanitizePath(path);
var mount = getMount(path);
@@ -161,10 +165,8 @@ public class FileSystem {
}
// Return list
var array = new String[list.size()];
list.toArray(array);
Arrays.sort(array);
return array;
list.sort(Comparator.naturalOrder());
return list;
}
public synchronized boolean exists(String path) throws FileSystemException {

View File

@@ -13,6 +13,7 @@ import dan200.computercraft.core.Logging;
import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.methods.LuaMethod;
import dan200.computercraft.core.methods.MethodSupplier;
import dan200.computercraft.core.util.LuaUtil;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.core.util.SanitisedError;
import org.slf4j.Logger;
@@ -183,10 +184,35 @@ public class CobaltLuaMachine implements ILuaMachine {
return ValueFactory.valueOf(bytes);
}
// Don't share singleton values, and instead convert them to a new table.
if (LuaUtil.isSingletonCollection(object)) return new LuaTable();
if (values == null) values = new IdentityHashMap<>(1);
var result = values.get(object);
if (result != null) return result;
var wrapped = toValueWorker(object, values);
if (wrapped == null) {
LOG.warn(Logging.JAVA_ERROR, "Received unknown type '{}', returning nil.", object.getClass().getName());
return Constants.NIL;
}
values.put(object, wrapped);
return wrapped;
}
/**
* Convert a complex Java object (such as a collection or Lua object) to a Lua value.
* <p>
* This is a worker function for {@link #toValue(Object, IdentityHashMap)}, which handles the actual construction
* of values, without reading/writing from the value map.
*
* @param object The object to convert.
* @param values The map of Java to Lua values.
* @return The converted value, or {@code null} if it could not be converted.
* @throws LuaError If the value could not be converted.
*/
private @Nullable LuaValue toValueWorker(Object object, IdentityHashMap<Object, LuaValue> values) throws LuaError {
if (object instanceof ILuaFunction) {
return new ResultInterpreterFunction(this, FUNCTION_METHOD, object, context, object.toString());
}
@@ -194,15 +220,12 @@ public class CobaltLuaMachine implements ILuaMachine {
if (object instanceof IDynamicLuaObject) {
LuaValue wrapped = wrapLuaObject(object);
if (wrapped == null) wrapped = new LuaTable();
values.put(object, wrapped);
return wrapped;
}
if (object instanceof Map<?, ?> map) {
var table = new LuaTable();
values.put(object, table);
for (Map.Entry<?, ?> pair : map.entrySet()) {
for (var pair : map.entrySet()) {
var key = toValue(pair.getKey(), values);
var value = toValue(pair.getValue(), values);
if (!key.isNil() && !value.isNil()) table.rawset(key, value);
@@ -212,27 +235,18 @@ public class CobaltLuaMachine implements ILuaMachine {
if (object instanceof Collection<?> objects) {
var table = new LuaTable(objects.size(), 0);
values.put(object, table);
var i = 0;
for (Object child : objects) table.rawset(++i, toValue(child, values));
for (var child : objects) table.rawset(++i, toValue(child, values));
return table;
}
if (object instanceof Object[] objects) {
var table = new LuaTable(objects.length, 0);
values.put(object, table);
for (var i = 0; i < objects.length; i++) table.rawset(i + 1, toValue(objects[i], values));
return table;
}
var wrapped = wrapLuaObject(object);
if (wrapped != null) {
values.put(object, wrapped);
return wrapped;
}
LOG.warn(Logging.JAVA_ERROR, "Received unknown type '{}', returning nil.", object.getClass().getName());
return Constants.NIL;
return wrapLuaObject(object);
}
Varargs toValues(@Nullable Object[] objects) throws LuaError {

View File

@@ -4,9 +4,18 @@
package dan200.computercraft.core.util;
import dan200.computercraft.core.lua.ILuaMachine;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class LuaUtil {
private static final List<?> EMPTY_LIST = List.of();
private static final Set<?> EMPTY_SET = Set.of();
private static final Map<?, ?> EMPTY_MAP = Map.of();
public static Object[] consArray(Object value, Collection<?> rest) {
if (rest.isEmpty()) return new Object[]{ value };
@@ -14,7 +23,20 @@ public class LuaUtil {
var out = new Object[rest.size() + 1];
out[0] = value;
var i = 1;
for (Object additionalType : rest) out[i++] = additionalType;
for (var additionalType : rest) out[i++] = additionalType;
return out;
}
/**
* Determine whether a value is a singleton collection, such as one created with {@link List#of()}.
* <p>
* These collections are treated specially by {@link ILuaMachine} implementations: we skip sharing for them, and
* create a new table each time.
*
* @param value The value to test.
* @return Whether this is a singleton collection.
*/
public static boolean isSingletonCollection(Object value) {
return value == EMPTY_LIST || value == EMPTY_SET || value == EMPTY_MAP;
}
}

View File

@@ -851,13 +851,32 @@ unserialise = unserialize -- GB version
--[[- Returns a JSON representation of the given data.
This function attempts to guess whether a table is a JSON array or
object. However, empty tables are assumed to be empty objects - use
[`textutils.empty_json_array`] to mark an empty array.
This is largely intended for interacting with various functions from the
[`commands`] API, though may also be used in making [`http`] requests.
Lua has a rather different data model to Javascript/JSON. As a result, some Lua
values do not serialise cleanly into JSON.
- Lua tables can contain arbitrary key-value pairs, but JSON only accepts arrays,
and objects (which require a string key). When serialising a table, if it only
has numeric keys, then it will be treated as an array. Otherwise, the table will
be serialised to an object using the string keys. Non-string keys (such as numbers
or tables) will be dropped.
A consequence of this is that an empty table will always be serialised to an object,
not an array. [`textutils.empty_json_array`] may be used to express an empty array.
- Lua strings are an a sequence of raw bytes, and do not have any specific encoding.
However, JSON strings must be valid unicode. By default, non-ASCII characters in a
string are serialised to their unicode code point (for instance, `"\xfe"` is
converted to `"\u00fe"`). The `unicode_strings` option may be set to treat all input
strings as UTF-8.
- Lua does not distinguish between missing keys (`undefined` in JS) and ones explicitly
set to `null`. As a result `{ x = nil }` is serialised to `{}`. [`textutils.json_null`]
may be used to get an explicit null value (`{ x = textutils.json_null }` will serialise
to `{"x": null}`).
@param[1] t The value to serialise. Like [`textutils.serialise`], this should not
contain recursive tables or functions.
@tparam[1,opt] {

View File

@@ -114,7 +114,7 @@ local vector = {
--
-- @tparam Vector self The first vector to compute the dot product of.
-- @tparam Vector o The second vector to compute the dot product of.
-- @treturn Vector The dot product of `self` and `o`.
-- @treturn number The dot product of `self` and `o`.
-- @usage v1:dot(v2)
dot = function(self, o)
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end

View File

@@ -1,3 +1,15 @@
# New features in CC: Tweaked 1.113.0
* Allow placing printed pages and books in lecterns.
Several bug fixes:
* Various documentation fixes (MCJack123)
* Fix computers and turtles not being dropped when exploded with TNT.
* Fix crash when turtles are broken while mining a block.
* Fix pocket computer terminals not updating when in the off-hand.
* Fix disk drives not being exposed as a peripheral.
* Fix item details being non-serialisable due to duplicated tables.
# New features in CC: Tweaked 1.112.0
* Report a custom error when using `!` instead of `not`.
@@ -811,7 +823,7 @@ And several bug fixes:
# New features in CC: Tweaked 1.86.2
* Fix peripheral.getMethods returning an empty table.
* Update to Minecraft 1.15.2. This is currently alpha-quality and so is missing missing features and may be unstable.
* Update to Minecraft 1.15.2. This is currently alpha-quality and so is missing features and may be unstable.
# New features in CC: Tweaked 1.86.1
@@ -1427,7 +1439,7 @@ And several bug fixes:
* Turtles can now compare items in their inventories
* Turtles can place signs with text on them with `turtle.place( [signText] )`
* Turtles now optionally require fuel items to move, and can refuel themselves
* The size of the the turtle inventory has been increased to 16
* The size of the turtle inventory has been increased to 16
* The size of the turtle screen has been increased
* New turtle functions: `turtle.compareTo( [slotNum] )`, `turtle.craft()`, `turtle.attack()`, `turtle.attackUp()`, `turtle.attackDown()`, `turtle.dropUp()`, `turtle.dropDown()`, `turtle.getFuelLevel()`, `turtle.refuel()`
* New disk function: disk.getID()

View File

@@ -1,14 +1,13 @@
New features in CC: Tweaked 1.112.0
New features in CC: Tweaked 1.113.0
* Report a custom error when using `!` instead of `not`.
* Update several translations (zyxkad, MineKID-LP).
* Add `cc.strings.split` function.
* Allow placing printed pages and books in lecterns.
Several bug fixes:
* Fix `drive.getAudioTitle` returning `nil` when no disk is inserted.
* Preserve item data when upgrading pocket computers.
* Add missing bounds check to `cc.strings.wrap` (Lupus950).
* Fix dyed turtles rendering transparent.
* Fix dupe bug when crafting with turtles.
* Various documentation fixes (MCJack123)
* Fix computers and turtles not being dropped when exploded with TNT.
* Fix crash when turtles are broken while mining a block.
* Fix pocket computer terminals not updating when in the off-hand.
* Fix disk drives not being exposed as a peripheral.
* Fix item details being non-serialisable due to duplicated tables.
Type "help changelog" to see the full version history.

View File

@@ -13,7 +13,7 @@ Typically DFPWM audio is read from [the filesystem][`fs.ReadHandle`] or a [a web
and converted a format suitable for [`speaker.playAudio`].
## Encoding and decoding files
This modules exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
This module exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
The returned encoder/decoder is itself a function, which converts between the two kinds of data.
These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for
@@ -21,9 +21,9 @@ a specific audio stream. Typically you will want to create a decoder for each st
for each one you write.
## Converting audio to DFPWM
DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it.
DFPWM is not a popular file format and so standard audio processing tools may not have an option to export to it.
Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java
application or development builds of [FFmpeg].
application or [FFmpeg] 5.1 or later.
[music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked"
[LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter "
@@ -211,7 +211,7 @@ end
--[[- A convenience function for encoding a complete file of audio at once.
This should only be used for complete pieces of audio. If you are writing writing multiple chunks to the same place,
This should only be used for complete pieces of audio. If you are writing multiple chunks to the same place,
you should use an encoder returned by [`make_encoder`] instead.
@tparam { number... } input The table of amplitude data.

View File

@@ -5,11 +5,14 @@
package dan200.computercraft.core.computer;
import com.google.common.io.CharStreams;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaFunction;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import static java.time.Duration.ofSeconds;
@@ -30,6 +33,26 @@ public class ComputerTest {
});
}
@Test
public void testDuplicateObjects() {
class CustomApi implements ILuaAPI {
@Override
public String[] getNames() {
return new String[]{ "custom" };
}
@LuaFunction
public final Object[] getObjects() {
return new Object[]{ List.of(), List.of() };
}
}
ComputerBootstrap.run("""
local x, y = custom.getObjects()
assert(x ~= y)
""", i -> i.addApi(new CustomApi()), 50);
}
public static void main(String[] args) throws Exception {
var stream = ComputerTest.class.getClassLoader().getResourceAsStream("benchmark.lua");
try (var reader = new InputStreamReader(Objects.requireNonNull(stream), StandardCharsets.UTF_8)) {

View File

@@ -158,6 +158,65 @@ describe("The fs library", function()
expect(fs.combine("", "a")):eq("a")
expect(fs.combine("a", "", "b", "c")):eq("a/b/c")
end)
it("preserves pattern characters", function()
expect(fs.combine("foo*?")):eq("foo*?")
end)
it("sanitises paths", function()
expect(fs.combine("foo\":<>|")):eq("foo")
end)
end)
describe("fs.getName", function()
it("returns 'root' for the empty path", function()
expect(fs.getName("")):eq("root")
expect(fs.getName("foo/..")):eq("root")
end)
it("returns the file name", function()
expect(fs.getName("foo/bar")):eq("bar")
expect(fs.getName("foo/bar/")):eq("bar")
expect(fs.getName("../foo")):eq("foo")
end)
it("returns '..' for parent directories", function()
expect(fs.getName("..")):eq("..")
end)
it("preserves pattern characters", function()
expect(fs.getName("foo*?")):eq("foo*?")
end)
it("sanitises paths", function()
expect(fs.getName("foo\":<>|")):eq("foo")
end)
end)
describe("fs.getDir", function()
it("returns '..' for the empty path", function()
expect(fs.getDir("")):eq("..")
expect(fs.getDir("foo/..")):eq("..")
end)
it("returns the directory name", function()
expect(fs.getDir("foo/bar")):eq("foo")
expect(fs.getDir("foo/bar/")):eq("foo")
expect(fs.getDir("../foo")):eq("..")
end)
it("returns '..' for parent directories", function()
expect(fs.getDir("..")):eq("../..")
expect(fs.getDir("../..")):eq("../../..")
end)
it("preserves pattern characters", function()
expect(fs.getDir("foo*?/x")):eq("foo*?")
end)
it("sanitises paths", function()
expect(fs.getDir("foo\":<>|/x")):eq("foo")
end)
end)
describe("fs.getSize", function()
@@ -200,6 +259,14 @@ describe("The fs library", function()
handle.close()
end)
it("reading an empty file returns nil", function()
local file = create_test_file ""
local handle = fs.open(file, mode)
expect(handle.read()):eq(nil)
handle.close()
end)
it("can read a line of text", function()
local file = create_test_file "some\nfile\r\ncontents\n\n"
@@ -223,6 +290,16 @@ describe("The fs library", function()
expect(handle.readLine(true)):eq(nil)
handle.close()
end)
it("readAll always returns a string", function()
local contents = "some\nfile\ncontents"
local file = create_test_file "some\nfile\ncontents"
local handle = fs.open(file, mode)
expect(handle.readAll()):eq(contents)
expect(handle.readAll()):eq("")
handle.close()
end)
end
describe("reading", function()

View File

@@ -188,4 +188,31 @@ describe("The os library", function()
expect.error(os.loadAPI, nil):eq("bad argument #1 (string expected, got nil)")
end)
end)
describe("os.queueEvent", function()
local function roundtrip(...)
local event_name = ("event_%08x"):format(math.random(1, 0x7FFFFFFF))
os.queueEvent(event_name, ...)
return select(2, os.pullEvent(event_name))
end
it("preserves references in tables", function()
local tbl = {}
local xs = roundtrip({ tbl, tbl })
expect(xs[1]):eq(xs[2])
end)
it("does not preserve references in separate args", function()
-- I'm not sure I like this behaviour, but it is what CC has always done.
local tbl = {}
local xs, ys = roundtrip(tbl, tbl)
expect(xs):ne(ys)
end)
it("clones objects", function()
local tbl = {}
local xs = roundtrip(tbl)
expect(xs):ne(tbl)
end)
end)
end)

View File

@@ -13,13 +13,13 @@ correct tokens and positions, and that it can report sensible error messages.
We can lex some basic comments:
```lua
-- A basic singleline comment comment
-- A basic singleline comment
--[ Not a multiline comment
--[= Also not a multiline comment!
```
```txt
1:1-1:37 COMMENT -- A basic singleline comment comment
1:1-1:29 COMMENT -- A basic singleline comment
2:1-2:27 COMMENT --[ Not a multiline comment
3:1-3:34 COMMENT --[= Also not a multiline comment!
```