loxxy 0.0.10 → 0.0.15
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 +64 -0
- data/README.md +152 -55
- data/lib/loxxy.rb +23 -2
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -0
- data/lib/loxxy/ast/ast_builder.rb +82 -26
- 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 +6 -0
- data/lib/loxxy/datatype/lx_string.rb +29 -6
- data/lib/loxxy/datatype/nil.rb +6 -0
- data/lib/loxxy/datatype/number.rb +49 -1
- data/lib/loxxy/datatype/true.rb +6 -0
- data/lib/loxxy/front_end/grammar.rb +10 -10
- 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 +65 -65
- data/spec/interpreter_spec.rb +60 -0
- metadata +17 -2
@@ -39,75 +39,63 @@ module Loxxy
|
|
39
39
|
top.accept(self)
|
40
40
|
end
|
41
41
|
|
42
|
-
# Visit event. The visitor is
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
broadcast(:before_literal_expr, aLiteralExpr)
|
47
|
-
broadcast(:after_literal_expr, aLiteralExpr)
|
42
|
+
# Visit event. The visitor is about to visit the ptree.
|
43
|
+
# @param aParseTree [Rley::PTre::ParseTree] the ptree to visit.
|
44
|
+
def start_visit_ptree(aParseTree)
|
45
|
+
broadcast(:before_ptree, aParseTree)
|
48
46
|
end
|
49
47
|
|
50
|
-
|
48
|
+
# Visit event. The visitor has completed the visit of the ptree.
|
49
|
+
# @param aParseTree [Rley::PTre::ParseTree] the visited ptree.
|
50
|
+
def end_visit_ptree(aParseTree)
|
51
|
+
broadcast(:after_ptree, aParseTree)
|
52
|
+
end
|
51
53
|
|
54
|
+
# Visit event. The visitor is about to visit a print statement expression.
|
55
|
+
# @param aPrintStmt [AST::LOXPrintStmt] the print statement node to visit
|
56
|
+
def visit_print_stmt(aPrintStmt)
|
57
|
+
broadcast(:before_print_stmt, aPrintStmt)
|
58
|
+
traverse_subnodes(aPrintStmt)
|
59
|
+
broadcast(:after_print_stmt, aPrintStmt)
|
60
|
+
end
|
52
61
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
broadcast(:
|
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)
|
57
68
|
end
|
58
69
|
|
59
70
|
# Visit event. The visitor is visiting the
|
60
|
-
# given
|
61
|
-
# @param
|
62
|
-
def
|
63
|
-
broadcast(:
|
64
|
-
broadcast(:
|
71
|
+
# given terminal node containing a datatype object.
|
72
|
+
# @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
|
73
|
+
def visit_literal_expr(aLiteralExpr)
|
74
|
+
broadcast(:before_literal_expr, aLiteralExpr)
|
75
|
+
broadcast(:after_literal_expr, aLiteralExpr)
|
65
76
|
end
|
66
77
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
=begin
|
74
|
-
# Visit event. The visitor is about to visit the given non terminal node.
|
75
|
-
# @param aNonTerminalNode [NonTerminalNode] the node to visit.
|
76
|
-
def visit_nonterminal(aNonTerminalNode)
|
77
|
-
if @traversal == :post_order
|
78
|
-
broadcast(:before_non_terminal, aNonTerminalNode)
|
79
|
-
traverse_subnodes(aNonTerminalNode)
|
80
|
-
else
|
81
|
-
traverse_subnodes(aNonTerminalNode)
|
82
|
-
broadcast(:before_non_terminal, aNonTerminalNode)
|
78
|
+
# Visit event. The visitor is about to visit the given non terminal node.
|
79
|
+
# @param aNonTerminalNode [Rley::PTre::NonTerminalNode] the node to visit.
|
80
|
+
def visit_nonterminal(_non_terminal_node)
|
81
|
+
# Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
|
82
|
+
# that it cannot handle.
|
83
|
+
raise NotImplementedError, 'Loxxy cannot execute this code yet.'
|
83
84
|
end
|
84
|
-
broadcast(:after_non_terminal, aNonTerminalNode)
|
85
|
-
end
|
86
|
-
=end
|
87
85
|
|
88
86
|
private
|
89
87
|
|
90
|
-
|
91
|
-
|
92
|
-
|
88
|
+
# Visit event. The visitor is about to visit the subnodes of a non
|
89
|
+
# terminal node.
|
90
|
+
# @param aParentNode [Ast::LocCompoundExpr] the parent node.
|
91
|
+
def traverse_subnodes(aParentNode)
|
92
|
+
subnodes = aParentNode.subnodes
|
93
|
+
broadcast(:before_subnodes, aParentNode, subnodes)
|
93
94
|
|
94
|
-
# Let's proceed with the visit of
|
95
|
-
|
95
|
+
# Let's proceed with the visit of subnodes
|
96
|
+
subnodes.each { |a_node| a_node.accept(self) }
|
96
97
|
|
97
|
-
broadcast(:
|
98
|
-
end
|
99
|
-
|
100
|
-
def traverse_car_cdr(aPair)
|
101
|
-
if aPair.car
|
102
|
-
broadcast(:before_car, aPair, aPair.car)
|
103
|
-
aPair.car.accept(self)
|
104
|
-
broadcast(:after_car, aPair, aPair.car)
|
105
|
-
end
|
106
|
-
if aPair.cdr
|
107
|
-
broadcast(:before_cdr, aPair, aPair.cdr)
|
108
|
-
aPair.cdr.accept(self)
|
109
|
-
broadcast(:after_cdr, aPair, aPair.cdr)
|
110
|
-
end
|
98
|
+
broadcast(:after_subnodes, aParentNode, subnodes)
|
111
99
|
end
|
112
100
|
|
113
101
|
# Send a notification to all subscribers.
|
@@ -117,7 +105,7 @@ module Loxxy
|
|
117
105
|
subscribers.each do |subscr|
|
118
106
|
next unless subscr.respond_to?(msg) || subscr.respond_to?(:accept_all)
|
119
107
|
|
120
|
-
subscr.send(msg,
|
108
|
+
subscr.send(msg, *args)
|
121
109
|
end
|
122
110
|
end
|
123
111
|
end # class
|
@@ -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
|
@@ -5,7 +5,7 @@ require_relative 'lox_node'
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
7
|
class LoxCompoundExpr < LoxNode
|
8
|
-
# @return [Array<Ast::LoxNode>]
|
8
|
+
# @return [Array<Ast::LoxNode>] the children nodes (sub-expressions)
|
9
9
|
attr_reader :subnodes
|
10
10
|
|
11
11
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
data/lib/loxxy/ast/lox_node.rb
CHANGED
@@ -11,6 +11,11 @@ module Loxxy
|
|
11
11
|
@position = aPosition
|
12
12
|
end
|
13
13
|
|
14
|
+
# Notification that the parsing was successfully completed
|
15
|
+
def done!
|
16
|
+
# Default: do nothing ...
|
17
|
+
end
|
18
|
+
|
14
19
|
# Abstract method.
|
15
20
|
# Part of the 'visitee' role in Visitor design pattern.
|
16
21
|
# @param _visitor [LoxxyTreeVisitor] the visitor
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_node'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxNoopExpr < LoxNode
|
8
|
+
# Abstract method.
|
9
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
10
|
+
# @param _visitor [LoxxyTreeVisitor] the visitor
|
11
|
+
def accept(_visitor)
|
12
|
+
# Do nothing...
|
13
|
+
end
|
14
|
+
end # class
|
15
|
+
end # module
|
16
|
+
end # module
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxPrintStmt < LoxCompoundExpr
|
8
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
+
# @param anExpression [Ast::LoxNode] expression to output
|
10
|
+
def initialize(aPosition, anExpression)
|
11
|
+
super(aPosition, [anExpression])
|
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_print_stmt(self)
|
18
|
+
end
|
19
|
+
end # class
|
20
|
+
end # module
|
21
|
+
end # module
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Load all the classes implementing AST nodes
|
4
|
+
require_relative '../ast/all_lox_nodes'
|
5
|
+
|
6
|
+
module Loxxy
|
7
|
+
module BackEnd
|
8
|
+
# An instance of this class executes the statements as when they
|
9
|
+
# occur during the abstract syntax tree walking.
|
10
|
+
# @note WIP: very crude implementation.
|
11
|
+
class Engine
|
12
|
+
# @return [Hash] A set of configuration options
|
13
|
+
attr_reader :config
|
14
|
+
|
15
|
+
# @return [Array] Data stack used for passing data between statements
|
16
|
+
attr_reader :stack
|
17
|
+
|
18
|
+
# @param theOptions [Hash]
|
19
|
+
def initialize(theOptions)
|
20
|
+
@config = theOptions
|
21
|
+
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
22
|
+
@stack = []
|
23
|
+
end
|
24
|
+
|
25
|
+
# Given an abstract syntax parse tree visitor, luanch the visit
|
26
|
+
# and execute the visit events in the output stream.
|
27
|
+
# @param aVisitor [AST::ASTVisitor]
|
28
|
+
# @return [Loxxy::Datatype::BuiltinDatatype]
|
29
|
+
def execute(aVisitor)
|
30
|
+
aVisitor.subscribe(self)
|
31
|
+
aVisitor.start
|
32
|
+
aVisitor.unsubscribe(self)
|
33
|
+
stack.empty? ? Datatype::Nil.instance : stack.pop
|
34
|
+
end
|
35
|
+
|
36
|
+
# Visit event handling
|
37
|
+
|
38
|
+
def after_print_stmt(_printStmt)
|
39
|
+
tos = stack.pop
|
40
|
+
@ostream.print tos.to_str
|
41
|
+
end
|
42
|
+
|
43
|
+
def after_binary_expr(aBinaryExpr)
|
44
|
+
op = aBinaryExpr.operator
|
45
|
+
operand2 = stack.pop
|
46
|
+
operand1 = stack.pop
|
47
|
+
implemented = %i[+ -].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
|
+
|
56
|
+
# @param literalExpr [Ast::LoxLiteralExpr]
|
57
|
+
def before_literal_expr(literalExpr)
|
58
|
+
stack.push(literalExpr.literal)
|
59
|
+
end
|
60
|
+
end # class
|
61
|
+
end # module
|
62
|
+
end # module
|
@@ -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,12 @@ module Loxxy
|
|
14
14
|
@value = validated_value(aValue)
|
15
15
|
end
|
16
16
|
|
17
|
+
# Method called from Lox to obtain the text representation of the boolean.
|
18
|
+
# @return [String]
|
19
|
+
def to_str
|
20
|
+
value.to_s # Default implementation...
|
21
|
+
end
|
22
|
+
|
17
23
|
protected
|
18
24
|
|
19
25
|
def validated_value(aValue)
|
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,41 @@ 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
|
+
|
41
|
+
# Method called from Lox to obtain the text representation of the object.
|
42
|
+
# @return [String]
|
43
|
+
def to_str
|
44
|
+
value
|
45
|
+
end
|
46
|
+
|
24
47
|
protected
|
25
48
|
|
26
49
|
def validated_value(aValue)
|
data/lib/loxxy/datatype/nil.rb
CHANGED
@@ -13,6 +13,12 @@ module Loxxy
|
|
13
13
|
def initialize
|
14
14
|
super(nil)
|
15
15
|
end
|
16
|
+
|
17
|
+
# Method called from Lox to obtain the text representation of nil.
|
18
|
+
# @return [String]
|
19
|
+
def to_str
|
20
|
+
'nil'
|
21
|
+
end
|
16
22
|
end # class
|
17
23
|
|
18
24
|
Nil.instance.freeze # Make the sole instance immutable
|
@@ -1,11 +1,59 @@
|
|
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 other [Loxxy::Datatype::Number, Numeric]
|
28
|
+
# @return [Loxxy::Datatype::Number]
|
29
|
+
def +(other)
|
30
|
+
case other
|
31
|
+
when Number
|
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
|
+
|
41
|
+
# Perform the subtraction of two Lox numbers or
|
42
|
+
# one Lox number and a Ruby Numeric
|
43
|
+
# @param other [Loxxy::Datatype::Number, Numeric]
|
44
|
+
# @return [Loxxy::Datatype::Number]
|
45
|
+
def -(other)
|
46
|
+
case other
|
47
|
+
when Number
|
48
|
+
self.class.new(value - other.value)
|
49
|
+
when Numeric
|
50
|
+
self.class.new(value - other)
|
51
|
+
else
|
52
|
+
err_msg = "`-': #{other.class} can't be coerced into #{self.class} (TypeError)"
|
53
|
+
raise TypeError, err_msg
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
9
57
|
protected
|
10
58
|
|
11
59
|
def validated_value(aValue)
|
data/lib/loxxy/datatype/true.rb
CHANGED