Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E2 Compiler Rewrite #2555

Merged
merged 140 commits into from
Oct 1, 2023
Merged

E2 Compiler Rewrite #2555

merged 140 commits into from
Oct 1, 2023

Conversation

Vurv78
Copy link
Member

@Vurv78 Vurv78 commented Apr 5, 2023

Fixes #2544
Fixes #2049
Fixes #2494
Fixes #2696
Fixes #2713 (by removing optimizer)
Fixes #2667

Overview of Changes

  • Rewrite of the parser
  • Rewrite of the compiler
  • Rewrite of the extension preprocessor
  • Tokenizer and Preprocessor now return multiple errors to the editor
  • Opcosts generally decreased
  • Removed wire_expression2_delta functionality
  • Removed the optimizer step (to be reimplemented later)

This is all to achieve a faster and simpler runtime.
For example, for (and foreach) loops run ~100x faster now.

Users

This shouldn't break any of your E2s.
Any broken E2s are caused by bugs and will be fixed.

The only user-facing change is that using j, i, k literals on their own is not accepted.
If you don't know what that is, you don't need to worry about it. Otherwise, see below.

Fixes

A long time issue with switch case has been fixed, which was related to how the old compiler worked. It used to evaluate the expression at the top (ie. a function call) for every single case. This no longer happens on the new compiler.

Complex and quaternion literals are no longer ambiguous. Before, i, j, and k were valid expressions that would equate to complex and quaternion literals. This is no longer allowed (you must prefix them with a 1 now, so 1j). This resolves an issue introduced by the tokenizer rewrite.

Extension Developers

registerFunction and registerOperator

These functions will remain the same by default, but will likely be deprecated in the future, since there is a new form of registering functions and operators with the runtime that is much faster.

If you want to use this new format, either use the e2function syntax, or set the legacy attribute (in the attributes parameter for the function) to false.

The differences between the two formats is that the old format would pass instructions not yet evaluated to the function/operator, while the new format passes the raw values. (If you don't know what this means, stick to e2function syntax).

Specific operator changes

is / operator_is

This no longer supports the legacy format as stated above. If you are using e2function syntax, you should be fine.
Otherwise, you should switch to using e2function syntax or look into the source code for examples.

and / or / operator& / operator|

These operators are no longer used. Instead the compiler uses the is operator for the type.

ass

This operator was the "assignment" operator. It has been removed, and assignment is now implemented in the compiler for all types automatically.

inc / dec

These operators are no longer being used.
Now incrementing and decrementing is syntax sugar for A = A + 1, so any type with + or - operators can be incremented/decremented.

fea

The fea (foreach) operator has been removed and replaced with iter. This expects the operation to return a lua iterator when given the value to "turn into an iterator".

This was done because the fea operator is not used by any extension, and requires the extension author to invoke the E2 chip's runtime itself, which should not be up to the extension author.

Example:

local function iterc(str, i)
	i = i + 1
	if i < #str then
		return i, string_sub(str, i, i)
	end
end

registerOperator("iter", "ns=s", "", function(state, str)
	state.prf = state.prf + #str
	return function()
		return iterc, str, 0
	end
end)

idx

This operator has been split into indexget and indexset respectively.

Example for angle:

registerOperator("indexget", "an", "n", function(state, this, index) -- getter
	return this[floor(math.Clamp(index, 1, 3) + 0.5)]
end)

registerOperator("indexset", "ann", "", function(state, this, index, value)
	this[floor(math.Clamp(index, 1, 3) + 0.5)] = value
	state.GlobalScope.vclk[this] = true
end)

Todos

  • String calls to variadic functions.
  • Incrementing (++) and decrementing (--) non-numeric values
  • A lot of testing.

* Removed ast.lua file (Largely useless)
* Removed optimizer.lua (Will be reimplemented with the new compiler as a step later.)
* Documented more parser changes
* Make tokenizer behave backwards-compatibly again
* Delete Compiler and Reimplement
* Removed syranide credit since E2 has been largely completely rewritten by me and wireteam/contributors

With just ~300 LOC the new compiler can already handle for loops, if statements and function calls. I just need to implement the rest of the functionality and make functions backward compatible.
* Reworked preprocessor to also return multiple errors, and to use the new trace/error system.

* Added multi-error support to the editor

* All compiler steps now return errors in the form of the "Error" struct. Even E2Lib.errorHandler now will return one.

* Made wire_expression2_reload also reload the compiler and E2Lib.

* Work on backwards compatibility for registerFunction, registerOperator, registerType, etc functions. These are being deprecated for a future better extension api that won't need any preprocessing.

* Optimization: Only check for continue and break when inside a loop

* Add more compiler instruction support (Try, Function, Arithmetic, etc). Still need to deal with handling variables aot to avoid runtime scoping.
* wire_expression2_validate -> E2Lib.Validate
* removed more useless operator internals

Passing test suite doesn't really mean anything because it only tests the parsing part really well. It isn't comprehensive considering all tests pass despite me not having implemented variable scoping at runtime nor index assignment.
* Reimplemented perf checks, probably not 1:1 but it works.

* Fixed lua error when the e2 editor was loading extensions

* Fix calling E2Lib.Compiler.Execute incorrectly in E2Lib.Validate

* Reimplemented variadic parameters
* If attributes not specified in `registerFunction`, automatically add and mark as legacy.

* Fix methodcall not properly taking arguments

* Make parser arguments function faster and more concise
* Moved ScopeManager into the e2lib
* Implement key value constructors for array and table
* Implement index get operator (no backwards compat)
* Reimplement index get operator for table and array, lower op cost (1 op for arrays, 3 for table from 5 for both.)
* Auto-mark registerOperator functions as legacy same as functions
* Add check for self.registered_events in destructor in case compilecode isn't called for whatever reason.
* Remove unused DefineLocal node variant
* Implement more helpful error message for (=) in expression.
* Reimplement includes

A lot closer to functional, still very far though.
* Inject trace into state every sequence

Sad runtime performance hit, maybe in the future there could be a system for fallibility so it only injects when doing something potentially fallible
* Implemented indexing assignment
* Implemented runtime scoping
* Partially-implemented events
* Rewrote array setters
* Fix assignment to another identifier
* Fix global variable initialization on E2 start (persist/io)
* Remove unused / redundant / now unsupported operators from extpp
* Make table setter/getters cost less ops

I need to reimplement all the warnings I added, and a check for variadic syntax sugar to ensure arrays aren't passed into array varargs.

Also there's a weird parsing issue with doing X = X + 1. (X = 1 + X works)
* Replace ScopeManager with RuntimeContext class (now shared with makeContext function

* Remove now redundant assignment operators. Need to come up with a solution for special table-like types, though.

* Removed and/or/neq/not operators, since they are redundant and can be done internally using the "is" operator.

* Fixed printing of arrays to not be garbage (Doesn't break on empty arrays, and much faster using table.concat)

* Removed "delta" functionality, it's slow, useless, and I have no idea why anyone would ever want this, and probably nobody will notice it was removed.

* Rewrote a good amount of operators from the preprocessor syntax to the new registerOperator arguments. All operators will have to be rewritten, functions will keep backwards compatibility since we can't expect extension developers to rewrite everything.

* Fixed parser issue when assigning an identifier

* Fixed parser issue when using an identifier in certain cases

There's still an issue with doing X = X + 1 (wtf)

* Make for loop and while loop iterations only cost 1 / 20 ops per iteration (base cost). This is just the cost of an empty block. It's added to the cost of what's in the actual block so it's fine to be this low. It also means now E2 can run an empty for loop to ~400k numbers at base quota.
* Reimplement support for legacy operators for certain operations

* Mark new numeric operators as nonlegacy

* Remove "delta" use from vector equal

* Delete unnecessary != operator from vector

* Remove unnecessary push and pop scopes on execute (Should start on scope 0 anyway.)

* Fix parse error when doing X = Y + 1
* Fixed hopefully final issue parsing assignments

* Change opcost of print functions (lower normal ones, raise driver/color ones)

* Port string operators to new api (and fix some functions that could be abused)

* Fixed inputs/outputs

* Mark ported angle operators as nonlegacy
* Fixed outputs not using correct wire_expression_types table
* Tidy include and preprocessor code
* Fix recursion typing
* Fix an issue from creating an error message concatenating a void returning function's return
* Disallow user functions overriding lua functions
* Disallow overriding user functions with functions that return differing types
* Fix "is" operator by marking as nonlegacy
* Fix string calls implementation (ew)
* Fix function recursion by reimplementing the old isolated scope system (ew)
* Fix error when assigning to void expression (ew)
* Only allow incrementing/decrementing number types
* Local declaration now has its proper special case so it isn't being treated as an assignment

* Increment/Decrement lookup a variable's scope rather than only looking in the current scope
Fixed issue where if you had a methodcall or stringcall after an index it'd fail to parse
* Fix wirelinks (Rewrite operators with new system)

* Remove nodiscard from runOnTick

* Rewrite string idx operator to new system

* Fix some incomplete parsing

* Don't allow void functions to be used as an expression (Separate CompileNode into CompileExpr and CompileStmt)
* Removed "fea" operator for new "iter" operator, which is much simpler and doesn't require the end user to manage executing the E2.

* Rewrote foreach operations to use "iter" operator.
* Fix and operation (was being called incorrectly)
* Remove all assignment operators
* Support mutable data type clks
* Remove all != operators (now redundant, uses == operator internally.)
* Port wirelink index setters getters to new system
Final tweaks on switch case working. This would also resolve wiremod#2049 since operators take the actual values as arguments rather than instructions.
Removed their operators (registerOperator) and are now implemented in the compiler.
Thank fuck

Also made a ton more branches for and/or operators for mixing with legacy operations.. awful..
* Fixed writing error message to wrong scope
* Fixed 2420 regression test erroring because of the e2lib emulation function pushing a scope before running the code (as E2 used to do)
When you had a function nested somewhere since it wasn't isolating the scope (like it does at runtime) it was indexing the wrong location for the function parameters.

Also made events do this
Was only using the scope field for the depth. Storing it in VarData lead to cyclic reference which couldn't be serialized in JSON, breaking dupes.
This fixes the case of compound operators alongside increment/decrement not working with types that had legacy operators.

Now these will simply act as syntax sugar for assignment and use the operators internally, now that I've removed the compound assignment operators.

It simplifies the compiler implementation of these significantly and deduplicates a lot of code regarding variable usage
Enforce that no operator is registered named "is" that returns a type other than "n", and remove redundant runtime enforcement of this in Not operator compilation
Trim string before gsub, and remove extra argument passed to parseParameters
@Vurv78
Copy link
Member Author

Vurv78 commented Sep 11, 2023

Now user functions highlight and autocomplete, even across included files:

image

They were cutting off one char/byte early
Adds explicit error typing to the error variable in try catch, with a warning if not present.

Added highlighting and test case for this.
Deprecate E2Lib.raiseException in favor of new function, RuntimeContext:forceThrow.

Now only in the case of non-strict, a runtime context's throw function is overridden with one that returns the default value, otherwise it inherits the function from its metatable.

forceThrow uses this inherited function.

Added coverage for early returns to unit test suite, change __return__ check position

Removed old string function implementation (completely scrapped for raw compiler implementation)
Optimizing the parser led me to accidentally mess up the precedence of certain operators.

Specifically | should've had less precedence than &, but this was not true, they had the same precedence.

Added test cases for these operators.

Also optimized delta operator since the scope will always be the global scope.
Incorrect and I'd rather not be connected to that
Remove SaveScopes and LoadScopes functions, inline their contents to avoid allocation of a redundant table and the function overhead.
@thegrb93
Copy link
Contributor

Is this ready?

@Vurv78
Copy link
Member Author

Vurv78 commented Sep 27, 2023

Yeah, I haven't had to patch many bugs at all within the past few months, and it's worked fine for the two servers that I know that use it, (E2BI and TAS). I wanted to get a larger server like CFC to try it, but that didn't end up happening.

I was afraid of some giant internal compiler bug that I wouldn't be able to patch quickly happening, but I don't think that will happen.

Also having this unmerged has largely stalled progress on E2..

@Vurv78
Copy link
Member Author

Vurv78 commented Sep 30, 2023

Gonna merge later today if there's no objections, since I'll have the weekend to patch any potential bugs that show up

@Vurv78 Vurv78 merged commit 44d9366 into wiremod:master Oct 1, 2023
1 check failed
assert(k == len, "PP syntax error: Ellipses (...) must be the last argument.")
return argtable, ArgsKind.Variadic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this deprecated ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Luajit can't optimize Lua varargs. E2 already passes args as a table, so doing Lua varargs means you're unpacking a table and then repacking it for no gain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment