loxxy 0.1.12 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 665b2f201c4e369bb46b4b62cd8398f8d1f6f563b073fb6e1595b767f306c627
4
- data.tar.gz: 7bd3ca2024f2f05150d3fd8af9cdaee28d6dc4d2bbe48ebfe6ae9fd78166fc7f
3
+ metadata.gz: 172ec5df8e553332b497968f7b910383cb38314aabbdb84fdb42fba6986841ea
4
+ data.tar.gz: 3d72fd4f13f507f88e9e735e1ebdd0d63fd603b771f9cfb552b9aa38b5b3ca6a
5
5
  SHA512:
6
- metadata.gz: 0cffb636754ac0140aa7873955c75d8e6a048a0806cacf22484de91bacecb4fdb7d55127f7ff6e2f5602ff02f1eb9f24de788a6be0053768c68f12b9cdd0caab
7
- data.tar.gz: 331fb47a8a46a9ea6a086556814b95aba7d7a4d7a3c600f82acacfd38dd567c657ca79f6ce683e17e64e8104167692a2b49e7628bac9d8536a5bd0625ceab347
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
- var_name = theChildren[1].token.lexeme.dup
273
- Ast::LoxAssignExpr.new(tokens[1].position, var_name, theChildren[3])
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 aXlassStmt [AST::LOXClassStmt] the for statement node to visit
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, theMethods)
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 'lox_compound_expr'
3
+ require_relative 'lox_node'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
7
  # rubocop: disable Style/AccessorGrouping
8
- class LoxFunStmt < LoxCompoundExpr
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, _visitor)
73
- klass = LoxClass.new(aClassStmt.name, aClassStmt.methods, self)
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, "Unknown variable #{var_name}" unless var
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
- new_var = Variable.new(aFunStmt.name, function)
263
- symbol_table.insert(new_var)
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 [Array<>] the list of methods
14
- attr_reader :methods
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
- @methods = theMethods
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 this this object belong
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, _visitor)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.1.12'
4
+ VERSION = '0.1.13'
5
5
  end
@@ -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
@@ -451,8 +451,19 @@ LOX_END
451
451
  end
452
452
  # rubocop: enable Style/StringConcatenation
453
453
 
454
- it 'should support class declaration' do
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
- print Duck;
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
- class Duck {
474
- quack() {
475
- print "quack";
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 print the hello world message' do
502
+ it 'should support calls to method' do
486
503
  program = <<-LOX_END
487
- var greeting = "Hello"; // Declaring a variable
488
- print greeting + ", " + "world!"; // ... Playing with concatenation
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('Hello, world!')
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.12
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-03 00:00:00.000000000 Z
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