loxxy 0.0.15 → 0.0.20
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 +69 -1
- data/README.md +140 -48
- data/lib/loxxy/ast/all_lox_nodes.rb +6 -1
- data/lib/loxxy/ast/ast_builder.rb +44 -3
- data/lib/loxxy/ast/ast_visitor.rb +54 -2
- data/lib/loxxy/ast/lox_binary_expr.rb +1 -1
- data/lib/loxxy/ast/lox_grouping_expr.rb +23 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +35 -0
- data/lib/loxxy/ast/lox_logical_expr.rb +28 -0
- data/lib/loxxy/ast/lox_unary_expr.rb +27 -0
- data/lib/loxxy/back_end/engine.rb +60 -3
- data/lib/loxxy/datatype/builtin_datatype.rb +55 -0
- data/lib/loxxy/datatype/false.rb +22 -0
- data/lib/loxxy/datatype/lx_string.rb +3 -4
- data/lib/loxxy/datatype/nil.rb +22 -0
- data/lib/loxxy/datatype/number.rb +98 -15
- data/lib/loxxy/datatype/true.rb +8 -0
- data/lib/loxxy/front_end/grammar.rb +5 -5
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/parser_spec.rb +4 -4
- data/spec/interpreter_spec.rb +208 -9
- metadata +6 -2
@@ -5,7 +5,7 @@ require_relative 'lox_compound_expr'
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
7
|
class LoxBinaryExpr < LoxCompoundExpr
|
8
|
-
# @return [Symbol]
|
8
|
+
# @return [Symbol] message name to be sent to receiver
|
9
9
|
attr_reader :operator
|
10
10
|
|
11
11
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxGroupingExpr < LoxCompoundExpr
|
8
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
+
# @param subExpr [Loxxy::Ast::LoxNode]
|
10
|
+
def initialize(aPosition, subExpr)
|
11
|
+
super(aPosition, [subExpr])
|
12
|
+
end
|
13
|
+
|
14
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
15
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
16
|
+
def accept(visitor)
|
17
|
+
visitor.visit_grouping_expr(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
alias operands subnodes
|
21
|
+
end # class
|
22
|
+
end # module
|
23
|
+
end # module
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxIfStmt < LoxCompoundExpr
|
8
|
+
# @return [LoxNode] code of then branch
|
9
|
+
attr_reader :then_stmt
|
10
|
+
|
11
|
+
# @return [LoxNode, NilClass] code of else branch
|
12
|
+
attr_reader :else_stmt
|
13
|
+
|
14
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
|
+
# @param subExpr [Loxxy::Ast::LoxNode]
|
16
|
+
def initialize(aPosition, condExpr, thenStmt, elseStmt)
|
17
|
+
super(aPosition, [condExpr])
|
18
|
+
@then_stmt = thenStmt
|
19
|
+
@else_stmt = elseStmt
|
20
|
+
end
|
21
|
+
|
22
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
23
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
24
|
+
def accept(visitor)
|
25
|
+
visitor.visit_if_stmt(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Accessor to the condition expression
|
29
|
+
# @return [LoxNode]
|
30
|
+
def condition
|
31
|
+
subnodes[0]
|
32
|
+
end
|
33
|
+
end # class
|
34
|
+
end # module
|
35
|
+
end # module
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxLogicalExpr < LoxCompoundExpr
|
8
|
+
# @return [Symbol] message name to be sent to receiver
|
9
|
+
attr_reader :operator
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param operand1 [Loxxy::Ast::LoxNode]
|
13
|
+
# @param operand2 [Loxxy::Ast::LoxNode]
|
14
|
+
def initialize(aPosition, anOperator, operand1, operand2)
|
15
|
+
super(aPosition, [operand1, operand2])
|
16
|
+
@operator = anOperator
|
17
|
+
end
|
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_logical_expr(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
alias operands subnodes
|
26
|
+
end # class
|
27
|
+
end # module
|
28
|
+
end # module
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxUnaryExpr < LoxCompoundExpr
|
8
|
+
# @return [Symbol] message name to be sent to receiver
|
9
|
+
attr_reader :operator
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param operand [Loxxy::Ast::LoxNode]
|
13
|
+
def initialize(aPosition, anOperator, operand)
|
14
|
+
super(aPosition, [operand])
|
15
|
+
@operator = anOperator
|
16
|
+
end
|
17
|
+
|
18
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
19
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
20
|
+
def accept(visitor)
|
21
|
+
visitor.visit_unary_expr(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
alias operands subnodes
|
25
|
+
end # class
|
26
|
+
end # module
|
27
|
+
end # module
|
@@ -22,7 +22,7 @@ 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
28
|
# @return [Loxxy::Datatype::BuiltinDatatype]
|
@@ -33,19 +33,61 @@ module Loxxy
|
|
33
33
|
stack.empty? ? Datatype::Nil.instance : stack.pop
|
34
34
|
end
|
35
35
|
|
36
|
+
##########################################################################
|
36
37
|
# Visit event handling
|
38
|
+
##########################################################################
|
39
|
+
def after_if_stmt(anIfStmt, aVisitor)
|
40
|
+
# Retrieve the result of the condition evaluation
|
41
|
+
condition = stack.pop
|
42
|
+
if condition.truthy?
|
43
|
+
result = anIfStmt.then_stmt.accept(aVisitor)
|
44
|
+
elsif anIfStmt.else_stmt
|
45
|
+
anIfStmt.else_stmt.accept(aVisitor)
|
46
|
+
end
|
47
|
+
end
|
37
48
|
|
38
49
|
def after_print_stmt(_printStmt)
|
39
50
|
tos = stack.pop
|
40
51
|
@ostream.print tos.to_str
|
41
52
|
end
|
42
53
|
|
54
|
+
def after_logical_expr(aLogicalExpr, visitor)
|
55
|
+
op = aLogicalExpr.operator
|
56
|
+
operand1 = stack.pop # only first operand was evaluated
|
57
|
+
result = nil
|
58
|
+
if ((op == :and) && operand1.falsey?) || ((op == :or) && operand1.truthy?)
|
59
|
+
result = operand1
|
60
|
+
else
|
61
|
+
raw_operand2 = aLogicalExpr.subnodes[1]
|
62
|
+
raw_operand2.accept(visitor) # Visit means operand2 is evaluated
|
63
|
+
operand2 = stack.pop
|
64
|
+
result = logical_2nd_arg(operand2)
|
65
|
+
end
|
66
|
+
|
67
|
+
stack.push result
|
68
|
+
end
|
69
|
+
|
70
|
+
def logical_2nd_arg(operand2)
|
71
|
+
case operand2
|
72
|
+
when false
|
73
|
+
False.instance # Convert to Lox equivalent
|
74
|
+
when nil
|
75
|
+
Nil.instance # Convert to Lox equivalent
|
76
|
+
when true
|
77
|
+
True.instance # Convert to Lox equivalent
|
78
|
+
when Proc
|
79
|
+
# Second operand wasn't yet evaluated...
|
80
|
+
operand2.call
|
81
|
+
else
|
82
|
+
operand2
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
43
86
|
def after_binary_expr(aBinaryExpr)
|
44
87
|
op = aBinaryExpr.operator
|
45
88
|
operand2 = stack.pop
|
46
89
|
operand1 = stack.pop
|
47
|
-
|
48
|
-
if implemented && operand1.respond_to?(op)
|
90
|
+
if operand1.respond_to?(op)
|
49
91
|
stack.push operand1.send(op, operand2)
|
50
92
|
else
|
51
93
|
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
@@ -53,6 +95,21 @@ module Loxxy
|
|
53
95
|
end
|
54
96
|
end
|
55
97
|
|
98
|
+
def after_unary_expr(anUnaryExpr)
|
99
|
+
op = anUnaryExpr.operator
|
100
|
+
operand = stack.pop
|
101
|
+
if operand.respond_to?(op)
|
102
|
+
stack.push operand.send(op)
|
103
|
+
else
|
104
|
+
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
105
|
+
raise StandardError, msg1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def after_grouping_expr(_groupingExpr)
|
110
|
+
# Do nothing: work was already done by visiting /evaluating the subexpression
|
111
|
+
end
|
112
|
+
|
56
113
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
57
114
|
def before_literal_expr(literalExpr)
|
58
115
|
stack.push(literalExpr.literal)
|
@@ -14,6 +14,48 @@ 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
|
+
# Check for inequality of this object with another Lox object
|
32
|
+
# @param other [Datatype::BuiltinDatatype, Object]
|
33
|
+
# @return [Datatype::Boolean]
|
34
|
+
def !=(other)
|
35
|
+
!(self == other)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Negation ('not')
|
39
|
+
# Returns a boolean with opposite truthiness value.
|
40
|
+
# @return [Datatype::Boolean]
|
41
|
+
def !
|
42
|
+
falsey? ? True.instance : False.instance
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the first falsey argument (if any),
|
46
|
+
# otherwise returns the last truthy argument.
|
47
|
+
# @param operand2 [Loxxy::Datatype::BuiltinDatatype, Proc]
|
48
|
+
def and(operand2)
|
49
|
+
falsey? ? self : logical_2nd_arg(operand2)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the first truthy argument (if any),
|
53
|
+
# otherwise returns the last falsey argument.
|
54
|
+
# @param operand2 [Loxxy::Datatype::BuiltinDatatype, Proc]
|
55
|
+
def or(operand2)
|
56
|
+
truthy? ? self : logical_2nd_arg(operand2)
|
57
|
+
end
|
58
|
+
|
17
59
|
# Method called from Lox to obtain the text representation of the boolean.
|
18
60
|
# @return [String]
|
19
61
|
def to_str
|
@@ -25,6 +67,19 @@ module Loxxy
|
|
25
67
|
def validated_value(aValue)
|
26
68
|
aValue
|
27
69
|
end
|
70
|
+
|
71
|
+
def logical_2nd_arg(operand2)
|
72
|
+
case operand2
|
73
|
+
when false
|
74
|
+
False.instance # Convert to Lox equivalent
|
75
|
+
when nil
|
76
|
+
Nil.instance # Convert to Lox equivalent
|
77
|
+
when true
|
78
|
+
True.instance # Convert to Lox equivalent
|
79
|
+
else
|
80
|
+
operand2
|
81
|
+
end
|
82
|
+
end
|
28
83
|
end # class
|
29
84
|
end # module
|
30
85
|
end # module
|
data/lib/loxxy/datatype/false.rb
CHANGED
@@ -19,6 +19,28 @@ module Loxxy
|
|
19
19
|
def false?
|
20
20
|
true
|
21
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
|
22
44
|
end # class
|
23
45
|
|
24
46
|
False.instance.freeze # Make the sole instance immutable
|
@@ -7,8 +7,8 @@ module Loxxy
|
|
7
7
|
module Datatype
|
8
8
|
# Class for representing a Lox string of characters value.
|
9
9
|
class LXString < BuiltinDatatype
|
10
|
-
#
|
11
|
-
# @param other [Datatype::LxString, String]
|
10
|
+
# Check the equality of a Lox String with another Lox object.
|
11
|
+
# @param other [Datatype::LxString, String, Object]
|
12
12
|
# @return [Datatype::Boolean]
|
13
13
|
def ==(other)
|
14
14
|
case other
|
@@ -17,8 +17,7 @@ module Loxxy
|
|
17
17
|
when String
|
18
18
|
(value == other) ? True.instance : False.instance
|
19
19
|
else
|
20
|
-
|
21
|
-
raise StandardError, err_msg
|
20
|
+
False.instance
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
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
|
@@ -7,21 +7,6 @@ module Loxxy
|
|
7
7
|
module Datatype
|
8
8
|
# Class for representing a Lox numeric value.
|
9
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
10
|
# Perform the addition of two Lox numbers or
|
26
11
|
# one Lox number and a Ruby Numeric
|
27
12
|
# @param other [Loxxy::Datatype::Number, Numeric]
|
@@ -54,6 +39,104 @@ module Loxxy
|
|
54
39
|
end
|
55
40
|
end
|
56
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
|
+
|
57
140
|
protected
|
58
141
|
|
59
142
|
def validated_value(aValue)
|