loxxy 0.0.16 → 0.0.21
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 +60 -1
- data/README.md +155 -52
- data/lib/loxxy/ast/all_lox_nodes.rb +7 -1
- data/lib/loxxy/ast/ast_builder.rb +57 -3
- data/lib/loxxy/ast/ast_visitor.rb +62 -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/ast/lox_var_stmt.rb +28 -0
- data/lib/loxxy/back_end/engine.rb +71 -4
- data/lib/loxxy/back_end/entry.rb +41 -0
- data/lib/loxxy/back_end/environment.rb +66 -0
- data/lib/loxxy/back_end/symbol_table.rb +135 -0
- data/lib/loxxy/back_end/variable.rb +25 -0
- data/lib/loxxy/datatype/builtin_datatype.rb +55 -0
- data/lib/loxxy/datatype/false.rb +14 -8
- data/lib/loxxy/datatype/lx_string.rb +0 -14
- data/lib/loxxy/datatype/nil.rb +12 -6
- data/lib/loxxy/datatype/number.rb +44 -6
- data/lib/loxxy/datatype/true.rb +1 -9
- data/lib/loxxy/front_end/grammar.rb +7 -7
- data/lib/loxxy/interpreter.rb +1 -1
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +8 -0
- data/spec/back_end/environment_spec.rb +74 -0
- data/spec/back_end/symbol_table_spec.rb +142 -0
- data/spec/back_end/variable_spec.rb +79 -0
- data/spec/front_end/parser_spec.rb +4 -4
- data/spec/interpreter_spec.rb +152 -0
- metadata +17 -2
@@ -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
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'entry'
|
4
|
+
require_relative '../datatype/all_datatypes'
|
5
|
+
|
6
|
+
module Loxxy
|
7
|
+
module BackEnd
|
8
|
+
# Representation of a Lox variable.
|
9
|
+
# It is a named slot that can be associated with a value at the time.
|
10
|
+
class Variable
|
11
|
+
include Entry # Add expected behaviour for symbol table entries
|
12
|
+
|
13
|
+
# @return [Datatype::BuiltinDatatype] the value assigned to the variable
|
14
|
+
attr_accessor :value
|
15
|
+
|
16
|
+
# Create a variable with given name and initial value
|
17
|
+
# @param aName [String] The name of the variable
|
18
|
+
# @param aValue [Datatype::BuiltinDatatype] the initial assigned value
|
19
|
+
def initialize(aName, aValue = Datatype::Nil.instance)
|
20
|
+
init_name(aName)
|
21
|
+
@value = aValue
|
22
|
+
end
|
23
|
+
end # class
|
24
|
+
end # module
|
25
|
+
end # module
|
@@ -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
@@ -20,6 +20,20 @@ module Loxxy
|
|
20
20
|
true
|
21
21
|
end
|
22
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
|
+
|
23
37
|
# Check for equality of a Lox False with another Lox object
|
24
38
|
# @param other [Datatype::BuiltinDatatype, FalseClass, Object]
|
25
39
|
# @return [Datatype::Boolean]
|
@@ -27,14 +41,6 @@ module Loxxy
|
|
27
41
|
falsey = other.kind_of?(False) || other.kind_of?(FalseClass)
|
28
42
|
falsey ? True.instance : False.instance
|
29
43
|
end
|
30
|
-
|
31
|
-
# Check for inequality of a Lox False with another Lox object
|
32
|
-
# @param other [Datatype::BuiltinDatatype, FalseClass, Object]
|
33
|
-
# @return [Datatype::Boolean]
|
34
|
-
def !=(other)
|
35
|
-
falsey = other.kind_of?(False) || other.kind_of?(FalseClass)
|
36
|
-
falsey ? False.instance : True.instance
|
37
|
-
end
|
38
44
|
end # class
|
39
45
|
|
40
46
|
False.instance.freeze # Make the sole instance immutable
|
@@ -21,20 +21,6 @@ module Loxxy
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
# Check the inequality of a Lox String with another Lox object.
|
25
|
-
# @param other [Datatype::LxString, String, Object]
|
26
|
-
# @return [Datatype::Boolean]
|
27
|
-
def !=(other)
|
28
|
-
case other
|
29
|
-
when LXString
|
30
|
-
(value != other.value) ? True.instance : False.instance
|
31
|
-
when String
|
32
|
-
(value != other) ? True.instance : False.instance
|
33
|
-
else
|
34
|
-
True.instance
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
24
|
# Perform the concatenation of two Lox stings or
|
39
25
|
# one Lox string and a Ruby String
|
40
26
|
# @param other [Loxxy::Datatype::LXString, String]
|
data/lib/loxxy/datatype/nil.rb
CHANGED
@@ -22,12 +22,18 @@ module Loxxy
|
|
22
22
|
is_nil ? True.instance : False.instance
|
23
23
|
end
|
24
24
|
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
def
|
29
|
-
|
30
|
-
|
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
|
31
37
|
end
|
32
38
|
|
33
39
|
# Method called from Lox to obtain the text representation of nil.
|
@@ -71,6 +71,12 @@ module Loxxy
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+
# Unary minus (return value with changed sign)
|
75
|
+
# @return [Loxxy::Datatype::Number]
|
76
|
+
def -@
|
77
|
+
self.class.new(-value)
|
78
|
+
end
|
79
|
+
|
74
80
|
# Check the equality of a Lox number object with another object
|
75
81
|
# @param other [Datatype::BuiltinDatatype, Numeric, Object]
|
76
82
|
# @return [Datatype::Boolean]
|
@@ -85,20 +91,52 @@ module Loxxy
|
|
85
91
|
end
|
86
92
|
end
|
87
93
|
|
88
|
-
# Check
|
89
|
-
# @param other [Datatype::
|
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]
|
90
112
|
# @return [Datatype::Boolean]
|
91
|
-
def
|
113
|
+
def >=(other)
|
92
114
|
case other
|
93
115
|
when Number
|
94
|
-
(value
|
116
|
+
(value >= other.value) ? True.instance : False.instance
|
95
117
|
when Numeric
|
96
|
-
(value
|
118
|
+
(value >= other) ? True.instance : False.instance
|
97
119
|
else
|
98
|
-
|
120
|
+
msg = "'>': Operands must be numbers."
|
121
|
+
raise StandardError, msg
|
99
122
|
end
|
100
123
|
end
|
101
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
|
+
|
102
140
|
protected
|
103
141
|
|
104
142
|
def validated_value(aValue)
|
data/lib/loxxy/datatype/true.rb
CHANGED
@@ -20,21 +20,13 @@ module Loxxy
|
|
20
20
|
true
|
21
21
|
end
|
22
22
|
|
23
|
-
# Check for equality of a Lox True with another Lox object
|
23
|
+
# Check for equality of a Lox True with another Lox object / Ruby true
|
24
24
|
# @param other [Datatype::True, TrueClass, Object]
|
25
25
|
# @return [Datatype::Boolean]
|
26
26
|
def ==(other)
|
27
27
|
thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
|
28
28
|
thruthy ? True.instance : False.instance
|
29
29
|
end
|
30
|
-
|
31
|
-
# Check for inequality of a Lox True with another Lox object
|
32
|
-
# @param other [Datatype::BuiltinDatatype, TrueClass, Object]
|
33
|
-
# @return [Datatype::Boolean]
|
34
|
-
def !=(other)
|
35
|
-
thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
|
36
|
-
thruthy ? False.instance : True.instance
|
37
|
-
end
|
38
30
|
end # class
|
39
31
|
|
40
32
|
True.instance.freeze # Make the sole instance immutable
|
@@ -46,8 +46,8 @@ module Loxxy
|
|
46
46
|
|
47
47
|
rule('funDecl' => 'FUN function')
|
48
48
|
|
49
|
-
rule('varDecl' => 'VAR IDENTIFIER SEMICOLON')
|
50
|
-
rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON')
|
49
|
+
rule('varDecl' => 'VAR IDENTIFIER SEMICOLON').as 'var_declaration'
|
50
|
+
rule('varDecl' => 'VAR IDENTIFIER EQUAL expression SEMICOLON').as 'var_initialization'
|
51
51
|
|
52
52
|
# Statements: produce side effects, but don't introduce bindings
|
53
53
|
rule('statement' => 'exprStmt')
|
@@ -68,9 +68,9 @@ module Loxxy
|
|
68
68
|
rule('forTest' => 'expression_opt SEMICOLON')
|
69
69
|
rule('forUpdate' => 'expression_opt')
|
70
70
|
|
71
|
-
rule('ifStmt' => 'IF ifCondition statement elsePart_opt')
|
72
|
-
rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN')
|
73
|
-
rule('elsePart_opt' => 'ELSE statement')
|
71
|
+
rule('ifStmt' => 'IF ifCondition statement elsePart_opt').as 'if_stmt'
|
72
|
+
rule('ifCondition' => 'LEFT_PAREN expression RIGHT_PAREN').as 'keep_symbol2'
|
73
|
+
rule('elsePart_opt' => 'ELSE statement').as 'keep_symbol2'
|
74
74
|
rule('elsePart_opt' => [])
|
75
75
|
|
76
76
|
rule('printStmt' => 'PRINT expression SEMICOLON').as 'print_stmt'
|
@@ -121,7 +121,7 @@ module Loxxy
|
|
121
121
|
rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
|
122
122
|
rule('multOp' => 'SLASH')
|
123
123
|
rule('multOp' => 'STAR')
|
124
|
-
rule('unary' => 'unaryOp unary')
|
124
|
+
rule('unary' => 'unaryOp unary').as 'unary_expr'
|
125
125
|
rule('unary' => 'call')
|
126
126
|
rule('unaryOp' => 'BANG')
|
127
127
|
rule('unaryOp' => 'MINUS')
|
@@ -138,7 +138,7 @@ module Loxxy
|
|
138
138
|
rule('primary' => 'NUMBER').as 'literal_expr'
|
139
139
|
rule('primary' => 'STRING').as 'literal_expr'
|
140
140
|
rule('primary' => 'IDENTIFIER')
|
141
|
-
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
141
|
+
rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
|
142
142
|
rule('primary' => 'SUPER DOT IDENTIFIER')
|
143
143
|
|
144
144
|
# Utility rules
|