stefanobaghino / lox-interpreter Goto Github PK
View Code? Open in Web Editor NEWInterpreter for Lox (https://craftinginterpreters.com) written in Go
Interpreter for Lox (https://craftinginterpreters.com) written in Go
Set up the skeleton for the project
In principle, it looks like it's more or less the reverse of a scanner which can possibly be configured.
When a parsing error occurs in REPL mode, the parser tries to get to the following sync point before resuming normal operation, preventing the reporting of the error until such point is reached.
The scanner is more or less ported as-is from the original Java code. This is probably a good idea for the time being because by minimizing the differences from the book, it's less likely to be confusing. However, once it's clear that scanning if finalized, it can and should be refactored to be more idiomatic and make full use of Scala.
Since the whole project seems to not have any automatic testing in mind and the code is being ported as-is from Java to Scala Go, mutation testing might be useful to uncover whether the testing coverage I'm adding going along is not sufficient.
The syntax is a break
keyword followed by a semicolon. It should be a syntax error to have a break
statement appear outside of any enclosing loop. At runtime, a break
statement causes execution to jump to the end of the nearest enclosing loop and proceeds from there. Note that the break
may be nested inside other blocks and if
statements that also need to be exited.
Languages that encourage a functional style usually support anonymous functions or lambdas—an expression syntax that creates a function without binding it to a name. Add anonymous function syntax to Lox so that this works:
fun thrice(fn) { for (var i = 1; i <= 3; i = i + 1) { fn(i); } } thrice(fun (a) { print a; }); // "1". // "2". // "3".
How do you handle the tricky case of an anonymous function expression occurring in an expression statement:
fun () {};
-- https://craftinginterpreters.com/functions.html#challenges
Maybe you want Lox to be a little more explicit about variable initialization. Instead of implicitly initializing variables to nil, make it a runtime error to access a variable that has not been initialized or assigned to, as in:
// No initializers. var a; var b; a = "assigned"; print a; // OK, was assigned first. print b; // Error!
-- https://craftinginterpreters.com/statements-and-state.html#challenges
Print statements work well to manually inspect the state, but asserts could help making it easier to test the language. The current approach is that of having the interpreter execute some statements and then passing an expression that evaluates a predicate, which is ultimately used to evaluate whether the code was interpreted correctly.
lox-interpreter/src/test/scala/lox/InterpreterTest.scala
Lines 160 to 172 in 3ce6e26
This might be repackaged as a function after #7 is closed. Hypothetically, refactoring so that the state can be inspected without necessarily invoking the interpreter explicitly might make this point moot.
Run tests, verify formatting, evaluate and possibly implement code coverage checks
Many languages define + such that if either operand is a string, the other is converted to a string and the results are then concatenated. For example,
"scone" + 4
would yieldscone4
. Extend the code invisitBinaryExpr()
to support that.
-- https://craftinginterpreters.com/evaluating-expressions.html
As part of merging #41, I changed the scanner as it is implemented in the book to make the error flow explicit in the code. As part of this, the scanner is also supposed to stop on UTF-8 decoding errors, but this is currently not tested. To make sure all edge cases are covered in this regard, it would be nice to see whether fuzz testing could come in handy to do so without creating a bunch of test cases by hand.
What happens right now if you divide a number by zero? What do you think should happen? Justify your choice. How do other languages you know handle division by zero, and why do they make the choices they do?
Change the implementation in
visitBinaryExpr()
to detect and report a runtime error for this case.
-- https://craftinginterpreters.com/evaluating-expressions.html
In #26 the parser is tested by feeding manually crafted tokens. It would be interesting to integrate the two components in a single test. To what extent would it make sense to drop some or all tests in the scanner?
In C, a block is a statement form that allows you to pack a series of statements where a single one is expected. The comma operator is an analogous syntax for expressions. A comma-separated series of expressions can be given where a single expression is expected (except inside a function call’s argument list). At runtime, the comma operator evaluates the left operand and discards the result. Then it evaluates and returns the right operand.
Add support for comma expressions. Give them the same precedence and associativity as in C. Write the grammar, and then implement the necessary parsing code.
-- https://craftinginterpreters.com/parsing-expressions.html#challenges
Evaluate refactoring error detection so that it happens before error reporting to make it easier to test. Ideally this should enable to easily reach a 100% test coverage in the scanner without capturing logs.
The REPL no longer supports entering a single expression and automatically printing its result value. That’s a drag. Add support to the REPL to let users type in both statements and expressions. If they enter a statement, execute it. If they enter an expression, evaluate it and display the result value.
-- https://craftinginterpreters.com/statements-and-state.html#challenges
Add support to the scanner for C-style /* ... */
block comments. Make sure to handle newlines in them. Consider allowing them to nest. Is adding support for nesting more work than you expected? Why?
In reverse Polish notation (RPN), the operands to an arithmetic operator are both placed before the operator, so 1 + 2
becomes 1 2 +
. Evaluation proceeds from left to right. Numbers are pushed onto an implicit stack. An arithmetic operator pops the top two numbers, performs the operation, and pushes the result. Thus, this:
(1 + 2) * (4 - 3)
in RPN becomes:
1 2 + 4 3 - *
Define a visitor class for our syntax tree classes that takes an expression, converts it to RPN, and returns the resulting string.
What does the following program do?
var a = 1; { var a = a + 2; print a; }
What did you expect it to do? Is it what you think it should do? What does analogous code in other languages you are familiar with do? What do you think users will expect this to do?
-- https://craftinginterpreters.com/statements-and-state.html#challenges
Right now the assertion error always reports line 0 since the token doesn't have a reference to the line number. Make sure at least the line number is reported correctly.
Right now, parsing is implemented using the visitor pattern. This is useful writing Java, but it's probably better expressed in Scala using pattern matching. Evaluate this refactoring and possibly implement it. Ideally, write down as a comment which direction you decided to take and why.
[...] detect a binary operator appearing at the beginning of an expression. Report that as an error, but also parse and discard a right-hand operand with the appropriate precedence.
-- https://craftinginterpreters.com/parsing-expressions.html#challenges
As a personal challenge and a way to learn something new, it would be interesting to port this project to Scala 3.
Implementing static binding resolution (#59) seems to have broken shadowing. While the original Java code was ported apparently faithfully, I suspect the problem arises from the fact that I implemented the whole interpreter to discover the user input one line at a time, possibly breaking assumptions in the original implementation.
Check whether this speeds up workflow runs (right now the direnv
step which loads the flake takes ~20 seconds). If it doesn't it would probably best to roll back.
As a personal challenge and a way to learn something.
Referring to the comma operator (see #28):
Likewise, add support for the C-style conditional or “ternary” operator
?:
. What precedence level is allowed between the?
and:
? Is the whole operator left-associative or right-associative?
-- https://craftinginterpreters.com/parsing-expressions.html
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.