loxxy 0.0.20 → 0.0.25

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,22 @@ 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
+
54
70
  # Visit event. The visitor is about to visit a if statement.
55
71
  # @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
56
72
  def visit_if_stmt(anIfStmt)
@@ -67,6 +83,22 @@ module Loxxy
67
83
  broadcast(:after_print_stmt, aPrintStmt)
68
84
  end
69
85
 
86
+ # Visit event. The visitor is about to visit a block statement.
87
+ # @param aBlockStmt [AST::LOXBlockStmt] the print statement node to visit
88
+ def visit_block_stmt(aBlockStmt)
89
+ broadcast(:before_block_stmt, aBlockStmt)
90
+ traverse_subnodes(aBlockStmt)
91
+ broadcast(:after_block_stmt, aBlockStmt)
92
+ end
93
+
94
+ # Visit event. The visitor is visiting an assignment node
95
+ # @param aLiteralExpr [AST::LoxAssignExpr] the variable assignment node to visit.
96
+ def visit_assign_expr(anAssignExpr)
97
+ broadcast(:before_assign_expr, anAssignExpr)
98
+ traverse_subnodes(anAssignExpr)
99
+ broadcast(:after_assign_expr, anAssignExpr)
100
+ end
101
+
70
102
  # Visit event. The visitor is about to visit a logical expression.
71
103
  # Since logical expressions may take shorcuts by not evaluating all their
72
104
  # sub-expressiosns, they are responsible for visiting or not their children.
@@ -113,8 +145,22 @@ module Loxxy
113
145
  broadcast(:after_literal_expr, aLiteralExpr)
114
146
  end
115
147
 
148
+ # Visit event. The visitor is visiting a variable usage node
149
+ # @param aLiteralExpr [AST::LoxVariableExpr] the variable reference node to visit.
150
+ def visit_variable_expr(aVariableExpr)
151
+ broadcast(:before_variable_expr, aVariableExpr)
152
+ broadcast(:after_variable_expr, aVariableExpr, self)
153
+ end
154
+
155
+ # Visit event. The visitor is about to visit the given terminal datatype value.
156
+ # @param aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
157
+ def visit_builtin(aValue)
158
+ broadcast(:before_visit_builtin, aValue)
159
+ broadcast(:after_visit_builtin, aValue)
160
+ end
161
+
116
162
  # Visit event. The visitor is about to visit the given non terminal node.
117
- # @param aNonTerminalNode [Rley::PTre::NonTerminalNode] the node to visit.
163
+ # @param aNonTerminalNode [Rley::PTree::NonTerminalNode] the node to visit.
118
164
  def visit_nonterminal(_non_terminal_node)
119
165
  # Loxxy interpreter encountered a CST node (Concrete Syntax Tree)
120
166
  # 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
@@ -32,4 +32,4 @@ module Loxxy
32
32
  end
33
33
  end # class
34
34
  end # module
35
- 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
 
@@ -36,11 +41,21 @@ module Loxxy
36
41
  ##########################################################################
37
42
  # Visit event handling
38
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
+
39
54
  def after_if_stmt(anIfStmt, aVisitor)
40
55
  # Retrieve the result of the condition evaluation
41
56
  condition = stack.pop
42
57
  if condition.truthy?
43
- result = anIfStmt.then_stmt.accept(aVisitor)
58
+ anIfStmt.then_stmt.accept(aVisitor)
44
59
  elsif anIfStmt.else_stmt
45
60
  anIfStmt.else_stmt.accept(aVisitor)
46
61
  end
@@ -51,6 +66,25 @@ module Loxxy
51
66
  @ostream.print tos.to_str
52
67
  end
53
68
 
69
+ def before_block_stmt(_aBlockStmt)
70
+ new_env = Environment.new
71
+ symbol_table.enter_environment(new_env)
72
+ end
73
+
74
+ def after_block_stmt(_aBlockStmt)
75
+ symbol_table.leave_environment
76
+ end
77
+
78
+ def after_assign_expr(anAssignExpr)
79
+ var_name = anAssignExpr.name
80
+ variable = symbol_table.lookup(var_name)
81
+ raise StandardError, "Unknown variable #{var_name}" unless variable
82
+
83
+ value = stack.pop
84
+ variable.assign(value)
85
+ stack.push value # An expression produces a value
86
+ end
87
+
54
88
  def after_logical_expr(aLogicalExpr, visitor)
55
89
  op = aLogicalExpr.operator
56
90
  operand1 = stack.pop # only first operand was evaluated
@@ -110,10 +144,23 @@ module Loxxy
110
144
  # Do nothing: work was already done by visiting /evaluating the subexpression
111
145
  end
112
146
 
147
+ def after_variable_expr(aVarExpr, aVisitor)
148
+ var_name = aVarExpr.name
149
+ var = symbol_table.lookup(var_name)
150
+ raise StandardError, "Unknown variable #{var_name}" unless var
151
+
152
+ var.value.accept(aVisitor) # Evaluate the variable value
153
+ end
154
+
113
155
  # @param literalExpr [Ast::LoxLiteralExpr]
114
156
  def before_literal_expr(literalExpr)
115
157
  stack.push(literalExpr.literal)
116
158
  end
159
+
160
+ # @param aNonTerminalNode [Ast::BuiltinDattype] the built-in datatype value
161
+ def before_visit_builtin(aValue)
162
+ stack.push(aValue)
163
+ end
117
164
  end # class
118
165
  end # module
119
166
  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 enclosing (parent) environment.
13
+ # @return [Environment, NilClass]
14
+ attr_accessor :enclosing
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
+ @enclosing = 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.enclosing = 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.enclosing
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.enclosing
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