loxxy
What is loxxy?
A Ruby implementation of the Lox programming language, a simple language used in Bob Nystrom's online book Crafting Interpreters.
Purpose of this project:
- To deliver an open source example of a programming language fully implemented in Ruby
(from the scanner, parser, an interpreter). - The implementation should be mature enough to run LoxLox,
a Lox interpreter written in Lox.
Current status
The project is still in inception and the interpreter is being implemented...
Currently it can execute a tiny subset of Lox language.
But the loxxy gem hosts also a parser class RawPaser
that can parse, in principle, any valid Lox input.
What's the fuss about Lox?
... Nothing...
Bob Nystrom designed a language simple enough so that he could present
two implementations (an interpreter, then a compiler) in one single book.
Although Lox is fairly simple, it is far from a toy language:
- Dynamically typed,
- Provides datatypes such as booleans, number, strings,
- Supports arithmetic operations (+, -, *, / ) and comparison ( >, >= , <, <=)
- Implements equality operators (==, !=) and the logical connectors
and
andor
. - Control flow statements
if
,for
andwhile
- Functions and closures
- Object-orientation (classes, methods, inheritance).
In other words, Lox contains interesting features expected from most general-purpose languages.
What's missing in Lox?
Lox was constrained by design and therefore was not aimed to be a language used in real-world applications. Here are some missing parts to make it a practical language:
- Collections (arrays, maps, ...)
- Modules (importing stuff from other packages/files)
- Error handling (e.g. exceptions)
- Support for concurrency (e.g. threads, coroutines)
Also a decent standard library for IO, networking,... is lacking.
For sure, the language has shortcomings but on the other hand, it exhibits the essential features to cover in an introduction to language implementation.
That's already fun... and if all this gives you the inspiration for creating your own language, that might be even funnier...
Last point: what's makes Lox interesting is the fact that there are implementations in many languages
Hello world example
require 'loxxy'
lox_program = <<LOX_END
// Your first Lox program!
print "Hello, world!";
LOX_END
lox = Loxxy::Interpreter.new
lox.evaluate(lox_program) # Output: Hello, world!
Retrieving the result from a Lox program
The Loxxy interpreter returns the value of the last evaluated expression.
require 'loxxy'
lox = Loxxy::Interpreter.new
lox_program = '47 - 5; // THE answer'
result = lox.evaluate(lox_program) # => Loxxy::Datatype::Number
# `result` is a Ruby object, so let's use it...
puts result.value # Output: 42
Example using RawParser class
require 'loxxy'
lox_input = <<-LOX_END
// Your first Lox program!
print "Hello, world!";
LOX_END
# Show that the raw parser accepts the above program
base_parser = Loxxy::FrontEnd::RawParser.new
# Now parse the input into a concrete parse tree...
ptree = base_parser.parse(lox_input)
# Display the parse tree thanks to Rley formatters...
visitor = Rley::ParseTreeVisitor.new(ptree)
tree_formatter = Rley::Formatter::Asciitree.new($stdout)
tree_formatter.render(visitor)
This is the output produced by the above example:
program
+-- declaration_plus
| +-- declaration
| +-- statement
| +-- printStmt
| +-- PRINT: 'print'
| +-- expression
| | +-- assignment
| | +-- logic_or
| | +-- logic_and
| | +-- equality
| | +-- comparison
| | +-- term
| | +-- factor
| | +-- unary
| | +-- call
| | +-- primary
| | +-- STRING: '"Hello, world!"'
| +-- SEMICOLON: ';'
+-- EOF: ''
Suppported Lox language features
On one hand, the parser covers the complete Lox grammar and should therefore, in principle, parse any valid Lox program.
On the other hand, the interpreter is under development and currently it can evaluate only a tiny subset of Lox. But the situation is changing almost daily, stay tuned...
Here are the language features currently supported by the interpreter:
- Comments
- Keywords
- Datatypes
- Statements
-Expressions
-Print Statement
Comments
Loxxy supports single line C-style comments.
// single line comment
Keywords
Loxxy implements the following Lox reserved keywords:
and, false, nil, or, print, true
Datatypes
loxxy supports all the standard Lox datatypes:
-
Boolean
: Can betrue
orfalse
-
Number
: Can be an integer or a floating-point numbers. For example:123, 12.34, -45.67
-
String
: Sequence of characters surrounded by"
. For example:"Hello!"
-
Nil
: Used to define a null value, denoted by thenil
keyword
Statements
Loxxy supports the following statements:
-Expressions
-Arithmetic expressions
-String concatenation
-Comparison expressions
-Logical expressions
-Grouping expressions
-If Statement
-Print Statement
Expressions
Arithmetic expressions
Loxxy supports the following operators for arithmetic expressions:
-
+
: Adds of two numbers. Both operands must be of the type Number
E.g.37 + 5; // => 42
7 + -3; // => 4
-
-
: (Binary) Subtracts right operand from left operand. Both operands must be numbers.
E.g.47 - 5; // => 42
-
-
: (Unary) Negates (= changes the sign) of the given number operand.
E.g.- -3; // => 3
-
*
: Multiplies two numbers
E.g.2 * 3; // => 6
-
/
: Divides two numbers
E.g.8 / 2; // => 4
5 / 2; // => 2.5
String concatenation
-
+
: Concatenates two strings. Both operands must be of the String type.
E.g."Hello" + ", " + "world! // => "Hello, world!"
Comparison expressions
-
==
: Returnstrue
if left operand is equal to right operand, otherwisefalse
E.g.false == false; // => true
5 + 2 == 3 + 4; // => true
"" == ""; // => true
-
!=
: Returnstrue
if left operand is not equal to right operand, otherwisefalse
E.g.false != "false"; // => true
5 + 2 != 4 + 3; // => false
-
<
: Returnstrue
if left operand is less than right operand, otherwisefalse
. Both operands must be numbers
E.g.1 < 3; // => true
1 < 0; // => false
2 < 2; // => false
-
<=
: Returnstrue
if left operand is equal to right operand, otherwisefalse
. Both operands must be numbers
E.g.1 <= 3; // => true
1 <= 0; // => false
2 <= 2; // => true
-
>
: Returnstrue
if left operand is equal to right operand, otherwisefalse
. Both operands must be numbers
E.g.1 > 3; // => false
1 > 0; // => true
2 > 2; // => false
-
>=
: Returnstrue
if left operand is equal to right operand, otherwisefalse
. Both operands must be numbers
E.g.1 > 3; // => false
1 > 0; // => true
2 > 2; // => false
Logical expressions
REMINDER: In Lox, false
and nil
are considered falsey, everything else is truthy.
-
and
: When both operands are booleans, then returnstrue
if both left and right operands are truthy, otherwisefalse
.
If at least one operand isn't a boolean then returns first falsey operand else (both operands are truthy) returns the second operand. truthy returns the second operand.
E.g.false and true; // => false
true and nil; // => nil
0 and true and ""; // => ""
-
or
: When both operands are booleans, then returnstrue
if left or right operands are truthy, otherwisefalse
.
If at least one operand isn't a boolean then returns first truthy operand else (both operands are truthy) returns the second operand. E.g.false or true; // => true
true or nil; // => nil
false or nil; // => nil
0 or true or ""; // => 0
-
!
: Performs a logical negation on its operand
E.g.!false; // => true
!!true; // => true
!0; // => false
Grouping expressions
Use parentheses (
)
for a better control in expression/operator precedence.
print 3 + 4 * 5; // => 23
print (3 + 4) * 5; // => 35
If statement
Based on a given condition, an if statement executes one of two statements:
if (condition) {
print "then-branch";
} else {
print "else-branch";
}
As for other languages, the else
part is optional.
Warning: nested if
...else
Call it a bug ... Nested if
else
control flow structure aren't yet supported by Loxxy.
The culprit has a name: the dangling else.
The problem in a nutshell: in a nested if ... else ... statement like this:
'if (true) if (false) print "bad"; else print "good";
... there is an ambiguity. Indeed, according to the Lox grammar, the else
could be bound
either to the first if
or to the second one.
This ambiguity is usually lifted by applying an ad-hoc rule: an else
is aways bound to the most
recent (rightmost) if
.
Being a generic parsing library, Rley
doesn't apply any of these supplemental rules.
As a consequence,it complains about the found ambiguity and stops the parsing...
Although Rley
can cope with ambiguities, this requires the use of an advanced data structure
called Shared Packed Parse Forest (SPPF)
.
SPPF are much more complex to handle than the common
parse trees present in most compiler or interpreter books.
Therefore, a future version of Rley
will incorporate the capability to define disambuiguation rules.
In the meantime, the Loxxy
will progress on other Lox features like:
- Variables,
- Block structures...
Print Statement
The statement print + expression + ; prints the result of the expression to stdout.
print "Hello, world!"; // Output: Hello, world!
Installation
Add this line to your application's Gemfile:
gem 'loxxy'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install loxxy
Usage
TODO: Write usage instructions here
Other Lox implementations in Ruby
For Ruby, there is the lox gem. There are other Ruby-based projects as well:
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/famished_tiger/loxxy.
License
The gem is available as open source under the terms of the MIT License.