loxxy 0.1.12 → 0.1.13
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 +26 -0
- data/lib/loxxy/ast/all_lox_nodes.rb +2 -0
- data/lib/loxxy/ast/ast_builder.rb +19 -2
- data/lib/loxxy/ast/ast_visitor.rb +14 -2
- data/lib/loxxy/ast/lox_class_stmt.rb +7 -4
- data/lib/loxxy/ast/lox_fun_stmt.rb +5 -3
- data/lib/loxxy/ast/lox_get_expr.rb +30 -0
- data/lib/loxxy/ast/lox_set_expr.rb +28 -0
- data/lib/loxxy/back_end/engine.rb +38 -5
- data/lib/loxxy/back_end/lox_class.rb +11 -3
- data/lib/loxxy/back_end/lox_instance.rb +28 -1
- data/lib/loxxy/back_end/resolver.rb +14 -1
- data/lib/loxxy/front_end/grammar.rb +2 -2
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/parser_spec.rb +77 -0
- data/spec/interpreter_spec.rb +32 -13
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 172ec5df8e553332b497968f7b910383cb38314aabbdb84fdb42fba6986841ea
|
4
|
+
data.tar.gz: 3d72fd4f13f507f88e9e735e1ebdd0d63fd603b771f9cfb552b9aa38b5b3ca6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61073737ef6c069e6e1177fa79cdac543253e06ea63d2ca1b00517c08a06f46948fe7d51257308935d92579152793b4fe60bc0f5b901a1c931e5eafab9f35ecd
|
7
|
+
data.tar.gz: 57c87936451c3015511740d7d44f01d8f032a06e597bdfd1d133119413c227a61df11847f86a4be3a2b3440cc1f614a43b1144cceef05a58ade56ca1fa3c598b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
## [0.1.13] - 2021-04-05
|
2
|
+
- `Loxxy` now implements method calls
|
3
|
+
|
4
|
+
## New
|
5
|
+
- Class `Ast::LoxGetExpr` a syntax node that represents a read access to an object property
|
6
|
+
- Class `Ast::LoxSetExpr` a syntax node that represents a write access to an object property
|
7
|
+
- Method `Ast::ASTBuilder#reduce_set_expr` parse action for write access to an object property
|
8
|
+
- Method `Ast::ASTBuilder#reduce_get_expr` parse action for read access to an object property
|
9
|
+
- Method `Ast::Visitor#visit_set_expr` visit of an `Ast::LoxSetExpr` node
|
10
|
+
- Method `Ast::Visitor#visit_get_expr` visit of an `Ast::LoxGetExpr` node
|
11
|
+
- Method `BackEnd::Engine#after_set_expr` runtime action for property setting
|
12
|
+
- Method `BackEnd::Engine#after_get_expr` runtime action for property getting
|
13
|
+
- Method `BackEnd::LoxInstance#set` implementation of write accessor
|
14
|
+
- Method `BackEnd::LoxInstance#getr` implementation of read accessor
|
15
|
+
- Method `BackEnd::Resolver#after_set_expr` resolve action for property setting
|
16
|
+
- Method `BackEnd::Resolver#after_get_expr` resolve action for property getting
|
17
|
+
|
18
|
+
## Changed
|
19
|
+
- Method `Ast::ASTBuilder#reduce_assign_expr` expanded to support write access to an object property
|
20
|
+
- Class `LoxClassStmt`: methods are now aggregate under the `body` attribute
|
21
|
+
- Class `LoxFunStmt`: has a new attribute `is_method` and inherits from `Ast::LoxNode`
|
22
|
+
- Method `BackEnd::Engine#after_class_stmt` methods are aggregated into the classes
|
23
|
+
- Method `BackEnd::Engine#after_fun_stmt` extension for method
|
24
|
+
- File `grammar.rb` added names to two syntax rules
|
25
|
+
|
26
|
+
|
1
27
|
## [0.1.12] - 2021-04-03
|
2
28
|
- Intermediate version: `Loxxy` does instance creation (default constructor)
|
3
29
|
|
@@ -4,11 +4,13 @@ require_relative 'lox_fun_stmt'
|
|
4
4
|
require_relative 'lox_variable_expr'
|
5
5
|
require_relative 'lox_literal_expr'
|
6
6
|
require_relative 'lox_noop_expr'
|
7
|
+
require_relative 'lox_get_expr'
|
7
8
|
require_relative 'lox_call_expr'
|
8
9
|
require_relative 'lox_grouping_expr'
|
9
10
|
require_relative 'lox_unary_expr'
|
10
11
|
require_relative 'lox_binary_expr'
|
11
12
|
require_relative 'lox_logical_expr'
|
13
|
+
require_relative 'lox_set_expr'
|
12
14
|
require_relative 'lox_assign_expr'
|
13
15
|
require_relative 'lox_block_stmt'
|
14
16
|
require_relative 'lox_while_stmt'
|
@@ -269,10 +269,22 @@ module Loxxy
|
|
269
269
|
|
270
270
|
# rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
|
271
271
|
def reduce_assign_expr(_production, _range, tokens, theChildren)
|
272
|
-
|
273
|
-
|
272
|
+
name_assignee = theChildren[1].token.lexeme.dup
|
273
|
+
if theChildren[0].kind_of?(Ast::LoxSetExpr)
|
274
|
+
theChildren[0].property = name_assignee
|
275
|
+
theChildren[0].subnodes << theChildren[3]
|
276
|
+
theChildren[0]
|
277
|
+
else
|
278
|
+
Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# rule('owner_opt' => 'call DOT')
|
283
|
+
def reduce_set_expr(_production, _range, tokens, theChildren)
|
284
|
+
Ast::LoxSetExpr.new(tokens[1].position, theChildren[0])
|
274
285
|
end
|
275
286
|
|
287
|
+
|
276
288
|
# rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
|
277
289
|
# TODO: is it meaningful to implement this rule?
|
278
290
|
|
@@ -317,6 +329,11 @@ module Loxxy
|
|
317
329
|
LoxCallExpr.new(tokens[0].position, args)
|
318
330
|
end
|
319
331
|
|
332
|
+
# rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
|
333
|
+
def reduce_get_expr(_production, _range, tokens, theChildren)
|
334
|
+
LoxGetExpr.new(tokens[0].position, theChildren[1].token.lexeme)
|
335
|
+
end
|
336
|
+
|
320
337
|
# rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
|
321
338
|
def reduce_grouping_expr(_production, _range, tokens, theChildren)
|
322
339
|
subexpr = theChildren[1]
|
@@ -68,7 +68,7 @@ module Loxxy
|
|
68
68
|
end
|
69
69
|
|
70
70
|
# Visit event. The visitor is about to visit a class declaration.
|
71
|
-
# @param
|
71
|
+
# @param aClassStmt [AST::LOXClassStmt] the for statement node to visit
|
72
72
|
def visit_class_stmt(aClassStmt)
|
73
73
|
broadcast(:before_class_stmt, aClassStmt)
|
74
74
|
traverse_subnodes(aClassStmt) # The methods are visited here...
|
@@ -131,6 +131,13 @@ module Loxxy
|
|
131
131
|
broadcast(:after_assign_expr, anAssignExpr, self)
|
132
132
|
end
|
133
133
|
|
134
|
+
# @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
|
135
|
+
def visit_set_expr(aSetExpr)
|
136
|
+
broadcast(:before_set_expr, aSetExpr)
|
137
|
+
traverse_subnodes(aSetExpr)
|
138
|
+
broadcast(:after_set_expr, aSetExpr, self)
|
139
|
+
end
|
140
|
+
|
134
141
|
# Visit event. The visitor is about to visit a logical expression.
|
135
142
|
# Since logical expressions may take shorcuts by not evaluating all their
|
136
143
|
# sub-expressiosns, they are responsible for visiting or not their children.
|
@@ -169,6 +176,12 @@ module Loxxy
|
|
169
176
|
broadcast(:after_call_expr, aCallExpr, self)
|
170
177
|
end
|
171
178
|
|
179
|
+
# @param aGetExpr [AST::LOXGetExpr] the get expression node to visit
|
180
|
+
def visit_get_expr(aGetExpr)
|
181
|
+
broadcast(:before_get_expr, aGetExpr)
|
182
|
+
broadcast(:after_get_expr, aGetExpr, self)
|
183
|
+
end
|
184
|
+
|
172
185
|
# Visit event. The visitor is about to visit a grouping expression.
|
173
186
|
# @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
|
174
187
|
def visit_grouping_expr(aGroupingExpr)
|
@@ -203,7 +216,6 @@ module Loxxy
|
|
203
216
|
# @param aFunStmt [AST::LoxFunStmt] function declaration to visit
|
204
217
|
def visit_fun_stmt(aFunStmt)
|
205
218
|
broadcast(:before_fun_stmt, aFunStmt, self)
|
206
|
-
traverse_subnodes(aFunStmt)
|
207
219
|
broadcast(:after_fun_stmt, aFunStmt, self)
|
208
220
|
end
|
209
221
|
|
@@ -5,14 +5,19 @@ require_relative 'lox_compound_expr'
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
7
|
class LoxClassStmt < LoxCompoundExpr
|
8
|
+
# @return [String] the class name
|
8
9
|
attr_reader :name
|
9
10
|
|
11
|
+
# @return [Array<Ast::LoxFunStmt>] the methods
|
12
|
+
attr_reader :body
|
13
|
+
|
10
14
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
11
15
|
# @param condExpr [Loxxy::Ast::LoxNode] iteration condition
|
12
|
-
# @param theBody [Loxxy::Ast::LoxNode]
|
16
|
+
# @param theBody [Array<Loxxy::Ast::LoxNode>]
|
13
17
|
def initialize(aPosition, aName, theMethods)
|
14
|
-
super(aPosition,
|
18
|
+
super(aPosition, [])
|
15
19
|
@name = aName.dup
|
20
|
+
@body = theMethods
|
16
21
|
end
|
17
22
|
|
18
23
|
# Part of the 'visitee' role in Visitor design pattern.
|
@@ -20,8 +25,6 @@ module Loxxy
|
|
20
25
|
def accept(visitor)
|
21
26
|
visitor.visit_class_stmt(self)
|
22
27
|
end
|
23
|
-
|
24
|
-
alias body subnodes
|
25
28
|
end # class
|
26
29
|
end # module
|
27
30
|
end # module
|
@@ -1,24 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'lox_node'
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
7
|
# rubocop: disable Style/AccessorGrouping
|
8
|
-
class LoxFunStmt <
|
8
|
+
class LoxFunStmt < LoxNode
|
9
9
|
attr_reader :name
|
10
10
|
attr_reader :params
|
11
11
|
attr_reader :body
|
12
|
+
attr_accessor :is_method
|
12
13
|
|
13
14
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
14
15
|
# @param aName [String]
|
15
16
|
# @param arguments [Array<String>]
|
16
17
|
# @param body [Ast::LoxBlockStmt]
|
17
18
|
def initialize(aPosition, aName, paramList, aBody)
|
18
|
-
super(aPosition
|
19
|
+
super(aPosition)
|
19
20
|
@name = aName.dup
|
20
21
|
@params = paramList
|
21
22
|
@body = aBody
|
23
|
+
@is_method = false
|
22
24
|
end
|
23
25
|
|
24
26
|
# Part of the 'visitee' role in Visitor design pattern.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lox_node'
|
4
|
+
|
5
|
+
module Loxxy
|
6
|
+
module Ast
|
7
|
+
class LoxGetExpr < LoxNode
|
8
|
+
# @return [Ast::LoxNode] the object to which the property belongs to
|
9
|
+
attr_accessor :object
|
10
|
+
|
11
|
+
# @return [String] Name of an object property
|
12
|
+
attr_reader :property
|
13
|
+
|
14
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
|
+
# @param aPropertyName [String] Name of an object property
|
16
|
+
def initialize(aPosition, aPropertyName)
|
17
|
+
super(aPosition)
|
18
|
+
@property = aPropertyName
|
19
|
+
end
|
20
|
+
|
21
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
22
|
+
# @param visitor [ASTVisitor] the visitor
|
23
|
+
def accept(visitor)
|
24
|
+
visitor.visit_get_expr(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
alias callee= object=
|
28
|
+
end # class
|
29
|
+
end # module
|
30
|
+
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
|
+
class LoxSetExpr < LoxCompoundExpr
|
8
|
+
# @return [Ast::LoxNode] the object to which the property belongs to
|
9
|
+
attr_reader :object
|
10
|
+
|
11
|
+
# @return [String] Name of an object property
|
12
|
+
attr_accessor :property
|
13
|
+
|
14
|
+
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
|
+
# @param anObject [Ast::LoxNode] The object which the given property is being set
|
16
|
+
def initialize(aPosition, anObject)
|
17
|
+
super(aPosition, [])
|
18
|
+
@object = anObject
|
19
|
+
end
|
20
|
+
|
21
|
+
# Part of the 'visitee' role in Visitor design pattern.
|
22
|
+
# @param visitor [ASTVisitor] the visitor
|
23
|
+
def accept(visitor)
|
24
|
+
visitor.visit_set_expr(self)
|
25
|
+
end
|
26
|
+
end # class
|
27
|
+
end # module
|
28
|
+
end # module
|
@@ -69,8 +69,15 @@ module Loxxy
|
|
69
69
|
# Do nothing, subnodes were already evaluated
|
70
70
|
end
|
71
71
|
|
72
|
-
def after_class_stmt(aClassStmt,
|
73
|
-
|
72
|
+
def after_class_stmt(aClassStmt, aVisitor)
|
73
|
+
# Convert LoxFunStmt into LoxFunction
|
74
|
+
meths = aClassStmt.body.map do |func_node|
|
75
|
+
func_node.is_method = true
|
76
|
+
func_node.accept(aVisitor)
|
77
|
+
stack.pop
|
78
|
+
end
|
79
|
+
|
80
|
+
klass = LoxClass.new(aClassStmt.name, meths, self)
|
74
81
|
new_var = Variable.new(aClassStmt.name, klass)
|
75
82
|
symbol_table.insert(new_var)
|
76
83
|
end
|
@@ -153,6 +160,18 @@ module Loxxy
|
|
153
160
|
variable.assign(value)
|
154
161
|
end
|
155
162
|
|
163
|
+
def after_set_expr(aSetExpr, aVisitor)
|
164
|
+
value = stack.pop
|
165
|
+
# Evaluate object part
|
166
|
+
aSetExpr.object.accept(aVisitor)
|
167
|
+
assignee = stack.pop
|
168
|
+
unless assignee.kind_of?(LoxInstance)
|
169
|
+
raise StandardError, 'Only instances have fields.'
|
170
|
+
end
|
171
|
+
|
172
|
+
assignee.set(aSetExpr.property, value)
|
173
|
+
end
|
174
|
+
|
156
175
|
def after_logical_expr(aLogicalExpr, visitor)
|
157
176
|
op = aLogicalExpr.operator
|
158
177
|
operand1 = stack.pop # only first operand was evaluated
|
@@ -235,6 +254,16 @@ module Loxxy
|
|
235
254
|
end
|
236
255
|
end
|
237
256
|
|
257
|
+
def after_get_expr(aGetExpr, aVisitor)
|
258
|
+
aGetExpr.object.accept(aVisitor)
|
259
|
+
instance = stack.pop
|
260
|
+
unless instance.kind_of?(LoxInstance)
|
261
|
+
raise StandardError, 'Only instances have properties.'
|
262
|
+
end
|
263
|
+
|
264
|
+
stack.push instance.get(aGetExpr.property)
|
265
|
+
end
|
266
|
+
|
238
267
|
def after_grouping_expr(_groupingExpr)
|
239
268
|
# Do nothing: work was already done by visiting /evaluating the subexpression
|
240
269
|
end
|
@@ -242,7 +271,7 @@ module Loxxy
|
|
242
271
|
def after_variable_expr(aVarExpr, aVisitor)
|
243
272
|
var_name = aVarExpr.name
|
244
273
|
var = variable_lookup(aVarExpr)
|
245
|
-
raise StandardError, "
|
274
|
+
raise StandardError, "Undefined variable '#{var_name}'." unless var
|
246
275
|
|
247
276
|
var.value.accept(aVisitor) # Evaluate variable value then push on stack
|
248
277
|
end
|
@@ -259,8 +288,12 @@ module Loxxy
|
|
259
288
|
|
260
289
|
def after_fun_stmt(aFunStmt, _visitor)
|
261
290
|
function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
|
262
|
-
|
263
|
-
|
291
|
+
if aFunStmt.is_method
|
292
|
+
stack.push function
|
293
|
+
else
|
294
|
+
new_var = Variable.new(aFunStmt.name, function)
|
295
|
+
symbol_table.insert(new_var)
|
296
|
+
end
|
264
297
|
end
|
265
298
|
|
266
299
|
private
|
@@ -10,15 +10,18 @@ module Loxxy
|
|
10
10
|
# @return [String] The name of the class
|
11
11
|
attr_reader :name
|
12
12
|
|
13
|
-
# @return [
|
14
|
-
attr_reader :
|
13
|
+
# @return [Hash{String => LoxFunction}] the list of methods
|
14
|
+
attr_reader :meths
|
15
15
|
attr_reader :stack
|
16
16
|
|
17
17
|
# Create a class with given name
|
18
18
|
# @param aName [String] The name of the class
|
19
19
|
def initialize(aName, theMethods, anEngine)
|
20
20
|
@name = aName.dup
|
21
|
-
@
|
21
|
+
@meths = {}
|
22
|
+
theMethods.each do |func|
|
23
|
+
meths[func.name] = func
|
24
|
+
end
|
22
25
|
@stack = anEngine.stack
|
23
26
|
end
|
24
27
|
|
@@ -35,6 +38,11 @@ module Loxxy
|
|
35
38
|
engine.stack.push(instance)
|
36
39
|
end
|
37
40
|
|
41
|
+
# @param aName [String] the method name to search for
|
42
|
+
def find_method(aName)
|
43
|
+
meths[aName]
|
44
|
+
end
|
45
|
+
|
38
46
|
# Logical negation.
|
39
47
|
# As a function is a truthy thing, its negation is thus false.
|
40
48
|
# @return [Datatype::False]
|
@@ -6,16 +6,20 @@ module Loxxy
|
|
6
6
|
module BackEnd
|
7
7
|
# Runtime representation of a Lox object (instance).
|
8
8
|
class LoxInstance
|
9
|
-
# @return BackEnd::LoxClass] the class
|
9
|
+
# @return BackEnd::LoxClass] the class that this object is an instance of
|
10
10
|
attr_reader :klass
|
11
11
|
|
12
12
|
attr_reader :stack
|
13
13
|
|
14
|
+
# @return [Hash{String => BuiltinDatatype | LoxFunction | LoxInstance }]
|
15
|
+
attr_reader :fields
|
16
|
+
|
14
17
|
# Create an instance from given class
|
15
18
|
# @param aClass [BackEnd::LoxClass] the class this this object belong
|
16
19
|
def initialize(aClass, anEngine)
|
17
20
|
@klass = aClass
|
18
21
|
@stack = anEngine.stack
|
22
|
+
@fields = {}
|
19
23
|
end
|
20
24
|
|
21
25
|
def accept(_visitor)
|
@@ -26,6 +30,29 @@ module Loxxy
|
|
26
30
|
def to_str
|
27
31
|
"#{klass.to_str} instance"
|
28
32
|
end
|
33
|
+
|
34
|
+
# Look up the value of property with given name
|
35
|
+
# aName [String] name of object property
|
36
|
+
def get(aName)
|
37
|
+
return fields[aName] if fields.include? aName
|
38
|
+
|
39
|
+
method = klass.find_method(aName)
|
40
|
+
unless method
|
41
|
+
raise StandardError, "Undefined property '#{aName}'."
|
42
|
+
end
|
43
|
+
|
44
|
+
method
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set the value of property with given name
|
48
|
+
# aName [String] name of object property
|
49
|
+
def set(aName, aValue)
|
50
|
+
unless fields.include? aName
|
51
|
+
raise StandardError, "Undefined property '#{aName}'."
|
52
|
+
end
|
53
|
+
|
54
|
+
fields[aName] = aValue
|
55
|
+
end
|
29
56
|
end # class
|
30
57
|
end # module
|
31
58
|
end # module
|
@@ -58,8 +58,11 @@ module Loxxy
|
|
58
58
|
declare(aClassStmt.name)
|
59
59
|
end
|
60
60
|
|
61
|
-
def after_class_stmt(aClassStmt,
|
61
|
+
def after_class_stmt(aClassStmt, aVisitor)
|
62
62
|
define(aClassStmt.name)
|
63
|
+
aClassStmt.body.each do |fun_stmt|
|
64
|
+
resolve_function(fun_stmt, :method, aVisitor)
|
65
|
+
end
|
63
66
|
end
|
64
67
|
|
65
68
|
def before_for_stmt(aForStmt)
|
@@ -109,6 +112,11 @@ module Loxxy
|
|
109
112
|
resolve_local(anAssignExpr, aVisitor)
|
110
113
|
end
|
111
114
|
|
115
|
+
def after_set_expr(aSetExpr, aVisitor)
|
116
|
+
# Evaluate object part
|
117
|
+
aSetExpr.object.accept(aVisitor)
|
118
|
+
end
|
119
|
+
|
112
120
|
# Variable expressions require their variables resolved
|
113
121
|
def before_variable_expr(aVarExpr)
|
114
122
|
var_name = aVarExpr.name
|
@@ -127,6 +135,11 @@ module Loxxy
|
|
127
135
|
aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
|
128
136
|
end
|
129
137
|
|
138
|
+
def after_get_expr(aGetExpr, aVisitor)
|
139
|
+
# Evaluate object part
|
140
|
+
aGetExpr.object.accept(aVisitor)
|
141
|
+
end
|
142
|
+
|
130
143
|
# function declaration creates a new scope for its body & binds its parameters for that scope
|
131
144
|
def before_fun_stmt(aFunStmt, aVisitor)
|
132
145
|
declare(aFunStmt.name)
|
@@ -87,7 +87,7 @@ module Loxxy
|
|
87
87
|
rule('expression' => 'assignment')
|
88
88
|
rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
|
89
89
|
rule('assignment' => 'logic_or')
|
90
|
-
rule('owner_opt' => 'call DOT')
|
90
|
+
rule('owner_opt' => 'call DOT').as 'set_expr'
|
91
91
|
rule('owner_opt' => [])
|
92
92
|
rule('logic_or' => 'logic_and')
|
93
93
|
rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
|
@@ -132,7 +132,7 @@ module Loxxy
|
|
132
132
|
rule('refinement_plus' => 'refinement_plus refinement').as 'refinement_plus_more'
|
133
133
|
rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
|
134
134
|
rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
|
135
|
-
rule('refinement' => 'DOT IDENTIFIER')
|
135
|
+
rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
|
136
136
|
rule('primary' => 'TRUE').as 'literal_expr'
|
137
137
|
rule('primary' => 'FALSE').as 'literal_expr'
|
138
138
|
rule('primary' => 'NIL').as 'literal_expr'
|
data/lib/loxxy/version.rb
CHANGED
@@ -305,6 +305,83 @@ LOX_END
|
|
305
305
|
expect(expr.operands[1].operands[1].literal.value).to eq(5)
|
306
306
|
end
|
307
307
|
end # context
|
308
|
+
|
309
|
+
context 'Object orientation:' do
|
310
|
+
it 'should parse object property get access' do
|
311
|
+
input = 'print someObject.someProperty;'
|
312
|
+
ptree = subject.parse(input)
|
313
|
+
expr = ptree.root.subnodes[0]
|
314
|
+
expect(expr).to be_kind_of(Ast::LoxPrintStmt)
|
315
|
+
get_expr = expr.subnodes[0]
|
316
|
+
expect(get_expr).to be_kind_of(Ast::LoxGetExpr)
|
317
|
+
expect(get_expr.object.name).to eq('someObject')
|
318
|
+
expect(get_expr.property).to eq('someProperty')
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'should parse nested call expressions' do
|
322
|
+
input = 'print egg.scramble(3).with(cheddar);'
|
323
|
+
# From section 12.3.1, one expects something like:
|
324
|
+
# LoxCallExpr
|
325
|
+
# +- arguments = ['cheddar']
|
326
|
+
# +- callee = LoxGetExpr
|
327
|
+
# +- property = 'with'
|
328
|
+
# +- object = LoxCallExpr
|
329
|
+
# +- arguments = [3]
|
330
|
+
# +- callee = LoxGetExpr
|
331
|
+
# +- property = 'scramble'
|
332
|
+
# +- object = variable 'egg'
|
333
|
+
ptree = subject.parse(input)
|
334
|
+
print_stmt = ptree.root.subnodes[0]
|
335
|
+
expect(print_stmt).to be_kind_of(Ast::LoxPrintStmt)
|
336
|
+
outer_call = print_stmt.subnodes[0]
|
337
|
+
expect(outer_call).to be_kind_of(Ast::LoxCallExpr)
|
338
|
+
expect(outer_call.arguments[0].name).to eq('cheddar')
|
339
|
+
expect(outer_call.callee).to be_kind_of(Ast::LoxGetExpr)
|
340
|
+
expect(outer_call.callee.property).to eq('with')
|
341
|
+
inner_call = outer_call.callee.object
|
342
|
+
expect(inner_call).to be_kind_of(Ast::LoxCallExpr)
|
343
|
+
expect(inner_call.arguments[0].literal).to eq(3)
|
344
|
+
expect(inner_call.callee).to be_kind_of(Ast::LoxGetExpr)
|
345
|
+
expect(inner_call.callee.property).to eq('scramble')
|
346
|
+
expect(inner_call.callee.object.name).to eq('egg')
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'should parse object property set access' do
|
350
|
+
input = 'someObject.someProperty = value;'
|
351
|
+
ptree = subject.parse(input)
|
352
|
+
expr = ptree.root.subnodes[0]
|
353
|
+
expect(expr).to be_kind_of(Ast::LoxSetExpr)
|
354
|
+
expect(expr.object.name).to eq('someObject')
|
355
|
+
expect(expr.property).to eq('someProperty')
|
356
|
+
expect(expr.subnodes[0]).to be_kind_of(Ast::LoxVariableExpr)
|
357
|
+
expect(expr.subnodes[0].name).to eq('value')
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'should parse complex set access' do
|
361
|
+
input = 'breakfast.omelette.filling.meat = ham;'
|
362
|
+
# From section 12.3.2, one expects something like:
|
363
|
+
# LoxSetExpr
|
364
|
+
# +- property = 'meat'
|
365
|
+
# +- subnodes[0] = LoxVariableExpr 'ham'
|
366
|
+
# +- object = LoxGetExpr
|
367
|
+
# +- property = 'filling'
|
368
|
+
# +- object = LoxGetExpr
|
369
|
+
# +- property = 'omelette'
|
370
|
+
# +- object = LoxVariableExpr 'breakfast'
|
371
|
+
ptree = subject.parse(input)
|
372
|
+
expr = ptree.root.subnodes[0]
|
373
|
+
expect(expr).to be_kind_of(Ast::LoxSetExpr)
|
374
|
+
expect(expr.property).to eq('meat')
|
375
|
+
expect(expr.subnodes[0]).to be_kind_of(Ast::LoxVariableExpr)
|
376
|
+
expect(expr.subnodes[0].name).to eq('ham')
|
377
|
+
expect(expr.object).to be_kind_of(Ast::LoxGetExpr)
|
378
|
+
expect(expr.object.property).to eq('filling')
|
379
|
+
expect(expr.object.object).to be_kind_of(Ast::LoxGetExpr)
|
380
|
+
expect(expr.object.object.property).to eq('omelette')
|
381
|
+
expect(expr.object.object.object).to be_kind_of(Ast::LoxVariableExpr)
|
382
|
+
expect(expr.object.object.object.name).to eq('breakfast')
|
383
|
+
end
|
384
|
+
end # context
|
308
385
|
end # describe
|
309
386
|
end # module
|
310
387
|
end # module
|
data/spec/interpreter_spec.rb
CHANGED
@@ -451,8 +451,19 @@ LOX_END
|
|
451
451
|
end
|
452
452
|
# rubocop: enable Style/StringConcatenation
|
453
453
|
|
454
|
-
it 'should
|
454
|
+
it 'should print the hello world message' do
|
455
455
|
program = <<-LOX_END
|
456
|
+
var greeting = "Hello"; // Declaring a variable
|
457
|
+
print greeting + ", " + "world!"; // ... Playing with concatenation
|
458
|
+
LOX_END
|
459
|
+
expect { subject.evaluate(program) }.not_to raise_error
|
460
|
+
expect(sample_cfg[:ostream].string).to eq('Hello, world!')
|
461
|
+
end
|
462
|
+
end # context
|
463
|
+
|
464
|
+
context 'Object orientation:' do
|
465
|
+
let(:duck_class) do
|
466
|
+
snippet = <<-LOX_END
|
456
467
|
class Duck {
|
457
468
|
noise() {
|
458
469
|
quack();
|
@@ -462,33 +473,41 @@ LOX_END
|
|
462
473
|
print "quack";
|
463
474
|
}
|
464
475
|
}
|
465
|
-
|
476
|
+
LOX_END
|
477
|
+
|
478
|
+
snippet
|
479
|
+
end
|
480
|
+
|
481
|
+
it 'should support class declaration' do
|
482
|
+
program = <<-LOX_END
|
483
|
+
#{duck_class}
|
484
|
+
|
485
|
+
print Duck; // Class names can appear in statements
|
466
486
|
LOX_END
|
467
487
|
expect { subject.evaluate(program) }.not_to raise_error
|
468
488
|
expect(sample_cfg[:ostream].string).to eq('Duck')
|
469
489
|
end
|
470
490
|
|
471
|
-
it 'should support instance creation' do
|
491
|
+
it 'should support default instance creation' do
|
472
492
|
program = <<-LOX_END
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
}
|
477
|
-
}
|
478
|
-
var daffy = Duck();
|
493
|
+
#{duck_class}
|
494
|
+
|
495
|
+
var daffy = Duck(); // Default constructor
|
479
496
|
print daffy;
|
480
497
|
LOX_END
|
481
498
|
expect { subject.evaluate(program) }.not_to raise_error
|
482
499
|
expect(sample_cfg[:ostream].string).to eq('Duck instance')
|
483
500
|
end
|
484
501
|
|
485
|
-
it 'should
|
502
|
+
it 'should support calls to method' do
|
486
503
|
program = <<-LOX_END
|
487
|
-
|
488
|
-
|
504
|
+
#{duck_class}
|
505
|
+
|
506
|
+
var daffy = Duck(); // Default constructor
|
507
|
+
daffy.quack();
|
489
508
|
LOX_END
|
490
509
|
expect { subject.evaluate(program) }.not_to raise_error
|
491
|
-
expect(sample_cfg[:ostream].string).to eq('
|
510
|
+
expect(sample_cfg[:ostream].string).to eq('quack')
|
492
511
|
end
|
493
512
|
end # context
|
494
513
|
end # describe
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: loxxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04-
|
11
|
+
date: 2021-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -97,6 +97,7 @@ files:
|
|
97
97
|
- lib/loxxy/ast/lox_compound_expr.rb
|
98
98
|
- lib/loxxy/ast/lox_for_stmt.rb
|
99
99
|
- lib/loxxy/ast/lox_fun_stmt.rb
|
100
|
+
- lib/loxxy/ast/lox_get_expr.rb
|
100
101
|
- lib/loxxy/ast/lox_grouping_expr.rb
|
101
102
|
- lib/loxxy/ast/lox_if_stmt.rb
|
102
103
|
- lib/loxxy/ast/lox_literal_expr.rb
|
@@ -106,6 +107,7 @@ files:
|
|
106
107
|
- lib/loxxy/ast/lox_print_stmt.rb
|
107
108
|
- lib/loxxy/ast/lox_return_stmt.rb
|
108
109
|
- lib/loxxy/ast/lox_seq_decl.rb
|
110
|
+
- lib/loxxy/ast/lox_set_expr.rb
|
109
111
|
- lib/loxxy/ast/lox_unary_expr.rb
|
110
112
|
- lib/loxxy/ast/lox_var_stmt.rb
|
111
113
|
- lib/loxxy/ast/lox_variable_expr.rb
|