loxxy 0.0.13 → 0.0.14
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/README.md +79 -5
- data/lib/loxxy/ast/ast_visitor.rb +8 -0
- data/lib/loxxy/ast/lox_binary_expr.rb +6 -0
- data/lib/loxxy/ast/lox_print_stmt.rb +0 -1
- data/lib/loxxy/back_end/engine.rb +13 -0
- data/lib/loxxy/datatype/boolean.rb +11 -0
- data/lib/loxxy/datatype/false.rb +6 -0
- data/lib/loxxy/datatype/lx_string.rb +23 -6
- data/lib/loxxy/datatype/number.rb +33 -1
- data/lib/loxxy/datatype/true.rb +6 -0
- data/lib/loxxy/version.rb +1 -1
- data/spec/datatype/lx_string_spec.rb +23 -5
- data/spec/datatype/number_spec.rb +16 -0
- data/spec/interpreter_spec.rb +12 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18b1d3e9f498ca8249775f55cc3950622d9f5a49cd67db912816cc60f15b3d16
|
4
|
+
data.tar.gz: 5766130089ebbe6d85a90a9f59ac1a505202c0e2646d4d7da93af025a80b0651
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 621832b48a5625081a0b82a8f8992d78709a1d4a053d8e4719140510242ce14d4418ca23c82d9f52186453c42d5d432203e7af496d7f92726f75622c743119e2
|
7
|
+
data.tar.gz: aa67901fae29e13d3154e274c7014fe2002fb7e054c62cf253e87e44ac421747db9f5f05ef1e108cd92c8c0124ceb93b31fa537de64f875607c096465f89aee4
|
data/README.md
CHANGED
@@ -74,10 +74,11 @@ require 'loxxy'
|
|
74
74
|
|
75
75
|
lox = Loxxy::Interpreter.new
|
76
76
|
|
77
|
-
lox_program = '
|
77
|
+
lox_program = '37 + 5; // THE answer'
|
78
78
|
result = lox.evaluate(lox_program) # => Loxxy::Datatype::Number
|
79
79
|
|
80
|
-
|
80
|
+
# `result` is a Ruby object, so let's use it...
|
81
|
+
puts result.value # Output: 42
|
81
82
|
```
|
82
83
|
|
83
84
|
## Example using RawParser class
|
@@ -130,11 +131,84 @@ program
|
|
130
131
|
Although the interpreter should parse almost any valid Lox program,
|
131
132
|
it currently can evaluate a tiny set of AST node (AST = Abstract Syntax Tree).
|
132
133
|
|
133
|
-
Here are the language features supported by the interpreter:
|
134
|
-
|
135
|
-
-
|
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)
|
136
141
|
- `print` statement.
|
137
142
|
|
143
|
+
### Comments
|
144
|
+
|
145
|
+
Loxxy supports single line C-style comments.
|
146
|
+
|
147
|
+
```javascript
|
148
|
+
// single line comment
|
149
|
+
```
|
150
|
+
|
151
|
+
### Keywords
|
152
|
+
|
153
|
+
The parser knows all the __Lox__ reserved keywords:
|
154
|
+
```lang-none
|
155
|
+
and, class, else, false, fun, for, if, nil, or,
|
156
|
+
print, return, super, this, true, var, while
|
157
|
+
```
|
158
|
+
Of these, the interpreter implements: `false`, `nil`, `true`
|
159
|
+
|
160
|
+
### Operators and Special Chars
|
161
|
+
#### Operators
|
162
|
+
The parser recognizes all the __Lox__ operators, delimiters and separators:
|
163
|
+
- Arithmetic operators: `+`, `-`, `*`, `/`
|
164
|
+
- Comparison operators: `>`, `>=`, `<`, `<=`
|
165
|
+
- Equality operators: `==`, `!=`
|
166
|
+
|
167
|
+
Of these, the interpreter implements:
|
168
|
+
`+` (addition of two numbers or string concatenation)
|
169
|
+
|
170
|
+
#### Delimiters
|
171
|
+
The parser knows all the __Lox__ grouping delimiters:
|
172
|
+
(`, ), `{`, `}`
|
173
|
+
|
174
|
+
These aren't yet implemented in the interpreter.
|
175
|
+
|
176
|
+
The other characters that have a special meaning in __Lox__ are:
|
177
|
+
- `,` Used in parameter list
|
178
|
+
- `.` For the dot notation (i.e. calling a method)
|
179
|
+
- `;` The semicolon is used to terminates expressions
|
180
|
+
- `=` Assignment
|
181
|
+
|
182
|
+
The parser recognizes them all but the interpreter accepts the semicolons only.
|
183
|
+
|
184
|
+
### Datatypes
|
185
|
+
|
186
|
+
Loxxy supports the following standard __Lox__ datatypes:
|
187
|
+
- `Boolean`: Can be `true` or `false`
|
188
|
+
- `Number`: Can be an integer or a floating-point numbers. For example: `123, 12.34, -45.67`
|
189
|
+
- `String`: Sequence of characters surrounded by `"`. For example: `"Hello!"`
|
190
|
+
- `Nil`: Used to define a null value, denoted by the `nil` keyword
|
191
|
+
|
192
|
+
All the Lox literals (booleans, numbers, strings and nil),
|
193
|
+
|
194
|
+
## Statements
|
195
|
+
### Implemented expressions
|
196
|
+
Loxxy implements expressions:
|
197
|
+
- Consisting of literals only; or,
|
198
|
+
- Addition of two numbers; or,
|
199
|
+
- Concatenation of two strings
|
200
|
+
|
201
|
+
### Implemented statements
|
202
|
+
Loxxy implements the following statements:
|
203
|
+
- Expressions (see above sub-section)
|
204
|
+
- Print statement
|
205
|
+
|
206
|
+
```javascript
|
207
|
+
// Print statement with nested concatenation
|
208
|
+
print "Hello" + ", " + "world!";
|
209
|
+
```
|
210
|
+
|
211
|
+
|
138
212
|
|
139
213
|
## Installation
|
140
214
|
|
@@ -59,6 +59,14 @@ module Loxxy
|
|
59
59
|
broadcast(:after_print_stmt, aPrintStmt)
|
60
60
|
end
|
61
61
|
|
62
|
+
# Visit event. The visitor is about to visit a print statement expression.
|
63
|
+
# @param aBinaryExpr [AST::LOXBinaryExpr] the binary expression node to visit
|
64
|
+
def visit_binary_expr(aBinaryExpr)
|
65
|
+
broadcast(:before_binary_expr, aBinaryExpr)
|
66
|
+
traverse_subnodes(aBinaryExpr)
|
67
|
+
broadcast(:after_binary_expr, aBinaryExpr)
|
68
|
+
end
|
69
|
+
|
62
70
|
# Visit event. The visitor is visiting the
|
63
71
|
# given terminal node containing a datatype object.
|
64
72
|
# @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
|
@@ -16,6 +16,12 @@ module Loxxy
|
|
16
16
|
@operator = anOperator
|
17
17
|
end
|
18
18
|
|
19
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
20
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
21
|
+
def accept(visitor)
|
22
|
+
visitor.visit_binary_expr(self)
|
23
|
+
end
|
24
|
+
|
19
25
|
alias operands subnodes
|
20
26
|
end # class
|
21
27
|
end # module
|
@@ -40,6 +40,19 @@ module Loxxy
|
|
40
40
|
@ostream.print tos.to_str
|
41
41
|
end
|
42
42
|
|
43
|
+
def after_binary_expr(aBinaryExpr)
|
44
|
+
op = aBinaryExpr.operator
|
45
|
+
operand2 = stack.pop
|
46
|
+
operand1 = stack.pop
|
47
|
+
implemented = [:+].include?(op)
|
48
|
+
if implemented && operand1.respond_to?(op)
|
49
|
+
stack.push operand1.send(op, operand2)
|
50
|
+
else
|
51
|
+
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
52
|
+
raise StandardError, msg1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
43
56
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
44
57
|
def before_literal_expr(literalExpr)
|
45
58
|
stack.push(literalExpr.literal)
|
@@ -8,6 +8,17 @@ module Loxxy
|
|
8
8
|
# An instance acts merely as a wrapper around a Ruby representation
|
9
9
|
# of the value.
|
10
10
|
class Boolean < BuiltinDatatype
|
11
|
+
# Is this object representing a false value in Lox?
|
12
|
+
# @return [FalseClass, TrueClass]
|
13
|
+
def false?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
# Is this object representing the true value in Lox?
|
18
|
+
# @return [FalseClass, TrueClass]
|
19
|
+
def true?
|
20
|
+
false
|
21
|
+
end
|
11
22
|
end # class
|
12
23
|
end # module
|
13
24
|
end # module
|
data/lib/loxxy/datatype/false.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'false'
|
4
|
+
require_relative 'true'
|
4
5
|
|
5
6
|
module Loxxy
|
6
7
|
module Datatype
|
@@ -8,19 +9,35 @@ module Loxxy
|
|
8
9
|
class LXString < BuiltinDatatype
|
9
10
|
# Compare a Lox String with another Lox (or genuine Ruby) String
|
10
11
|
# @param other [Datatype::LxString, String]
|
11
|
-
# @return [Boolean]
|
12
|
+
# @return [Datatype::Boolean]
|
12
13
|
def ==(other)
|
13
14
|
case other
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
when LXString
|
16
|
+
(value == other.value) ? True.instance : False.instance
|
17
|
+
when String
|
18
|
+
(value == other) ? True.instance : False.instance
|
18
19
|
else
|
19
20
|
err_msg = "Cannot compare a #{self.class} with #{other.class}"
|
20
21
|
raise StandardError, err_msg
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
25
|
+
# Perform the concatenation of two Lox stings or
|
26
|
+
# one Lox string and a Ruby String
|
27
|
+
# @param other [Loxxy::Datatype::LXString, String]
|
28
|
+
# @return [Loxxy::Datatype::LXString]
|
29
|
+
def +(other)
|
30
|
+
case other
|
31
|
+
when LXString
|
32
|
+
self.class.new(value + other.value)
|
33
|
+
when Numeric
|
34
|
+
self.class.new(value + other)
|
35
|
+
else
|
36
|
+
err_msg = "`+': #{other.class} can't be coerced into #{self.class} (TypeError)"
|
37
|
+
raise TypeError, err_msg
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
24
41
|
# Method called from Lox to obtain the text representation of the object.
|
25
42
|
# @return [String]
|
26
43
|
def to_str
|
@@ -1,11 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'false'
|
4
|
+
require_relative 'true'
|
4
5
|
|
5
6
|
module Loxxy
|
6
7
|
module Datatype
|
7
8
|
# Class for representing a Lox numeric value.
|
8
9
|
class Number < BuiltinDatatype
|
10
|
+
# Compare a Lox Number with another Lox (or genuine Ruby) Number
|
11
|
+
# @param other [Datatype::Number, Numeric]
|
12
|
+
# @return [Datatype::Boolean]
|
13
|
+
def ==(other)
|
14
|
+
case other
|
15
|
+
when Number
|
16
|
+
(value == other.value) ? True.instance : False.instance
|
17
|
+
when Numeric
|
18
|
+
(value == other) ? True.instance : False.instance
|
19
|
+
else
|
20
|
+
err_msg = "Cannot compare a #{self.class} with #{other.class}"
|
21
|
+
raise StandardError, err_msg
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Perform the addition of two Lox numbers or
|
26
|
+
# one Lox number and a Ruby Numeric
|
27
|
+
# @param numeric [Loxxy::Datatype::Number, Numeric]
|
28
|
+
# @return [Loxxy::Datatype::Number]
|
29
|
+
def +(numeric)
|
30
|
+
case numeric
|
31
|
+
when Number
|
32
|
+
self.class.new(value + numeric.value)
|
33
|
+
when Numeric
|
34
|
+
self.class.new(value + numeric)
|
35
|
+
else
|
36
|
+
err_msg = "`+': #{numeric.class} can't be coerced into #{self.class} (TypeError)"
|
37
|
+
raise TypeError, err_msg
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
9
41
|
protected
|
10
42
|
|
11
43
|
def validated_value(aValue)
|
data/lib/loxxy/datatype/true.rb
CHANGED
data/lib/loxxy/version.rb
CHANGED
@@ -26,12 +26,30 @@ module Loxxy
|
|
26
26
|
expect(subject.to_str).to eq(sample_text)
|
27
27
|
end
|
28
28
|
|
29
|
-
it 'compares with another string' do
|
30
|
-
|
31
|
-
expect(
|
29
|
+
it 'compares with another Lox string' do
|
30
|
+
result = subject == LXString.new(sample_text.dup)
|
31
|
+
expect(result).to be_true
|
32
32
|
|
33
|
-
|
34
|
-
expect(
|
33
|
+
result = subject == LXString.new('other-text')
|
34
|
+
expect(result).to be_false
|
35
|
+
|
36
|
+
result = subject == LXString.new('')
|
37
|
+
expect(result).to be_false
|
38
|
+
|
39
|
+
# Two empty strings are equal
|
40
|
+
result = LXString.new('') == LXString.new('')
|
41
|
+
expect(result).to be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'compares with a Ruby string' do
|
45
|
+
result = subject == sample_text.dup
|
46
|
+
expect(result).to be_true
|
47
|
+
|
48
|
+
result = subject == 'other-text'
|
49
|
+
expect(result).to be_false
|
50
|
+
|
51
|
+
result = subject == ''
|
52
|
+
expect(result).to be_false
|
35
53
|
end
|
36
54
|
end
|
37
55
|
end # describe
|
@@ -22,6 +22,22 @@ module Loxxy
|
|
22
22
|
end
|
23
23
|
|
24
24
|
context 'Provided services:' do
|
25
|
+
it 'should compare with other Lox numbers' do
|
26
|
+
result = subject == Number.new(sample_value)
|
27
|
+
expect(result).to be_true
|
28
|
+
|
29
|
+
result = subject == Number.new(5)
|
30
|
+
expect(result).to be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should compare with Ruby numbers' do
|
34
|
+
result = subject == sample_value
|
35
|
+
expect(result).to be_true
|
36
|
+
|
37
|
+
result = subject == 5
|
38
|
+
expect(result).to be_false
|
39
|
+
end
|
40
|
+
|
25
41
|
it 'should give its display representation' do
|
26
42
|
expect(subject.to_str).to eq(sample_value.to_s)
|
27
43
|
end
|
data/spec/interpreter_spec.rb
CHANGED
@@ -33,6 +33,18 @@ module Loxxy
|
|
33
33
|
expect(result).to be_kind_of(Loxxy::Datatype::True)
|
34
34
|
end
|
35
35
|
|
36
|
+
it 'should evaluate arithmetic operation' do
|
37
|
+
result = subject.evaluate('123 + 456; // => 579')
|
38
|
+
expect(result).to be_kind_of(Loxxy::Datatype::Number)
|
39
|
+
expect(result == 579).to be_true
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should evaluate string concatenation' do
|
43
|
+
result = subject.evaluate('"str" + "ing"; // => "string"')
|
44
|
+
expect(result).to be_kind_of(Loxxy::Datatype::LXString)
|
45
|
+
expect(result == 'string').to be_true
|
46
|
+
end
|
47
|
+
|
36
48
|
it 'should print the hello world message' do
|
37
49
|
expect { subject.evaluate(hello_world) }.not_to raise_error
|
38
50
|
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|