We now suggest alternative table keys when code errors with "attempt
to index/call 'foo' (a nil value)". For example: "redstone.getinput()",
will now suggest "Did you mean: getInput".
This is a bit tricky to get right! In the above example, our code reads
like:
1 GETTABUP 0 0 0 ; r0 := _ENV["redstone"]
2 GETFIELD 0 0 1 ; r0 := r0["getinput"]
3 CALL 0 1 1 ; r0()
Note, that when we get to the problematic line, we don't have access to
the original table that we attempted to index. In order to do this, we
borrow ideas from Lua's getobjname — we effectively write an evaluator
that walks back over the code and tries to reconstruct the expression
that resulted in nil.
For example, in the above case:
- We know an instruction happened at pc=3, so we try to find the
expression that computed r0.
- We know this was set at pc=2, so we step back one. This is a GETFIELD
instruction, so we check the key (it's a constant, so worth
reporting), and then try to evaluate the table.
- This version of r0 was set at pc=1, so we step back again. It's a
GETTABUP instruction, so we can just evaluate that directly.
We then use this information (indexing _ENV.redstone with "getinput") to
find alternative keys (e.g. getInput, getOutput, etc...) and then pick
some likely suggestions with Damerau-Levenshtein/OSD.
I'm not entirely thrilled by the implementation here. The core
interpretation logic is implemented in Java. Which is *fine*, but a)
feels a little cheaty and b) means we're limited to what Lua bytecode
can provide (for instance, we can't inspect outer functions, or list all
available names in scope). We obviously can expand the bytecode if
needed, but something we'd want to be careful with.
The alternative approach would be to handle all the parsing in
Lua. Unfortunately, this is quite hard to get right — I think we'd need
some lazy parsing strategy to avoid constructing the whole AST, while
still retaining all the scope information we need.
I don't know. We really could make this as complex as we like, and I
don't know what the right balance is. It'd be cool to detect patterns
like the following, but is it *useful*?
local monitor = peripheral.wrap("left")
monitor.write("Hello")
-- ^ monitor is nil. Is there a peripheral to the left of the
-- computer?
For now, the current approach feels the easiest, and should allow us to
prototype things and see what does/doesn't work.
In the original implementation of our prettier runtime errors (#1320), we
wrapped the errors thrown within parallel functions into an exception
object. This means the call-stack is available to the catching-code, and
so is able to report a pretty exception message.
Unfortunately, this was a breaking change, and so we had to roll that
back. Some people were pcalling the parallel function, and matching on
the result of the error.
This is a second attempt at this, using a technique I've affectionately
dubbed "magic throws". The parallel API is now aware of whether it is
being pcalled or not, and thus able to decide whether to wrap the error
into an exception or not:
- Add a new `cc.internal.tiny_require` module. This is a tiny
reimplementation of require, for use in our global APIs.
- Add a new (global, in the debug registry) `cc_try_barrier` function.
This acts as a marker function, and is used to store additional
information about the current coroutine.
Currently this stores the parent coroutine (used to walk the full call
stack) and a cache of whether any `pcall`-like function is on the
stack.
Both `parallel` and `cc.internal.exception.try` add this function to
the root of the call stack.
- When an error occurs within `parallel`, we walk up the call stack,
using `cc_try_barrier` to traverse up the parent coroutine's stack
too. If we do not find any `pcall`-like functions, then we know the
error is never intercepted by user code, and so its safe to throw a
full exception.
In 94ad6dab0e5b8d9eb65467dd1b635d708ce58b53, we changed it so typing
characters outside of CC's codepage were replaced with '?' rather than
ignored. This can be quite annoying for non-European users (where latin1
isn't very helpful!), so it makes sense to revert this change.
See discussion in #860 for more context.
- Store the filter alongside the coroutine rather than in a separate
table (like we do in multishell).
- Remove the redudant (I think!) second loop that checks for dead
coroutines. We already check for dead coroutines in the main loop.
- Rename some variables to be a bit more consistent. This makes this
commit look noisier than it is. Sorry!
- Check whether the computer is a command computer before registering
the capability.
- Add tests to check what is/isn't a peripheral. See also #2020, where
we forgot to register a peripheral on NeoForge 1.21.1.
Fixes#2070.
We now convert uncode characters from "char" and "paste" events to CC's
charset[^1], rather than just leaving them unconverted. This means you
can paste in special characters like "♠" or "🮙" and they will be
converted correctly. Characters outside that range will be replaced with
"?", as before.
It would be nice to make this a bi-directional mapping, and do this for
Lua methods too (e.g. os.setComputerLabel). However, that has much wider
ramifications (and more likelyhood of breaking something), so avoiding
that for now.
- Remove the generic "queue event" client->server message, and replace
it with separate char/terminate/paste messages. This allows us to
delete a chunk of code (all the NBT<->Object conversion), and makes
server-side validation of events possible.
- Fix os.setComputerLabel accepting the section sign — this is treated
as special by Minecraft's formatting code. Sorry, no fun allowed.
- Convert paste/char codepoints to CC's charset. Sadly MC's char hook
splits the codepoint into surrogate pairs, which we *don't* attempt
to reconstruct, so you can't currently use unicode input for block
characters — you can paste them though!
[^1]: I'm referring this to the "terminal charset" within the code. I've
flip-flopped between "CraftOS", "terminal", "ComputerCraft", but feel
especially great.
This abstraction never made much sense on InputHandler, as we only leave
the default methods on ServerComputer.
We now add a new class (ComputerEvents), which has a series of *static*
methods, that can queue an event on a ComputerEvents.Receiver object.
This is a bit of an odd indirection (why not just make them instance
methods on Receiver?!), but I don't really want those methods leaking
everywhere.
As part of this, we also rewrite some of the turtle placing code, and
how it uses the turtle_can_use tag:
Minecraft 1.21 cleaned up the item/block clicking code a little bit,
splitting Block.use into Block.useItemOn and Block.useWithoutItem. The
first of these is pretty much exactly what we wanted in the first place,
so the tag was kinda redundant and we commented it out in the 1.21
update.
This was never meant to be a long-term fix, but time has gone by anyway.
We now check that tag, and call useWithoutItem() if present —
effectively restoring the previous behaviour.
Fixes#2011
Okay, listen. I started writing a few more gametests (see #1682), and
then thought I'd do a cheeky Gradle update. However, that broke
vanilla-extract[^1], and also triggered a load of deprecation warnings,
and at that point it was too late to separate the too.
[^1]: 8975ed5a7b
I've no motivation for modding right now, but always got time for build
system busywork!
CC:T (and CC before that) has always published its API docs. However,
they're not always the most helpful — they're useful if you know what
you're looking for, but aren't a good getting-started guide.
Part of the issue here is there's no examples, and everything is
described pretty abstractly. I have occasionally tried to improve this
(e.g. the peripheral docs in bdffabc08e2eb9895f966c949acc8334a2bf4475),
but it's a long road.
This commit adds a new example mod, which registers peripherals, an API
and a turtle upgrade. While the mod itself isn't exported as part of the
docs, we reference blocks of it using Java's new {@snippet} tag.
- Switch the Forge project to use NeoForge's new Legacy MDG plugin. We
don't *need* to do this, but it means the build logic for Forge and
NeoForge is more closely aligned.
- Add a new SnippetTaglet, which is a partial backport of Java 18+'s
{@snippet}.
- Add an example mod. This is a working multi-loader mod, complete with
datagen (albeit with no good multi-loader abstractions).
- Move our existing <pre>{@code ...}</pre> blocks into the example mod,
replacing them with {@snippet}s.
- Add a new overview page to the docs, providing some getting-started
information. We had this already in the dan200.computercraft.api
package docs, but it's not especially visible there.
fs.getDrive returns nil for missing files, rather than the mount of the
parent file. This is a bit confusing — other mount-related functions
(e.g. getFreeSpace) find the nearest mount — but I think it's too late
to change this. Instead, we check if the file exists first.
- Move redstone methods out of the IAPIEnvironment, and into a new
RedstoneAccess. We similarly move the implementation from Environment
into a new RedstoneState class.
The interface is possibly a little redundant (interfaces with a
single implementation are always a little suspect), but it's nice to
keep the consumer/producer interfaces separate.
- Abstract most redstone API methods into a separate shared class, that
can be used by both the rs API and the new redstone relay.
- Add the new redstone relay block.
The docs are probably a little lacking here, but I really struggled to
write anything which wasn't just "look, it's the same as the redstone
API".
Several functions accept a "timeout" argument, which is implemented by
starting a timer, and then racing the desired output against the timer
event.
However, if the timer never wins, we weren't cancelling the timer, and
so it was still queued. This is especially problematic if dozens or
hundreds of rednet (or websocket) messages are received in quick
succession, as we could fill the entire event queue, and stall the
computer.
See #1995
Under Forge, netty-codec lives on the BOOT layer. However, this means it
does not have access to our jzlib (which lives on the GAME layer). To
fix this, we now shadow netty-codec (and its dependents, like netty-http
and netty-proxy) rather than jar-in-jaring them.
This involves some horrible build logic, but means websocket compression
works on Forge.
Fixes#1958.
Previously we used an RGBA byte array. However, this comes with some
overhead (extra memory reads, bounds checks).
Minecraft 1.21+ uses ARGB32 colours for rendering (well, in the public
code — internaly it converts to ABGR), so it makes sense to match that
here.
We also add some helper functions for dealing with ARGB32 colours. These
can be removed in 1.21, as Minecraft will have these builtin.
This probably isn't useful in practice — nobody is escaping 1MB of data.
Right. Right???? But no harm in doing it.
- Cache globals as locals.
- Remove redundant pattern capture.
- Merge string.format calls into one.
Also remove the "if str then" check. I assume we accepted nil values a
long time ago, but that was broken when we added arg checks. Woops!
In cdcd82679c8e286738afd6f19d1d9ed845dcd2aa, we rewrote the Lua
conversion function to update the "Java -> Lua" mapping after
conversion, rather than part way through.
This made the code a little cleaner (as we only updated the mapping in
one place), but is entirely incorrect — we need to store the object
first, in order to correctly handle recursive objects — otherwise we'll
just recurse infinitely (or until we overflow).
This partially reverts the above commit, while preserving the new
behaviour for singleton collections.
Fixes#1955.
Even more of these! I really need to stop getting bored half way through
writing sentences and then continue writing at the wrong place. Or,
y'know, get better at proof reading.
It's not entirely clear what the correct behaviour of fs.getDir("..")
should be, and there's not much consensus between various languages.
I think the intended behaviour of this function is to move "up" one
directory level in the path, meaning it should return "../..".