loxxy 0.0.12 → 0.0.17
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/.rubocop.yml +1 -1
- data/CHANGELOG.md +76 -0
- data/README.md +132 -13
- data/lib/loxxy.rb +5 -5
- data/lib/loxxy/ast/all_lox_nodes.rb +3 -1
- data/lib/loxxy/ast/ast_builder.rb +19 -3
- data/lib/loxxy/ast/ast_visitor.rb +17 -1
- data/lib/loxxy/ast/lox_binary_expr.rb +7 -1
- data/lib/loxxy/ast/lox_print_stmt.rb +0 -1
- data/lib/loxxy/ast/lox_unary_expr.rb +27 -0
- data/lib/loxxy/back_end/engine.rb +25 -1
- data/lib/loxxy/datatype/boolean.rb +11 -0
- data/lib/loxxy/datatype/builtin_datatype.rb +28 -0
- data/lib/loxxy/datatype/false.rb +28 -0
- data/lib/loxxy/datatype/lx_string.rb +26 -10
- data/lib/loxxy/datatype/nil.rb +22 -0
- data/lib/loxxy/datatype/number.rb +132 -1
- data/lib/loxxy/datatype/true.rb +14 -0
- data/lib/loxxy/front_end/grammar.rb +2 -2
- data/lib/loxxy/interpreter.rb +3 -1
- data/lib/loxxy/version.rb +1 -1
- data/spec/datatype/lx_string_spec.rb +28 -5
- data/spec/datatype/number_spec.rb +26 -0
- data/spec/front_end/parser_spec.rb +18 -57
- data/spec/interpreter_spec.rb +136 -0
- metadata +3 -2
@@ -22,9 +22,10 @@ module Loxxy
|
|
22
22
|
@stack = []
|
23
23
|
end
|
24
24
|
|
25
|
-
# Given an abstract syntax parse tree visitor,
|
25
|
+
# Given an abstract syntax parse tree visitor, launch the visit
|
26
26
|
# and execute the visit events in the output stream.
|
27
27
|
# @param aVisitor [AST::ASTVisitor]
|
28
|
+
# @return [Loxxy::Datatype::BuiltinDatatype]
|
28
29
|
def execute(aVisitor)
|
29
30
|
aVisitor.subscribe(self)
|
30
31
|
aVisitor.start
|
@@ -39,6 +40,29 @@ module Loxxy
|
|
39
40
|
@ostream.print tos.to_str
|
40
41
|
end
|
41
42
|
|
43
|
+
def after_binary_expr(aBinaryExpr)
|
44
|
+
op = aBinaryExpr.operator
|
45
|
+
operand2 = stack.pop
|
46
|
+
operand1 = stack.pop
|
47
|
+
if operand1.respond_to?(op)
|
48
|
+
stack.push operand1.send(op, operand2)
|
49
|
+
else
|
50
|
+
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
51
|
+
raise StandardError, msg1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def after_unary_expr(anUnaryExpr)
|
56
|
+
op = anUnaryExpr.operator
|
57
|
+
operand = stack.pop
|
58
|
+
if operand.respond_to?(op)
|
59
|
+
stack.push operand.send(op)
|
60
|
+
else
|
61
|
+
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
62
|
+
raise StandardError, msg1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
42
66
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
43
67
|
def before_literal_expr(literalExpr)
|
44
68
|
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
|
@@ -14,6 +14,34 @@ module Loxxy
|
|
14
14
|
@value = validated_value(aValue)
|
15
15
|
end
|
16
16
|
|
17
|
+
# Is the value considered falsey in Lox?
|
18
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
19
|
+
# This test used in conditional statements (i.e. if, while)
|
20
|
+
def falsey?
|
21
|
+
false # Default implementation
|
22
|
+
end
|
23
|
+
|
24
|
+
# Is the value considered truthy in Lox?
|
25
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
26
|
+
# This test used in conditional statements (i.e. if, while)
|
27
|
+
def truthy?
|
28
|
+
true # Default implementation
|
29
|
+
end
|
30
|
+
|
31
|
+
# Negation ('not')
|
32
|
+
# Returns a boolean with opposite truthiness value.
|
33
|
+
# @return [Datatype::Boolean]
|
34
|
+
def !
|
35
|
+
falsey? ? True.instance : False.instance
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check for inequality of this object with another Lox object
|
39
|
+
# @param other [Datatype::BuiltinDatatype, Object]
|
40
|
+
# @return [Datatype::Boolean]
|
41
|
+
def !=(other)
|
42
|
+
!(self == other)
|
43
|
+
end
|
44
|
+
|
17
45
|
# Method called from Lox to obtain the text representation of the boolean.
|
18
46
|
# @return [String]
|
19
47
|
def to_str
|
data/lib/loxxy/datatype/false.rb
CHANGED
@@ -13,6 +13,34 @@ module Loxxy
|
|
13
13
|
def initialize
|
14
14
|
super(false)
|
15
15
|
end
|
16
|
+
|
17
|
+
# Is this object representing a false value in Lox?
|
18
|
+
# @return [TrueClass]
|
19
|
+
def false?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
# Is the value considered falsey in Lox?
|
24
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
25
|
+
# This test used in conditional statements (i.e. if, while)
|
26
|
+
def falsey?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
# Is the value considered truthy in Lox?
|
31
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
32
|
+
# This test used in conditional statements (i.e. if, while)
|
33
|
+
def truthy?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check for equality of a Lox False with another Lox object
|
38
|
+
# @param other [Datatype::BuiltinDatatype, FalseClass, Object]
|
39
|
+
# @return [Datatype::Boolean]
|
40
|
+
def ==(other)
|
41
|
+
falsey = other.kind_of?(False) || other.kind_of?(FalseClass)
|
42
|
+
falsey ? True.instance : False.instance
|
43
|
+
end
|
16
44
|
end # class
|
17
45
|
|
18
46
|
False.instance.freeze # Make the sole instance immutable
|
@@ -1,23 +1,39 @@
|
|
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 string of characters value.
|
8
9
|
class LXString < BuiltinDatatype
|
9
|
-
#
|
10
|
-
# @param other [Datatype::LxString, String]
|
11
|
-
# @return [Boolean]
|
10
|
+
# Check the equality of a Lox String with another Lox object.
|
11
|
+
# @param other [Datatype::LxString, String, Object]
|
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
|
-
|
20
|
+
False.instance
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Perform the concatenation of two Lox stings or
|
25
|
+
# one Lox string and a Ruby String
|
26
|
+
# @param other [Loxxy::Datatype::LXString, String]
|
27
|
+
# @return [Loxxy::Datatype::LXString]
|
28
|
+
def +(other)
|
29
|
+
case other
|
30
|
+
when LXString
|
31
|
+
self.class.new(value + other.value)
|
32
|
+
when Numeric
|
33
|
+
self.class.new(value + other)
|
34
|
+
else
|
35
|
+
err_msg = "`+': #{other.class} can't be coerced into #{self.class} (TypeError)"
|
36
|
+
raise TypeError, err_msg
|
21
37
|
end
|
22
38
|
end
|
23
39
|
|
data/lib/loxxy/datatype/nil.rb
CHANGED
@@ -14,6 +14,28 @@ module Loxxy
|
|
14
14
|
super(nil)
|
15
15
|
end
|
16
16
|
|
17
|
+
# Check the equality with another object.
|
18
|
+
# @param other [Datatype::BuiltinDatatype, NilClass, Object]
|
19
|
+
# @return [Datatype::Boolean]
|
20
|
+
def ==(other)
|
21
|
+
is_nil = other.kind_of?(Nil) || other.kind_of?(NilClass)
|
22
|
+
is_nil ? True.instance : False.instance
|
23
|
+
end
|
24
|
+
|
25
|
+
# Is the value considered falsey in Lox?
|
26
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
27
|
+
# This test used in conditional statements (i.e. if, while)
|
28
|
+
def falsey?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Is the value considered truthy in Lox?
|
33
|
+
# Rule: false and nil are falsey and everything else is truthy.
|
34
|
+
# This test used in conditional statements (i.e. if, while)
|
35
|
+
def truthy?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
17
39
|
# Method called from Lox to obtain the text representation of nil.
|
18
40
|
# @return [String]
|
19
41
|
def to_str
|
@@ -1,11 +1,142 @@
|
|
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
|
+
# Perform the addition of two Lox numbers or
|
11
|
+
# one Lox number and a Ruby Numeric
|
12
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
13
|
+
# @return [Loxxy::Datatype::Number]
|
14
|
+
def +(other)
|
15
|
+
case other
|
16
|
+
when Number
|
17
|
+
self.class.new(value + other.value)
|
18
|
+
when Numeric
|
19
|
+
self.class.new(value + other)
|
20
|
+
else
|
21
|
+
err_msg = "`+': #{other.class} can't be coerced into #{self.class} (TypeError)"
|
22
|
+
raise TypeError, err_msg
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Perform the subtraction of two Lox numbers or
|
27
|
+
# one Lox number and a Ruby Numeric
|
28
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
29
|
+
# @return [Loxxy::Datatype::Number]
|
30
|
+
def -(other)
|
31
|
+
case other
|
32
|
+
when Number
|
33
|
+
self.class.new(value - other.value)
|
34
|
+
when Numeric
|
35
|
+
self.class.new(value - other)
|
36
|
+
else
|
37
|
+
err_msg = "`-': #{other.class} can't be coerced into #{self.class} (TypeError)"
|
38
|
+
raise TypeError, err_msg
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Perform the multiplication of two Lox numbers or
|
43
|
+
# one Lox number and a Ruby Numeric
|
44
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
45
|
+
# @return [Loxxy::Datatype::Number]
|
46
|
+
def *(other)
|
47
|
+
case other
|
48
|
+
when Number
|
49
|
+
self.class.new(value * other.value)
|
50
|
+
when Numeric
|
51
|
+
self.class.new(value * other)
|
52
|
+
else
|
53
|
+
err_msg = "'*': Operands must be numbers."
|
54
|
+
raise TypeError, err_msg
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Perform the division of two Lox numbers or
|
59
|
+
# one Lox number and a Ruby Numeric
|
60
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
61
|
+
# @return [Loxxy::Datatype::Number]
|
62
|
+
def /(other)
|
63
|
+
case other
|
64
|
+
when Number
|
65
|
+
self.class.new(value / other.value)
|
66
|
+
when Numeric
|
67
|
+
self.class.new(value / other)
|
68
|
+
else
|
69
|
+
err_msg = "'/': Operands must be numbers."
|
70
|
+
raise TypeError, err_msg
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Unary minus (return value with changed sign)
|
75
|
+
# @return [Loxxy::Datatype::Number]
|
76
|
+
def -@
|
77
|
+
self.class.new(-value)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Check the equality of a Lox number object with another object
|
81
|
+
# @param other [Datatype::BuiltinDatatype, Numeric, Object]
|
82
|
+
# @return [Datatype::Boolean]
|
83
|
+
def ==(other)
|
84
|
+
case other
|
85
|
+
when Number
|
86
|
+
(value == other.value) ? True.instance : False.instance
|
87
|
+
when Numeric
|
88
|
+
(value == other) ? True.instance : False.instance
|
89
|
+
else
|
90
|
+
False.instance
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Check whether this Lox number has a greater value than given argument.
|
95
|
+
# @param other [Datatype::Number, Numeric]
|
96
|
+
# @return [Datatype::Boolean]
|
97
|
+
def >(other)
|
98
|
+
case other
|
99
|
+
when Number
|
100
|
+
(value > other.value) ? True.instance : False.instance
|
101
|
+
when Numeric
|
102
|
+
(value > other) ? True.instance : False.instance
|
103
|
+
else
|
104
|
+
msg = "'>': Operands must be numbers."
|
105
|
+
raise StandardError, msg
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Check whether this Lox number has a greater or equal value
|
110
|
+
# than given argument.
|
111
|
+
# @param other [Datatype::Number, Numeric]
|
112
|
+
# @return [Datatype::Boolean]
|
113
|
+
def >=(other)
|
114
|
+
case other
|
115
|
+
when Number
|
116
|
+
(value >= other.value) ? True.instance : False.instance
|
117
|
+
when Numeric
|
118
|
+
(value >= other) ? True.instance : False.instance
|
119
|
+
else
|
120
|
+
msg = "'>': Operands must be numbers."
|
121
|
+
raise StandardError, msg
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Check whether this Lox number has a lesser value than given argument.
|
126
|
+
# @param other [Datatype::Number, Numeric]
|
127
|
+
# @return [Datatype::Boolean]
|
128
|
+
def <(other)
|
129
|
+
!(self >= other)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Check whether this Lox number has a lesser or equal value
|
133
|
+
# than given argument.
|
134
|
+
# @param other [Datatype::Number, Numeric]
|
135
|
+
# @return [Datatype::Boolean]
|
136
|
+
def <=(other)
|
137
|
+
!(self > other)
|
138
|
+
end
|
139
|
+
|
9
140
|
protected
|
10
141
|
|
11
142
|
def validated_value(aValue)
|
data/lib/loxxy/datatype/true.rb
CHANGED
@@ -13,6 +13,20 @@ module Loxxy
|
|
13
13
|
def initialize
|
14
14
|
super(true)
|
15
15
|
end
|
16
|
+
|
17
|
+
# Is this object representing the true value in Lox?
|
18
|
+
# @return [TrueClass]
|
19
|
+
def true?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check for equality of a Lox True with another Lox object / Ruby true
|
24
|
+
# @param other [Datatype::True, TrueClass, Object]
|
25
|
+
# @return [Datatype::Boolean]
|
26
|
+
def ==(other)
|
27
|
+
thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
|
28
|
+
thruthy ? True.instance : False.instance
|
29
|
+
end
|
16
30
|
end # class
|
17
31
|
|
18
32
|
True.instance.freeze # Make the sole instance immutable
|
@@ -58,7 +58,7 @@ module Loxxy
|
|
58
58
|
rule('statement' => 'whileStmt')
|
59
59
|
rule('statement' => 'block')
|
60
60
|
|
61
|
-
rule('exprStmt' => 'expression SEMICOLON')
|
61
|
+
rule('exprStmt' => 'expression SEMICOLON').as 'exprStmt'
|
62
62
|
|
63
63
|
rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
|
64
64
|
rule('forControl' => 'forInitialization forTest forUpdate')
|
@@ -121,7 +121,7 @@ module Loxxy
|
|
121
121
|
rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
|
122
122
|
rule('multOp' => 'SLASH')
|
123
123
|
rule('multOp' => 'STAR')
|
124
|
-
rule('unary' => 'unaryOp unary')
|
124
|
+
rule('unary' => 'unaryOp unary').as 'unary_expr'
|
125
125
|
rule('unary' => 'call')
|
126
126
|
rule('unaryOp' => 'BANG')
|
127
127
|
rule('unaryOp' => 'MINUS')
|
data/lib/loxxy/interpreter.rb
CHANGED
@@ -19,8 +19,10 @@ module Loxxy
|
|
19
19
|
@config = theOptions
|
20
20
|
end
|
21
21
|
|
22
|
-
# Evaluate the given Lox program
|
22
|
+
# Evaluate the given Lox program.
|
23
|
+
# Return the result of the last executed expression (if any)
|
23
24
|
# @param lox_input [String] Lox program to evaluate
|
25
|
+
# @return [Loxxy::Datatype::BuiltinDatatype]
|
24
26
|
def evaluate(lox_input)
|
25
27
|
# Front-end scans, parses the input and blurps an AST...
|
26
28
|
parser = FrontEnd::Parser.new
|
data/lib/loxxy/version.rb
CHANGED
@@ -26,12 +26,35 @@ 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
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'performs the concatenation with another string' do
|
56
|
+
concatenation = LXString.new('str') + LXString.new('ing')
|
57
|
+
expect(concatenation == 'string').to be_true
|
35
58
|
end
|
36
59
|
end
|
37
60
|
end # describe
|