loxxy 0.0.19 → 0.0.24

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.
@@ -51,6 +51,30 @@ 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
+
62
+ # Visit event. The visitor is about to visit a variable declaration statement.
63
+ # @param aPrintStmt [AST::LOXVarStmt] the variable declaration node to visit
64
+ def visit_var_stmt(aVarStmt)
65
+ broadcast(:before_var_stmt, aVarStmt)
66
+ traverse_subnodes(aVarStmt)
67
+ broadcast(:after_var_stmt, aVarStmt)
68
+ end
69
+
70
+ # Visit event. The visitor is about to visit a if statement.
71
+ # @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
72
+ def visit_if_stmt(anIfStmt)
73
+ broadcast(:before_if_stmt, anIfStmt)
74
+ traverse_subnodes(anIfStmt) # The condition is visited/evaluated here...
75
+ broadcast(:after_if_stmt, anIfStmt, self)
76
+ end
77
+
54
78
  # Visit event. The visitor is about to visit a print statement.
55
79
  # @param aPrintStmt [AST::LOXPrintStmt] the print statement node to visit
56
80
  def visit_print_stmt(aPrintStmt)
@@ -59,6 +83,14 @@ module Loxxy
59
83
  broadcast(:after_print_stmt, aPrintStmt)
60
84
  end
61
85
 
86
+ # Visit event. The visitor is visiting an assignment node
87
+ # @param aLiteralExpr [AST::LoxAssignExpr] the variable assignment node to visit.
88
+ def visit_assign_expr(anAssignExpr)
89
+ broadcast(:before_assign_expr, anAssignExpr)
90
+ traverse_subnodes(anAssignExpr)
91
+ broadcast(:after_assign_expr, anAssignExpr)
92
+ end
93
+
62
94
  # Visit event. The visitor is about to visit a logical expression.
63
95
  # Since logical expressions may take shorcuts by not evaluating all their
64
96
  # sub-expressiosns, they are responsible for visiting or not their children.
@@ -105,8 +137,22 @@ module Loxxy
105
137
  broadcast(:after_literal_expr, aLiteralExpr)
106
138
  end
107
139
 
140
+ # Visit event. The visitor is visiting a variable usage node
141
+ # @param aLiteralExpr [AST::LoxVariableExpr] the variable reference node to visit.
142
+ def visit_variable_expr(aVariableExpr)
143
+ broadcast(:before_variable_expr, aVariableExpr)
144
+ broadcast(:after_variable_expr, aVariableExpr, self)
145
+ end
146
+
147
+ # Visit event. The visitor is about to visit the given terminal datatype value.
148
+ # @param aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
149
+ def visit_builtin(aValue)
150
+ broadcast(:before_visit_builtin, aValue)
151
+ broadcast(:after_visit_builtin, aValue)
152
+ end
153
+
108
154
  # Visit event. The visitor is about to visit the given non terminal node.
109
- # @param aNonTerminalNode [Rley::PTre::NonTerminalNode] the node to visit.
155
+ # @param aNonTerminalNode [Rley::PTree::NonTerminalNode] the node to visit.
110
156
  def visit_nonterminal(_non_terminal_node)
111
157
  # Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
112
158
  # 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,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,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
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ # This AST node represents a variable declaration
8
+ class LoxVarStmt < 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] initial value for the variable
15
+ def initialize(aPosition, aName, aValue)
16
+ initial_value = aValue ? [aValue] : [Datatype::Nil.instance]
17
+ super(aPosition, initial_value)
18
+ @name = aName
19
+ end
20
+
21
+ # Part of the 'visitee' role in Visitor design pattern.
22
+ # @param visitor [Ast::ASTVisitor] the visitor
23
+ def accept(visitor)
24
+ visitor.visit_var_stmt(self)
25
+ end
26
+ end # class
27
+ end # module
28
+ end # module
@@ -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
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Load all the classes implementing AST nodes
4
4
  require_relative '../ast/all_lox_nodes'
5
+ require_relative 'symbol_table'
5
6
 
6
7
  module Loxxy
7
8
  module BackEnd
@@ -12,13 +13,17 @@ module Loxxy
12
13
  # @return [Hash] A set of configuration options
13
14
  attr_reader :config
14
15
 
15
- # @return [Array] Data stack used for passing data between statements
16
+ # @return [BackEnd::SymbolTable]
17
+ attr_reader :symbol_table
18
+
19
+ # @return [Array<Datatype::BuiltinDatatyp>] Stack for the values of expr
16
20
  attr_reader :stack
17
21
 
18
22
  # @param theOptions [Hash]
19
23
  def initialize(theOptions)
20
24
  @config = theOptions
21
25
  @ostream = config.include?(:ostream) ? config[:ostream] : $stdout
26
+ @symbol_table = SymbolTable.new
22
27
  @stack = []
23
28
  end
24
29
 
@@ -33,13 +38,43 @@ module Loxxy
33
38
  stack.empty? ? Datatype::Nil.instance : stack.pop
34
39
  end
35
40
 
41
+ ##########################################################################
36
42
  # Visit event handling
43
+ ##########################################################################
44
+
45
+ def after_seq_decl(aSeqDecls)
46
+ # Do nothing, subnodes were already evaluated
47
+ end
48
+
49
+ def after_var_stmt(aVarStmt)
50
+ new_var = Variable.new(aVarStmt.name, aVarStmt.subnodes[0])
51
+ symbol_table.insert(new_var)
52
+ end
53
+
54
+ def after_if_stmt(anIfStmt, aVisitor)
55
+ # Retrieve the result of the condition evaluation
56
+ condition = stack.pop
57
+ if condition.truthy?
58
+ anIfStmt.then_stmt.accept(aVisitor)
59
+ elsif anIfStmt.else_stmt
60
+ anIfStmt.else_stmt.accept(aVisitor)
61
+ end
62
+ end
37
63
 
38
64
  def after_print_stmt(_printStmt)
39
65
  tos = stack.pop
40
66
  @ostream.print tos.to_str
41
67
  end
42
68
 
69
+ def after_assign_expr(anAssignExpr)
70
+ var_name = anAssignExpr.name
71
+ variable = symbol_table.lookup(var_name)
72
+ raise StandardError, "Unknown variable #{var_name}" unless variable
73
+ value = stack.pop
74
+ variable.assign(value)
75
+ stack.push value # An expression produces a value
76
+ end
77
+
43
78
  def after_logical_expr(aLogicalExpr, visitor)
44
79
  op = aLogicalExpr.operator
45
80
  operand1 = stack.pop # only first operand was evaluated
@@ -99,10 +134,23 @@ module Loxxy
99
134
  # Do nothing: work was already done by visiting /evaluating the subexpression
100
135
  end
101
136
 
137
+ def after_variable_expr(aVarExpr, aVisitor)
138
+ var_name = aVarExpr.name
139
+ var = symbol_table.lookup(var_name)
140
+ raise StandardError, "Unknown variable #{var_name}" unless var
141
+
142
+ var.value.accept(aVisitor) # Evaluate the variable value
143
+ end
144
+
102
145
  # @param literalExpr [Ast::LoxLiteralExpr]
103
146
  def before_literal_expr(literalExpr)
104
147
  stack.push(literalExpr.literal)
105
148
  end
149
+
150
+ # @param aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
151
+ def before_visit_builtin(aValue)
152
+ stack.push(aValue)
153
+ end
106
154
  end # class
107
155
  end # module
108
156
  end # module
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loxxy
4
+ module BackEnd
5
+ # Mix-in module that implements the expected common behaviour of entries
6
+ # placed in the symbol table.
7
+ module Entry
8
+ # @return [String] User-defined name of the entry.
9
+ attr_reader :name
10
+
11
+ =begin
12
+ # @return [String] Suffix for building the internal name of the entry.
13
+ attr_accessor :suffix
14
+ =end
15
+
16
+ # Initialize the entry with given name
17
+ # @param aName [String] The name of the entry
18
+ def init_name(aName)
19
+ @name = aName.dup
20
+ @name.freeze
21
+ end
22
+ =begin
23
+ # Return the internal name of the entry
24
+ # Internal names used to disambiguate entry names.
25
+ # There might be homonyns between variable because:
26
+ # - A child Scope may have a entry with same name as one of its
27
+ # ancestor(s).
28
+ # - Multiple calls to same defrel or procedure may imply multiple creation
29
+ # of a entry given name...
30
+ # @return [String] internal name
31
+ def i_name
32
+ if suffix =~ /^_/
33
+ label + suffix
34
+ else
35
+ (suffix.nil? || suffix.empty?) ? label : suffix
36
+ end
37
+ end
38
+ =end
39
+ end # module
40
+ end # module
41
+ end # module
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'variable'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # A environment is a name space that corresponds either to a specific
8
+ # delimited region in Loxxy source code or to an activation record
9
+ # of a relation or a relation definition.
10
+ # It contains a map of names to the objects they name (e.g. logical var)
11
+ class Environment
12
+ # The parent (enclosing) environment.
13
+ # @return [Environment, NilClass]
14
+ attr_accessor :parent
15
+
16
+ # Mapping from user-defined name to related definition
17
+ # (say, a variable object)
18
+ # @return [Hash{String => Variable}] Pairs of the kind
19
+ attr_reader :defns
20
+
21
+ # Construct a environment instance.
22
+ # @param aParent [Environment, NilClass] Parent environment to this one.
23
+ def initialize(aParent = nil)
24
+ @parent = aParent
25
+ @defns = {}
26
+ end
27
+
28
+ # Add a new variable to the environment.
29
+ # @param anEntry [BackEnd::Variable]
30
+ # @return [BackEnd::Variable] the variable
31
+ def insert(anEntry)
32
+ e = validated_entry(anEntry)
33
+ # e.suffix = default_suffix if e.kind_of?(BackEnd::Variable)
34
+ defns[e.name] = e
35
+
36
+ e
37
+ end
38
+
39
+ # Returns a string with a human-readable representation of the object.
40
+ # @return [String]
41
+ def inspect
42
+ +"#<#{self.class}:#{object_id.to_s(16)}>"
43
+ end
44
+
45
+ private
46
+
47
+ def validated_entry(anEntry)
48
+ name = anEntry.name
49
+ unless name.kind_of?(String) && !name.empty?
50
+ err_msg = 'Invalid variable name argument.'
51
+ raise StandardError, err_msg
52
+ end
53
+ if defns.include?(name) && !anEntry.kind_of?(Variable)
54
+ err_msg = "Variable with name '#{name}' already exists."
55
+ raise StandardError, err_msg
56
+ end
57
+
58
+ anEntry
59
+ end
60
+
61
+ # def default_suffix
62
+ # @default_suffix ||= "_#{object_id.to_s(16)}"
63
+ # end
64
+ end # class
65
+ end # module
66
+ end # module
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'environment'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # A symbol table is basically a mapping from a name onto an object
8
+ # that holds information associated with that name. It is a data structure
9
+ # that keeps track of variables and their respective environment where they are
10
+ # declared. The key requirements for the symbol are:
11
+ # - To perform fast lookup operations: given a name, retrieve the corresponding
12
+ # object.
13
+ # - To allow the efficient insertion of names and related information
14
+ # - To support the nesting of environments
15
+ # - To handle the entry environment and exit environment events,
16
+ # - To cope with variable redefinition in nested environment
17
+ # The terminology 'symbol table' comes from the compiler design
18
+ # community.
19
+ class SymbolTable
20
+ # Mapping between a name and the environment(s) where it is defined
21
+ # @return [Hash{String => Array<Environment>}]
22
+ attr_reader :name2envs
23
+
24
+ # @return [Environment] The top-level environment (= root of environment tree)
25
+ attr_reader :root
26
+
27
+ # @return [Environment] The current environment.
28
+ attr_reader :current_env
29
+
30
+ # Build symbol table with given environment as root.
31
+ # @param anEnv [BackEnd::Environment,NilClass] The top-level Environment
32
+ def initialize(anEnv = nil)
33
+ @name2envs = {}
34
+ init_root(anEnv) # Set default (global) environment
35
+ end
36
+
37
+ # Returns iff there is no entry in the symbol table
38
+ # @return [Boolean]
39
+ def empty?
40
+ name2envs.empty?
41
+ end
42
+
43
+ # Use this method to signal the interpreter that a given environment
44
+ # to be a child of current environment and to be itself the new current environment.
45
+ # @param anEnv [BackEnd::Environment] the Environment that
46
+ def enter_environment(anEnv)
47
+ anEnv.parent = current_env
48
+ @current_env = anEnv
49
+ end
50
+
51
+ def leave_environment
52
+ current_env.defns.each_pair do |nm, _item|
53
+ environments = name2envs[nm]
54
+ if environments.size == 1
55
+ name2envs.delete(nm)
56
+ else
57
+ environments.pop
58
+ name2envs[nm] = environments
59
+ end
60
+ end
61
+ raise StandardError, 'Cannot remove root environment.' if current_env == root
62
+
63
+ @current_env = current_env.parent
64
+ end
65
+
66
+ # Add an entry with given name to current environment.
67
+ # @param anEntry [Variable]
68
+ # @return [String] Internal name of the entry
69
+ def insert(anEntry)
70
+ current_env.insert(anEntry)
71
+ name = anEntry.name
72
+ if name2envs.include?(name)
73
+ name2envs[name] << current_env
74
+ else
75
+ name2envs[name] = [current_env]
76
+ end
77
+
78
+ anEntry.name # anEntry.i_name
79
+ end
80
+
81
+ # Search for the object with the given name
82
+ # @param aName [String]
83
+ # @return [BackEnd::Variable]
84
+ def lookup(aName)
85
+ environments = name2envs.fetch(aName, nil)
86
+ return nil if environments.nil?
87
+
88
+ sc = environments.last
89
+ sc.defns[aName]
90
+ end
91
+
92
+ # Search for the object with the given i_name
93
+ # @param anIName [String]
94
+ # @return [BackEnd::Variable]
95
+ # def lookup_i_name(anIName)
96
+ # found = nil
97
+ # environment = current_env
98
+
99
+ # begin
100
+ # found = environment.defns.values.find { |e| e.i_name == anIName }
101
+ # break if found
102
+
103
+ # environment = environment.parent
104
+ # end while environment
105
+
106
+ # found
107
+ # end
108
+
109
+ # Return all variables defined in the current .. root chain.
110
+ # Variables are sorted top-down and left-to-right.
111
+ def all_variables
112
+ vars = []
113
+ skope = current_env
114
+ while skope
115
+ vars_of_environment = skope.defns.select { |_, item| item.kind_of?(Variable) }
116
+ vars = vars_of_environment.values.concat(vars)
117
+ skope = skope.parent
118
+ end
119
+
120
+ vars
121
+ end
122
+
123
+ private
124
+
125
+ def init_root(anEnv)
126
+ @root = valid_environment(anEnv)
127
+ @current_env = @root
128
+ end
129
+
130
+ def valid_environment(anEnv)
131
+ anEnv.nil? ? Environment.new : anEnv
132
+ end
133
+ end # class
134
+ end # module
135
+ end # module