loxxy 0.0.21 → 0.0.26
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 +93 -33
- data/README.md +46 -11
- data/lib/loxxy/ast/all_lox_nodes.rb +5 -0
- data/lib/loxxy/ast/ast_builder.rb +35 -3
- data/lib/loxxy/ast/ast_visitor.rb +47 -1
- data/lib/loxxy/ast/lox_assign_expr.rb +27 -0
- data/lib/loxxy/ast/lox_block_stmt.rb +23 -0
- data/lib/loxxy/ast/lox_if_stmt.rb +1 -1
- data/lib/loxxy/ast/lox_seq_decl.rb +17 -0
- data/lib/loxxy/ast/lox_var_stmt.rb +1 -1
- data/lib/loxxy/ast/lox_variable_expr.rb +26 -0
- data/lib/loxxy/ast/lox_while_stmt.rb +32 -0
- data/lib/loxxy/back_end/engine.rb +48 -1
- data/lib/loxxy/back_end/entry.rb +2 -2
- data/lib/loxxy/back_end/environment.rb +3 -3
- data/lib/loxxy/back_end/symbol_table.rb +3 -3
- data/lib/loxxy/back_end/variable.rb +7 -2
- data/lib/loxxy/datatype/builtin_datatype.rb +6 -0
- data/lib/loxxy/front_end/grammar.rb +6 -6
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +1 -0
- data/spec/back_end/environment_spec.rb +2 -2
- data/spec/back_end/symbol_table_spec.rb +3 -3
- data/spec/back_end/variable_spec.rb +4 -4
- data/spec/front_end/parser_spec.rb +21 -19
- data/spec/interpreter_spec.rb +102 -3
- metadata +7 -2
@@ -51,6 +51,14 @@ module Loxxy
|
|
51
51
|
broadcast(:after_ptree, aParseTree)
|
52
52
|
end
|
53
53
|
|
54
|
+
# Visit event. The visitor is about to visit a variable declaration statement.
|
55
|
+
# @param aPrintStmt [AST::LOXVarStmt] the variable declaration node to visit
|
56
|
+
def visit_seq_decl(aSeqDecls)
|
57
|
+
broadcast(:before_seq_decl, aSeqDecls)
|
58
|
+
traverse_subnodes(aSeqDecls)
|
59
|
+
broadcast(:after_seq_decl, aSeqDecls)
|
60
|
+
end
|
61
|
+
|
54
62
|
# Visit event. The visitor is about to visit a variable declaration statement.
|
55
63
|
# @param aPrintStmt [AST::LOXVarStmt] the variable declaration node to visit
|
56
64
|
def visit_var_stmt(aVarStmt)
|
@@ -75,6 +83,30 @@ module Loxxy
|
|
75
83
|
broadcast(:after_print_stmt, aPrintStmt)
|
76
84
|
end
|
77
85
|
|
86
|
+
# Visit event. The visitor is about to visit a while statement node.
|
87
|
+
# @param aWhileStmt [AST::LOXWhileStmt] the while statement node to visit
|
88
|
+
def visit_while_stmt(aWhileStmt)
|
89
|
+
broadcast(:before_while_stmt, aWhileStmt)
|
90
|
+
traverse_subnodes(aWhileStmt) # The condition is visited/evaluated here...
|
91
|
+
broadcast(:after_while_stmt, aWhileStmt, self)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Visit event. The visitor is about to visit a block statement.
|
95
|
+
# @param aBlockStmt [AST::LOXBlockStmt] the print statement node to visit
|
96
|
+
def visit_block_stmt(aBlockStmt)
|
97
|
+
broadcast(:before_block_stmt, aBlockStmt)
|
98
|
+
traverse_subnodes(aBlockStmt)
|
99
|
+
broadcast(:after_block_stmt, aBlockStmt)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Visit event. The visitor is visiting an assignment node
|
103
|
+
# @param aLiteralExpr [AST::LoxAssignExpr] the variable assignment node to visit.
|
104
|
+
def visit_assign_expr(anAssignExpr)
|
105
|
+
broadcast(:before_assign_expr, anAssignExpr)
|
106
|
+
traverse_subnodes(anAssignExpr)
|
107
|
+
broadcast(:after_assign_expr, anAssignExpr)
|
108
|
+
end
|
109
|
+
|
78
110
|
# Visit event. The visitor is about to visit a logical expression.
|
79
111
|
# Since logical expressions may take shorcuts by not evaluating all their
|
80
112
|
# sub-expressiosns, they are responsible for visiting or not their children.
|
@@ -121,8 +153,22 @@ module Loxxy
|
|
121
153
|
broadcast(:after_literal_expr, aLiteralExpr)
|
122
154
|
end
|
123
155
|
|
156
|
+
# Visit event. The visitor is visiting a variable usage node
|
157
|
+
# @param aLiteralExpr [AST::LoxVariableExpr] the variable reference node to visit.
|
158
|
+
def visit_variable_expr(aVariableExpr)
|
159
|
+
broadcast(:before_variable_expr, aVariableExpr)
|
160
|
+
broadcast(:after_variable_expr, aVariableExpr, self)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Visit event. The visitor is about to visit the given terminal datatype value.
|
164
|
+
# @param aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
|
165
|
+
def visit_builtin(aValue)
|
166
|
+
broadcast(:before_visit_builtin, aValue)
|
167
|
+
broadcast(:after_visit_builtin, aValue)
|
168
|
+
end
|
169
|
+
|
124
170
|
# Visit event. The visitor is about to visit the given non terminal node.
|
125
|
-
# @param aNonTerminalNode [Rley::
|
171
|
+
# @param aNonTerminalNode [Rley::PTree::NonTerminalNode] the node to visit.
|
126
172
|
def visit_nonterminal(_non_terminal_node)
|
127
173
|
# Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
|
128
174
|
# that it cannot handle.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
# This AST node represents the assignment of a value to a variable
|
8
|
+
class LoxAssignExpr < LoxCompoundExpr
|
9
|
+
# @return [String] variable name
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
13
|
+
# @param aName [String] name of the variable
|
14
|
+
# @param aValue [Loxxy::Ast::LoxNode, NilClass] value to assign
|
15
|
+
def initialize(aPosition, aName, aValue)
|
16
|
+
super(aPosition, [aValue])
|
17
|
+
@name = aName
|
18
|
+
end
|
19
|
+
|
20
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
21
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
22
|
+
def accept(visitor)
|
23
|
+
visitor.visit_assign_expr(self)
|
24
|
+
end
|
25
|
+
end # class
|
26
|
+
end # module
|
27
|
+
end # module
|
@@ -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 LoxBlockStmt < LoxCompoundExpr
|
8
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
9
|
+
# @param operand [Loxxy::Ast::LoxSeqDecl]
|
10
|
+
def initialize(aPosition, decls)
|
11
|
+
super(aPosition, [decls])
|
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_block_stmt(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
alias operands subnodes
|
21
|
+
end # class
|
22
|
+
end # module
|
23
|
+
end # module
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxSeqDecl < LoxCompoundExpr
|
8
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
9
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
10
|
+
def accept(visitor)
|
11
|
+
visitor.visit_seq_decl(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
alias operands subnodes
|
15
|
+
end # class
|
16
|
+
end # module
|
17
|
+
end # module
|
@@ -13,7 +13,7 @@ module Loxxy
|
|
13
13
|
# @param aName [String] name of the variable
|
14
14
|
# @param aValue [Loxxy::Ast::LoxNode, NilClass] initial value for the variable
|
15
15
|
def initialize(aPosition, aName, aValue)
|
16
|
-
initial_value = aValue ? [aValue] : []
|
16
|
+
initial_value = aValue ? [aValue] : [Datatype::Nil.instance]
|
17
17
|
super(aPosition, initial_value)
|
18
18
|
@name = aName
|
19
19
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_node'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
# This AST node represents a mention of a variable
|
8
|
+
class LoxVariableExpr < LoxNode
|
9
|
+
# @return [String] variable name
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
13
|
+
# @param aName [String] name of the variable
|
14
|
+
def initialize(aPosition, aName)
|
15
|
+
super(aPosition)
|
16
|
+
@name = aName
|
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_variable_expr(self)
|
23
|
+
end
|
24
|
+
end # class
|
25
|
+
end # module
|
26
|
+
end # module
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_compound_expr'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxWhileStmt < LoxCompoundExpr
|
8
|
+
# @return [LoxNode] body of the while loop (as a statement)
|
9
|
+
attr_reader :body
|
10
|
+
|
11
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
12
|
+
# @param condExpr [Loxxy::Ast::LoxNode] iteration condition
|
13
|
+
# @param theBody [Loxxy::Ast::LoxNode]
|
14
|
+
def initialize(aPosition, condExpr, theBody)
|
15
|
+
super(aPosition, [condExpr])
|
16
|
+
@body = theBody
|
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_while_stmt(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Accessor to the condition expression
|
26
|
+
# @return [LoxNode]
|
27
|
+
def condition
|
28
|
+
subnodes[0]
|
29
|
+
end
|
30
|
+
end # class
|
31
|
+
end # module
|
32
|
+
end # module
|
@@ -41,6 +41,11 @@ module Loxxy
|
|
41
41
|
##########################################################################
|
42
42
|
# Visit event handling
|
43
43
|
##########################################################################
|
44
|
+
|
45
|
+
def after_seq_decl(aSeqDecls)
|
46
|
+
# Do nothing, subnodes were already evaluated
|
47
|
+
end
|
48
|
+
|
44
49
|
def after_var_stmt(aVarStmt)
|
45
50
|
new_var = Variable.new(aVarStmt.name, aVarStmt.subnodes[0])
|
46
51
|
symbol_table.insert(new_var)
|
@@ -50,7 +55,7 @@ module Loxxy
|
|
50
55
|
# Retrieve the result of the condition evaluation
|
51
56
|
condition = stack.pop
|
52
57
|
if condition.truthy?
|
53
|
-
|
58
|
+
anIfStmt.then_stmt.accept(aVisitor)
|
54
59
|
elsif anIfStmt.else_stmt
|
55
60
|
anIfStmt.else_stmt.accept(aVisitor)
|
56
61
|
end
|
@@ -61,6 +66,35 @@ module Loxxy
|
|
61
66
|
@ostream.print tos.to_str
|
62
67
|
end
|
63
68
|
|
69
|
+
def after_while_stmt(aWhileStmt, aVisitor)
|
70
|
+
loop do
|
71
|
+
condition = stack.pop
|
72
|
+
break unless condition.truthy?
|
73
|
+
|
74
|
+
aWhileStmt.body.accept(aVisitor)
|
75
|
+
aWhileStmt.condition.accept(aVisitor)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def before_block_stmt(_aBlockStmt)
|
80
|
+
new_env = Environment.new
|
81
|
+
symbol_table.enter_environment(new_env)
|
82
|
+
end
|
83
|
+
|
84
|
+
def after_block_stmt(_aBlockStmt)
|
85
|
+
symbol_table.leave_environment
|
86
|
+
end
|
87
|
+
|
88
|
+
def after_assign_expr(anAssignExpr)
|
89
|
+
var_name = anAssignExpr.name
|
90
|
+
variable = symbol_table.lookup(var_name)
|
91
|
+
raise StandardError, "Unknown variable #{var_name}" unless variable
|
92
|
+
|
93
|
+
value = stack.pop
|
94
|
+
variable.assign(value)
|
95
|
+
stack.push value # An expression produces a value
|
96
|
+
end
|
97
|
+
|
64
98
|
def after_logical_expr(aLogicalExpr, visitor)
|
65
99
|
op = aLogicalExpr.operator
|
66
100
|
operand1 = stack.pop # only first operand was evaluated
|
@@ -120,10 +154,23 @@ module Loxxy
|
|
120
154
|
# Do nothing: work was already done by visiting /evaluating the subexpression
|
121
155
|
end
|
122
156
|
|
157
|
+
def after_variable_expr(aVarExpr, aVisitor)
|
158
|
+
var_name = aVarExpr.name
|
159
|
+
var = symbol_table.lookup(var_name)
|
160
|
+
raise StandardError, "Unknown variable #{var_name}" unless var
|
161
|
+
|
162
|
+
var.value.accept(aVisitor) # Evaluate the variable value
|
163
|
+
end
|
164
|
+
|
123
165
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
124
166
|
def before_literal_expr(literalExpr)
|
125
167
|
stack.push(literalExpr.literal)
|
126
168
|
end
|
169
|
+
|
170
|
+
# @param aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
|
171
|
+
def before_visit_builtin(aValue)
|
172
|
+
stack.push(aValue)
|
173
|
+
end
|
127
174
|
end # class
|
128
175
|
end # module
|
129
176
|
end # module
|
data/lib/loxxy/back_end/entry.rb
CHANGED
@@ -11,7 +11,7 @@ module Loxxy
|
|
11
11
|
=begin
|
12
12
|
# @return [String] Suffix for building the internal name of the entry.
|
13
13
|
attr_accessor :suffix
|
14
|
-
=end
|
14
|
+
=end
|
15
15
|
|
16
16
|
# Initialize the entry with given name
|
17
17
|
# @param aName [String] The name of the entry
|
@@ -35,7 +35,7 @@ module Loxxy
|
|
35
35
|
(suffix.nil? || suffix.empty?) ? label : suffix
|
36
36
|
end
|
37
37
|
end
|
38
|
-
=end
|
38
|
+
=end
|
39
39
|
end # module
|
40
40
|
end # module
|
41
41
|
end # module
|
@@ -9,9 +9,9 @@ module Loxxy
|
|
9
9
|
# of a relation or a relation definition.
|
10
10
|
# It contains a map of names to the objects they name (e.g. logical var)
|
11
11
|
class Environment
|
12
|
-
# The parent
|
12
|
+
# The enclosing (parent) environment.
|
13
13
|
# @return [Environment, NilClass]
|
14
|
-
attr_accessor :
|
14
|
+
attr_accessor :enclosing
|
15
15
|
|
16
16
|
# Mapping from user-defined name to related definition
|
17
17
|
# (say, a variable object)
|
@@ -21,7 +21,7 @@ module Loxxy
|
|
21
21
|
# Construct a environment instance.
|
22
22
|
# @param aParent [Environment, NilClass] Parent environment to this one.
|
23
23
|
def initialize(aParent = nil)
|
24
|
-
@
|
24
|
+
@enclosing = aParent
|
25
25
|
@defns = {}
|
26
26
|
end
|
27
27
|
|
@@ -44,7 +44,7 @@ module Loxxy
|
|
44
44
|
# to be a child of current environment and to be itself the new current environment.
|
45
45
|
# @param anEnv [BackEnd::Environment] the Environment that
|
46
46
|
def enter_environment(anEnv)
|
47
|
-
anEnv.
|
47
|
+
anEnv.enclosing = current_env
|
48
48
|
@current_env = anEnv
|
49
49
|
end
|
50
50
|
|
@@ -60,7 +60,7 @@ module Loxxy
|
|
60
60
|
end
|
61
61
|
raise StandardError, 'Cannot remove root environment.' if current_env == root
|
62
62
|
|
63
|
-
@current_env = current_env.
|
63
|
+
@current_env = current_env.enclosing
|
64
64
|
end
|
65
65
|
|
66
66
|
# Add an entry with given name to current environment.
|
@@ -114,7 +114,7 @@ module Loxxy
|
|
114
114
|
while skope
|
115
115
|
vars_of_environment = skope.defns.select { |_, item| item.kind_of?(Variable) }
|
116
116
|
vars = vars_of_environment.values.concat(vars)
|
117
|
-
skope = skope.
|
117
|
+
skope = skope.enclosing
|
118
118
|
end
|
119
119
|
|
120
120
|
vars
|
@@ -9,15 +9,20 @@ module Loxxy
|
|
9
9
|
# It is a named slot that can be associated with a value at the time.
|
10
10
|
class Variable
|
11
11
|
include Entry # Add expected behaviour for symbol table entries
|
12
|
-
|
12
|
+
|
13
13
|
# @return [Datatype::BuiltinDatatype] the value assigned to the variable
|
14
|
-
|
14
|
+
attr_reader :value
|
15
15
|
|
16
16
|
# Create a variable with given name and initial value
|
17
17
|
# @param aName [String] The name of the variable
|
18
18
|
# @param aValue [Datatype::BuiltinDatatype] the initial assigned value
|
19
19
|
def initialize(aName, aValue = Datatype::Nil.instance)
|
20
20
|
init_name(aName)
|
21
|
+
assign(aValue)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param aValue [Datatype::BuiltinDatatype] the assigned value
|
25
|
+
def assign(aValue)
|
21
26
|
@value = aValue
|
22
27
|
end
|
23
28
|
end # class
|
@@ -62,6 +62,12 @@ module Loxxy
|
|
62
62
|
value.to_s # Default implementation...
|
63
63
|
end
|
64
64
|
|
65
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
66
|
+
# @param visitor [Ast::ASTVisitor] the visitor
|
67
|
+
def accept(visitor)
|
68
|
+
visitor.visit_builtin(self)
|
69
|
+
end
|
70
|
+
|
65
71
|
protected
|
66
72
|
|
67
73
|
def validated_value(aValue)
|
@@ -30,8 +30,8 @@ module Loxxy
|
|
30
30
|
rule('program' => 'declaration_plus EOF').as 'lox_program'
|
31
31
|
|
32
32
|
# Declarations: bind an identifier to something
|
33
|
-
rule('declaration_plus' => 'declaration_plus declaration')
|
34
|
-
rule('declaration_plus' => 'declaration')
|
33
|
+
rule('declaration_plus' => 'declaration_plus declaration').as 'declaration_plus_more'
|
34
|
+
rule('declaration_plus' => 'declaration').as 'declaration_plus_end'
|
35
35
|
rule('declaration' => 'classDecl')
|
36
36
|
rule('declaration' => 'funDecl')
|
37
37
|
rule('declaration' => 'varDecl')
|
@@ -75,15 +75,15 @@ module Loxxy
|
|
75
75
|
|
76
76
|
rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
|
77
77
|
rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
|
78
|
-
rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
|
79
|
-
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
|
78
|
+
rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement').as 'while_stmt'
|
79
|
+
rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE').as 'block_stmt'
|
80
80
|
rule('block' => 'LEFT_BRACE RIGHT_BRACE')
|
81
81
|
|
82
82
|
# Expressions: produce values
|
83
83
|
rule('expression_opt' => 'expression')
|
84
84
|
rule('expression_opt' => [])
|
85
85
|
rule('expression' => 'assignment')
|
86
|
-
rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
|
86
|
+
rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
|
87
87
|
rule('assignment' => 'logic_or')
|
88
88
|
rule('owner_opt' => 'call DOT')
|
89
89
|
rule('owner_opt' => [])
|
@@ -137,7 +137,7 @@ module Loxxy
|
|
137
137
|
rule('primary' => 'THIS')
|
138
138
|
rule('primary' => 'NUMBER').as 'literal_expr'
|
139
139
|
rule('primary' => 'STRING').as 'literal_expr'
|
140
|
-
rule('primary' => 'IDENTIFIER')
|
140
|
+
rule('primary' => 'IDENTIFIER').as 'variable_expr'
|
141
141
|
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
|
142
142
|
rule('primary' => 'SUPER DOT IDENTIFIER')
|
143
143
|
|
data/lib/loxxy/version.rb
CHANGED