loxxy 0.0.11 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +74 -0
- data/README.md +155 -55
- data/lib/loxxy.rb +23 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -0
- data/lib/loxxy/ast/ast_builder.rb +53 -27
- data/lib/loxxy/ast/ast_visitor.rb +43 -55
- data/lib/loxxy/ast/lox_binary_expr.rb +6 -0
- data/lib/loxxy/ast/lox_compound_expr.rb +1 -1
- data/lib/loxxy/ast/lox_node.rb +5 -0
- data/lib/loxxy/ast/lox_noop_expr.rb +16 -0
- data/lib/loxxy/ast/lox_print_stmt.rb +21 -0
- data/lib/loxxy/back_end/engine.rb +62 -0
- data/lib/loxxy/datatype/boolean.rb +11 -0
- data/lib/loxxy/datatype/builtin_datatype.rb +6 -0
- data/lib/loxxy/datatype/false.rb +22 -0
- data/lib/loxxy/datatype/lx_string.rb +46 -10
- data/lib/loxxy/datatype/nil.rb +22 -0
- data/lib/loxxy/datatype/number.rb +94 -1
- data/lib/loxxy/datatype/true.rb +22 -0
- data/lib/loxxy/front_end/grammar.rb +4 -4
- data/lib/loxxy/interpreter.rb +40 -0
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +43 -0
- data/spec/datatype/boolean_spec.rb +31 -0
- data/spec/datatype/lx_string_spec.rb +32 -5
- data/spec/datatype/nil_spec.rb +26 -0
- data/spec/datatype/number_spec.rb +57 -0
- data/spec/front_end/parser_spec.rb +24 -73
- data/spec/interpreter_spec.rb +116 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d7d30d3246aa8a8f06f3456d3996c680e9ddb4ed31bcf9fff99685702e50dd1
|
4
|
+
data.tar.gz: dc428ddd0fc03e153e70873bc2827b6e4aac813e5fdfba40ef2f28bfa7283fc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d90f2494a324be036dae2f3d38636c571009b60f19d2b3d917b242ab98d231e03bb121f577e5598ac63514cc5817814972cabe927f05af2842a5a97c89eb69d9
|
7
|
+
data.tar.gz: e09a70a27baa1408904e1bfe512f4f523b8e4efd1aab00412b993c8f4a35b5498f1f6ddb5e67697108c80a91778301e41c15741451e73e1d869496d96441757a
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,77 @@
|
|
1
|
+
## [0.0.16] - 2021-01-11
|
2
|
+
- The interpreter can evaluate product and division of two numbers.
|
3
|
+
- It also implements equality `==` and inequality `!=` operators
|
4
|
+
|
5
|
+
## Added
|
6
|
+
- Method `Datatype::False#==` for equality testing
|
7
|
+
- Method `Datatype::False#!=` for inequality testing
|
8
|
+
- Method `Datatype::LXString#==` for equality testing
|
9
|
+
- Method `Datatype::LXString#!=` for inequality testing
|
10
|
+
- Method `Datatype::Nil#==` for equality testing
|
11
|
+
- Method `Datatype::Nil#!=` for inequality testing
|
12
|
+
- Method `Datatype::Number#==` for equality testing
|
13
|
+
- Method `Datatype::Number#!=` for inequality testing
|
14
|
+
- Method `Datatype::Number#*` for multiply operator
|
15
|
+
- Method `Datatype::Number#/` for divide operator
|
16
|
+
- Method `Datatype::True#==` for equality testing
|
17
|
+
- Method `Datatype::True#!=` for inequality testing
|
18
|
+
|
19
|
+
## Changed
|
20
|
+
- Method `BackEnd::Engine#after_binary_expr` to allow `*`, `/`, `==`, `!=` operators
|
21
|
+
- File `README.md` updated for the newly implemented operators
|
22
|
+
|
23
|
+
## [0.0.15] - 2021-01-11
|
24
|
+
- The interpreter can evaluate substraction between two numbers.
|
25
|
+
|
26
|
+
## Added
|
27
|
+
- Method `Datatype::Number#-` implmenting the subtraction operation
|
28
|
+
|
29
|
+
## Changed
|
30
|
+
- File `README.md` minor editorial changes.
|
31
|
+
- File `lx_string_spec.rb` Added test for string concatentation
|
32
|
+
- File `number_spec.rb` Added tests for addition and subtraction operations
|
33
|
+
- File `interpreter_spec.rb` Added tests for subtraction operation
|
34
|
+
|
35
|
+
## [0.0.14] - 2021-01-10
|
36
|
+
- The interpreter can evaluate addition of numbers and string concatenation
|
37
|
+
|
38
|
+
## Added
|
39
|
+
- Method `Ast::ASTVisitor::visit_binary_expr` for visiting binary expressions
|
40
|
+
- Method `Ast::LoxBinaryExpr#accept` for visitor pattern
|
41
|
+
- Method `BackEnd::Engine#after_binary_expr` to trigger execution of binary operator
|
42
|
+
- `Boolean` class hierarchy: added methos `true?` and `false?` to ease spec test writing
|
43
|
+
- Method `Datatype::LXString#+` implementation of the string concatenation
|
44
|
+
- Method `Datatype::Number#+` implementation of the addition of numbers
|
45
|
+
|
46
|
+
## Changed
|
47
|
+
- File `interpreter_spec.rb` Added tests for addition operation and string concatenation
|
48
|
+
|
49
|
+
|
50
|
+
## [0.0.13] - 2021-01-10
|
51
|
+
- The interpreter can evaluate directly simple literals.
|
52
|
+
|
53
|
+
## Changed
|
54
|
+
- Class `AST::ASTBuilder` added `reduce_exprStmt` to support the evaluation of literals.
|
55
|
+
- File `README.md` added one more example.
|
56
|
+
- File `parser_spec.rb` Updated the tests to reflect the change in the AST.
|
57
|
+
- File `interpreter_spec.rb` Added a test for literal expression.
|
58
|
+
|
59
|
+
## Fixed
|
60
|
+
- File `loxxy.rb`: shorthand method `lox_true` referenced the ... false object (oops).
|
61
|
+
|
62
|
+
## [0.0.12] - 2021-01-09
|
63
|
+
- Initial interpreter capable of evaluating a tiny subset of Lox language.
|
64
|
+
|
65
|
+
## Added
|
66
|
+
- Class `AST::LoxNoopExpr`
|
67
|
+
- Class `AST::LoxPrintStmt`
|
68
|
+
- Class `BackEnd::Engine` implementation of the print statement logic
|
69
|
+
- Class `Interpreter`
|
70
|
+
|
71
|
+
## Changed
|
72
|
+
- Class `Ast::ASTVisitor` Added visit method
|
73
|
+
- File `README.md` added Hello world example.
|
74
|
+
|
1
75
|
## [0.0.11] - 2021-01-08
|
2
76
|
- AST node generation for logical expression (and, or).
|
3
77
|
|
data/README.md
CHANGED
@@ -2,69 +2,87 @@
|
|
2
2
|
[](https://badge.fury.io/rb/loxxy)
|
3
3
|
[](https://github.com/famished-tiger/loxxy/blob/main/LICENSE.txt)
|
4
4
|
|
5
|
-
|
5
|
+
### What is loxxy?
|
6
6
|
A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html ),
|
7
7
|
a simple language used in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/ ).
|
8
8
|
|
9
|
-
|
9
|
+
### Purpose of this project:
|
10
10
|
- To deliver an open source example of a programming language fully implemented in Ruby
|
11
|
-
(from the scanner, parser,
|
11
|
+
(from the scanner, parser, an interpreter).
|
12
12
|
- The implementation should be mature enough to run [LoxLox](https://github.com/benhoyt/loxlox),
|
13
13
|
a Lox interpreter written in Lox.
|
14
14
|
|
15
|
-
|
16
|
-
The
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
-
|
28
|
-
-
|
29
|
-
|
30
|
-
|
31
|
-
-
|
32
|
-
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
-
|
42
|
-
-
|
43
|
-
-
|
44
|
-
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
15
|
+
### Current status
|
16
|
+
The project is still in inception and the interpreter is being implemented...
|
17
|
+
Currently it can execute a tiny subset of __Lox__ language.
|
18
|
+
|
19
|
+
But the __loxxy__ gem hosts also a parser class `RawPaser` that can parse, in principle, any valid Lox input.
|
20
|
+
|
21
|
+
## What's the fuss about Lox?
|
22
|
+
... Nothing...
|
23
|
+
Bob Nystrom designed a language __simple__ enough so that he could present
|
24
|
+
two implementations (an interpreter, then a compiler) in one single book.
|
25
|
+
|
26
|
+
Although __Lox__ is fairly simple, it is far from a toy language:
|
27
|
+
- Dynamically typed,
|
28
|
+
- Provides datatypes such as booleans, number, strings,
|
29
|
+
- Supports arithmetic operations (+, -, *, / ) and comparison ( >, >= , <, <=)
|
30
|
+
- Implements equality operators (==, !=) and the logical connectors `and` and `or`.
|
31
|
+
- Control flow statements `if`, `for` and `while`
|
32
|
+
- Functions and closures
|
33
|
+
- Object-orientation (classes, methods, inheritance).
|
34
|
+
|
35
|
+
In other words, __Lox__ contains interesting features expected from most general-purpose
|
36
|
+
languages.
|
37
|
+
|
38
|
+
### What's missing in Lox?
|
39
|
+
__Lox__ was constrained by design and therefore was not aimed to be a language used in real-world applications.
|
40
|
+
Here are some missing parts to make it a _practical_ language:
|
41
|
+
- Collections (arrays, maps, ...)
|
42
|
+
- Modules (importing stuff from other packages/files)
|
43
|
+
- Error handling (e.g. exceptions)
|
44
|
+
- Support for concurrency (e.g. threads, coroutines)
|
45
|
+
|
46
|
+
Also a decent standard library for IO, networking,... is lacking.
|
47
|
+
|
48
|
+
For sure, the language has shortcomings but on the other hand, it exhibits the essential features
|
49
|
+
to cover in an introduction to language implementation.
|
50
|
+
|
51
|
+
That's already fun... and if all this gives you the inspiration for creating your own
|
52
|
+
language, that might be even funnier...
|
53
|
+
|
54
|
+
Last point: what's makes __Lox__ interesting is the fact that there are implementations in many [languages](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations)
|
55
|
+
|
56
|
+
## Hello world example
|
57
|
+
```ruby
|
58
|
+
require 'loxxy'
|
59
|
+
|
60
|
+
lox_program = <<LOX_END
|
61
|
+
// Your first Lox program!
|
62
|
+
print "Hello, world!";
|
63
|
+
LOX_END
|
64
|
+
|
65
|
+
lox = Loxxy::Interpreter.new
|
66
|
+
lox.evaluate(lox_program) # Output: Hello, world!
|
67
|
+
```
|
68
|
+
|
69
|
+
## Retrieving the result from a Lox program
|
70
|
+
The __Loxxy__ interpreter returns the value of the last evaluated expression.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
require 'loxxy'
|
74
|
+
|
75
|
+
lox = Loxxy::Interpreter.new
|
76
|
+
|
77
|
+
lox_program = '47 - 5; // THE answer'
|
78
|
+
result = lox.evaluate(lox_program) # => Loxxy::Datatype::Number
|
79
|
+
|
80
|
+
# `result` is a Ruby object, so let's use it...
|
81
|
+
puts result.value # Output: 42
|
82
|
+
```
|
66
83
|
|
67
84
|
## Example using RawParser class
|
85
|
+
|
68
86
|
```ruby
|
69
87
|
require 'loxxy'
|
70
88
|
|
@@ -109,6 +127,89 @@ program
|
|
109
127
|
+-- EOF: ''
|
110
128
|
```
|
111
129
|
|
130
|
+
## Suppported Lox language features
|
131
|
+
Although the interpreter should parse almost any valid Lox program,
|
132
|
+
it currently can evaluate a tiny set of AST node (AST = Abstract Syntax Tree).
|
133
|
+
|
134
|
+
Here are the language features currently supported by the interpreter:
|
135
|
+
|
136
|
+
- [Comments](#comments)
|
137
|
+
- [Keywords](#keywords)
|
138
|
+
- [Operators and Special Chars](#operators-and-special-chars)
|
139
|
+
- [Datatypes](#datatypes)
|
140
|
+
- [Statements](#statements)
|
141
|
+
|
142
|
+
### Comments
|
143
|
+
|
144
|
+
Loxxy supports single line C-style comments.
|
145
|
+
|
146
|
+
```javascript
|
147
|
+
// single line comment
|
148
|
+
```
|
149
|
+
|
150
|
+
### Keywords
|
151
|
+
|
152
|
+
The parser knows all the __Lox__ reserved keywords:
|
153
|
+
```lang-none
|
154
|
+
and, class, else, false, fun, for, if, nil, or,
|
155
|
+
print, return, super, this, true, var, while
|
156
|
+
```
|
157
|
+
Of these, the interpreter implements: `false`, `nil`, `true`
|
158
|
+
|
159
|
+
### Operators and Special Chars
|
160
|
+
#### Operators
|
161
|
+
The parser recognizes all the __Lox__ operators, delimiters and separators:
|
162
|
+
- Arithmetic operators: `+`, `-`, `*`, `/`
|
163
|
+
- Comparison operators: `>`, `>=`, `<`, `<=`
|
164
|
+
- Equality operators: `==`, `!=`
|
165
|
+
|
166
|
+
Of these, the interpreter implements:
|
167
|
+
`+` (addition of two numbers or string concatenation)
|
168
|
+
`-` (difference between two numbers)
|
169
|
+
`*` (product of two numbers)
|
170
|
+
`/` (division of two number)
|
171
|
+
`==` and `!=` (in)equality testing of two Lox values
|
172
|
+
|
173
|
+
#### Delimiters
|
174
|
+
The parser knows all the __Lox__ grouping delimiters:
|
175
|
+
(`, ), `{`, `}`
|
176
|
+
|
177
|
+
These aren't yet implemented in the interpreter.
|
178
|
+
|
179
|
+
The other characters that have a special meaning in __Lox__ are:
|
180
|
+
- `,` Used in parameter list
|
181
|
+
- `.` For the dot notation (i.e. calling a method)
|
182
|
+
- `;` The semicolon is used to terminates expressions
|
183
|
+
- `=` Assignment
|
184
|
+
|
185
|
+
The parser recognizes them all but the interpreter accepts the semicolons only.
|
186
|
+
|
187
|
+
### Datatypes
|
188
|
+
|
189
|
+
loxxy supports all the standard __Lox__ datatypes:
|
190
|
+
- `Boolean`: Can be `true` or `false`
|
191
|
+
- `Number`: Can be an integer or a floating-point numbers. For example: `123, 12.34, -45.67`
|
192
|
+
- `String`: Sequence of characters surrounded by `"`. For example: `"Hello!"`
|
193
|
+
- `Nil`: Used to define a null value, denoted by the `nil` keyword
|
194
|
+
|
195
|
+
## Statements
|
196
|
+
### Implemented expressions
|
197
|
+
Loxxy implements expressions:
|
198
|
+
- Plain literals only; or,
|
199
|
+
- Addition, subtraction, product and division of two numbers; or,
|
200
|
+
- Concatenation of two strings,
|
201
|
+
- (In)equality test of two Lox values
|
202
|
+
|
203
|
+
### Implemented statements
|
204
|
+
Loxxy implements the following statements:
|
205
|
+
- Expressions (see above sub-section)
|
206
|
+
- Print statement
|
207
|
+
|
208
|
+
```javascript
|
209
|
+
// Print statement with nested concatenation
|
210
|
+
print "Hello" + ", " + "world!";
|
211
|
+
```
|
212
|
+
|
112
213
|
## Installation
|
113
214
|
|
114
215
|
Add this line to your application's Gemfile:
|
@@ -130,7 +231,6 @@ Or install it yourself as:
|
|
130
231
|
TODO: Write usage instructions here
|
131
232
|
|
132
233
|
## Other Lox implementations in Ruby
|
133
|
-
An impressive list of Lox implementations can be found [here](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations)
|
134
234
|
|
135
235
|
For Ruby, there is the [lox](https://github.com/rdodson41/ruby-lox) gem.
|
136
236
|
There are other Ruby-based projects as well:
|
data/lib/loxxy.rb
CHANGED
@@ -1,10 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'loxxy/version'
|
4
|
+
require_relative 'loxxy/interpreter'
|
4
5
|
require_relative 'loxxy/front_end/raw_parser'
|
5
|
-
require_relative 'loxxy/front_end/parser'
|
6
6
|
|
7
|
+
# Namespace for all classes and constants of __loxxy__ gem.
|
7
8
|
module Loxxy
|
8
9
|
class Error < StandardError; end
|
9
|
-
|
10
|
+
|
11
|
+
# Shorthand method. Returns the sole object that represents
|
12
|
+
# a Lox false literal.
|
13
|
+
# @return [Loxxy::Datatype::False]
|
14
|
+
def self.lox_false
|
15
|
+
Datatype::False.instance
|
16
|
+
end
|
17
|
+
|
18
|
+
# Shorthand method. Returns the sole object that represents
|
19
|
+
# a Lox nil literal.
|
20
|
+
# @return [Loxxy::Datatype::Nil]
|
21
|
+
def self.lox_nil
|
22
|
+
Datatype::Nil.instance
|
23
|
+
end
|
24
|
+
|
25
|
+
# Shorthand method. Returns the sole object that represents
|
26
|
+
# a Lox true literal.
|
27
|
+
# @return [Loxxy::Datatype::True]
|
28
|
+
def self.lox_true
|
29
|
+
Datatype::True.instance
|
30
|
+
end
|
10
31
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../datatype/all_datatypes'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'all_lox_nodes'
|
5
5
|
require_relative 'lox_binary_expr'
|
6
6
|
|
7
7
|
module Loxxy
|
@@ -10,31 +10,33 @@ module Loxxy
|
|
10
10
|
# (Abstract Syntax Tree) from a sequence of input tokens and
|
11
11
|
# visit events produced by walking over a GFGParsing object.
|
12
12
|
class ASTBuilder < Rley::ParseRep::ASTBaseBuilder
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
13
|
+
unless defined?(Name2special)
|
14
|
+
# Mapping Token name => operator | separator | delimiter characters
|
15
|
+
# @return [Hash{String => String}]
|
16
|
+
Name2special = {
|
17
|
+
'AND' => 'and',
|
18
|
+
'BANG' => '!',
|
19
|
+
'BANG_EQUAL' => '!=',
|
20
|
+
'COMMA' => ',',
|
21
|
+
'DOT' => '.',
|
22
|
+
'EQUAL' => '=',
|
23
|
+
'EQUAL_EQUAL' => '==',
|
24
|
+
'GREATER' => '>',
|
25
|
+
'GREATER_EQUAL' => '>=',
|
26
|
+
'LEFT_BRACE' => '{',
|
27
|
+
'LEFT_PAREN' => '(',
|
28
|
+
'LESS' => '<',
|
29
|
+
'LESS_EQUAL' => '<=',
|
30
|
+
'MINUS' => '-',
|
31
|
+
'OR' => 'or',
|
32
|
+
'PLUS' => '+',
|
33
|
+
'RIGHT_BRACE' => '}',
|
34
|
+
'RIGHT_PAREN' => ')',
|
35
|
+
'SEMICOLON' => ';',
|
36
|
+
'SLASH' => '/',
|
37
|
+
'STAR' => '*'
|
38
|
+
}.freeze
|
39
|
+
end # defined
|
38
40
|
|
39
41
|
attr_reader :strict
|
40
42
|
|
@@ -117,6 +119,30 @@ module Loxxy
|
|
117
119
|
[[operator, operand2]]
|
118
120
|
end
|
119
121
|
|
122
|
+
#####################################
|
123
|
+
# SEMANTIC ACTIONS
|
124
|
+
#####################################
|
125
|
+
|
126
|
+
# rule('program' => 'EOF').as 'null_program'
|
127
|
+
def reduce_null_program(_production, _range, _tokens, _theChildren)
|
128
|
+
Ast::LoxNoopExpr.new(tokens[0].position)
|
129
|
+
end
|
130
|
+
|
131
|
+
# rule('program' => 'declaration_plus EOF').as ''
|
132
|
+
def reduce_lox_program(_production, range, tokens, theChildren)
|
133
|
+
return_first_child(range, tokens, theChildren) # Discard the semicolon
|
134
|
+
end
|
135
|
+
|
136
|
+
# rule('exprStmt' => 'expression SEMICOLON')
|
137
|
+
def reduce_exprStmt(_production, range, tokens, theChildren)
|
138
|
+
return_first_child(range, tokens, theChildren) # Discard the semicolon
|
139
|
+
end
|
140
|
+
|
141
|
+
# rule('printStmt' => 'PRINT expression SEMICOLON')
|
142
|
+
def reduce_print_stmt(_production, _range, tokens, theChildren)
|
143
|
+
Ast::LoxPrintStmt.new(tokens[1].position, theChildren[1])
|
144
|
+
end
|
145
|
+
|
120
146
|
# rule('logic_or' => 'logic_and disjunct_plus')
|
121
147
|
def reduce_logic_or_plus(production, range, tokens, theChildren)
|
122
148
|
reduce_binary_operator(production, range, tokens, theChildren)
|
@@ -141,7 +167,7 @@ module Loxxy
|
|
141
167
|
def reduce_logic_and_plus_more(production, range, tokens, theChildren)
|
142
168
|
reduce_binary_plus_more(production, range, tokens, theChildren)
|
143
169
|
end
|
144
|
-
|
170
|
+
|
145
171
|
# rule('conjunct_plus' => 'AND equality')
|
146
172
|
def reduce_logic_and_plus_end(production, range, tokens, theChildren)
|
147
173
|
reduce_binary_plus_end(production, range, tokens, theChildren)
|