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 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