loxxy 0.1.09 → 0.1.14

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: 1d027baa99cb59b7a102b55d1dec07e75f7cb17a71c42708881507ae8bc83b75
4
- data.tar.gz: 6f27ba42c296a5845185fb8d746fd8cf24d9b8b71c2c98d590fa8a9c22cac33f
3
+ metadata.gz: 51728e22602ac35f1c31a9540daf75d4dcf0e84d1f323dd3ec287440b6fe21ef
4
+ data.tar.gz: beda9e48d5024a567bf2e1dd84f107991443e8bf2592b2a01f39e2ca39adc751
5
5
  SHA512:
6
- metadata.gz: 0b71f56915f53a4ece5bcfde6b74ee7d407517b54038b3a00a945813acd3137c5da8800f8ac2fea71220b35557d18eb63fef25ac0b90297b1067a9ed65e70a55
7
- data.tar.gz: b6faed818eff75c6beb32137cd97777b332b9a79994ab74ddb70cc4a257778e8a2571ab59d8c612fc89e5bf5f3c7a4e9f3868748ae737b3fe0cce1e404681460
6
+ metadata.gz: 72688ddf02138d6bab0cdbf8bcb3c0087a17e461dbd269fa56f4ddfe4f674172fd957ae071f0e61b6c7296ff3653a2734cc4fb9573340914337621b1d198ed0d
7
+ data.tar.gz: 45374a6d536f32c81f8bfdea4eeb670bb8c9d0ea0bdbc1d1c151379601d75f11e0353f20feb705a68cb647352f49c4c1231e7ba087081b9720297f79d1427db7
data/CHANGELOG.md CHANGED
@@ -1,7 +1,87 @@
1
+ ## [0.1.14] - 2021-04-05
2
+ - `Loxxy` now implements the 'this' keyword
3
+
4
+ ### New
5
+ - Class `Ast::LoxThisExpr` a syntax node that represents an occurrence of the `this` keyword
6
+ - Method `Ast::ASTBuilder#reduce_this_expr` parse action for this keyword
7
+ - Method `Ast::Visitor#visit_this_expr` visit of an `Ast::LoxThisExpr` node
8
+ - Method `BackEnd::Engine#after_this_expr` runtime action for this keyword (i.e. refers to current instance)
9
+ - Method `BackEnd::LoxFunction#bind` implementation of bound method
10
+
11
+ ### Changed
12
+ - Class `BackEnd::Resolver` implementing semantic actions for `this` keyword
13
+ - File `grammar.rb` added name to a syntax rule
14
+
15
+ ## [0.1.13] - 2021-04-05
16
+ - `Loxxy` now implements method calls
17
+
18
+ ### New
19
+ - Class `Ast::LoxGetExpr` a syntax node that represents a read access to an object property
20
+ - Class `Ast::LoxSetExpr` a syntax node that represents a write access to an object property
21
+ - Method `Ast::ASTBuilder#reduce_set_expr` parse action for write access to an object property
22
+ - Method `Ast::ASTBuilder#reduce_get_expr` parse action for read access to an object property
23
+ - Method `Ast::Visitor#visit_set_expr` visit of an `Ast::LoxSetExpr` node
24
+ - Method `Ast::Visitor#visit_get_expr` visit of an `Ast::LoxGetExpr` node
25
+ - Method `BackEnd::Engine#after_set_expr` runtime action for property setting
26
+ - Method `BackEnd::Engine#after_get_expr` runtime action for property getting
27
+ - Method `BackEnd::LoxInstance#set` implementation of write accessor
28
+ - Method `BackEnd::LoxInstance#get` implementation of read accessor
29
+ - Method `BackEnd::Resolver#after_set_expr` resolve action for property setting
30
+ - Method `BackEnd::Resolver#after_get_expr` resolve action for property getting
31
+
32
+ ### Changed
33
+ - Method `Ast::ASTBuilder#reduce_assign_expr` expanded to support write access to an object property
34
+ - Class `LoxClassStmt`: methods are now aggregate under the `body` attribute
35
+ - Class `LoxFunStmt`: has a new attribute `is_method` and inherits from `Ast::LoxNode`
36
+ - Method `BackEnd::Engine#after_class_stmt` methods are aggregated into the classes
37
+ - Method `BackEnd::Engine#after_fun_stmt` extension for method
38
+ - File `grammar.rb` added names to two syntax rules
39
+
40
+
41
+ ## [0.1.12] - 2021-04-03
42
+ - Intermediate version: `Loxxy` does instance creation (default constructor)
43
+
44
+ ### New
45
+ - Method `BackEnd::LoxClass#call` made class callable (for invoking the constructor)
46
+ - Class `BackEnd::LoxInstance` runtime representation of a Lox instance (object).
47
+
48
+ ### Changed
49
+ - Method `BackEnd::Engine#after_call_expr` Added Lox class as callable thing.
50
+
51
+ ### Fixed
52
+ - Method `Ast::ASTBuilder#reduce_class_body` couldn't handle properly empty classes
53
+
54
+ ## [0.1.11] - 2021-04-03
55
+ - Intermediate version: `Loxxy` does class declarations
56
+
57
+ ### New
58
+ - Class `Ast::LoxClassStmt` a syntax node that represents a class declaration
59
+ - Method `Ast::ASTBuilder#reduce_class_decl` creates a `LoxClassStmt` instance
60
+ - Method `Ast::ASTBuilder#reduce_class_name`
61
+ - Method `Ast::ASTBuilder#reduce_reduce_class_body` collect the methods of the class
62
+ - Method `Ast::ASTBuilder#reduce_method_plus_more` for dealing with methods
63
+ - Method `Ast::ASTBuilder#reduce_method_plus_end`
64
+ - Method `Ast::ASTVisitor#visit_class_stmt` for visiting an `Ast::LoxClassStmt` node
65
+ - Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
66
+ - Method `BackEnd::Engine#after_class_stmt`
67
+ - Method `BackEnd::Resolver#after_class_stmt`
68
+ - Method `BackEnd::Resolver#before_class_stmt`
69
+ - Class `BackEnd::LoxClass` runtime representation of a Lox class.
70
+
71
+ ### Changed
72
+ - File `grammar.rb` refactoring of class declaration syntax rules
73
+
74
+ ## [0.1.10] - 2021-03-31
75
+ - Flag return statements occurring outside functions as an error
76
+
77
+ ### Changed
78
+ - Class `BackEnd::Resolver` Added attribute `current_function` to know whether the visited parse node is located inside a function
79
+
80
+
1
81
  ## [0.1.09] - 2021-03-28
2
82
  - Fix and test suite for return statements
3
83
 
4
- ### Changed
84
+ ### CHANGED
5
85
  - `Loxxy` reports an error when a return statement occurs in top-level scope
6
86
 
7
87
  ### Fixed
@@ -13,7 +93,7 @@
13
93
  ### New
14
94
  - Class `BackEnd::Resolver` implements the variable resolution (whenever a variable is in use, locate the declaration of that variable)
15
95
 
16
- ### Changed
96
+ ### CHANGED
17
97
  - Class `Ast::Visitor` changes in some method signatures
18
98
  - Class `BackEnd::Engine` new attribute `resolver` that points to a `BackEnd::Resolver` instance
19
99
  - Class `BackEnd::Engine` several methods dealing with variables have been adapted to take the resolver into account.
@@ -103,9 +183,9 @@
103
183
  - Method `Ast::ASTVisitor#visit_fun_stmt` for visiting an `Ast::LoxFunStmt` node
104
184
  - Method `Ast::LoxBlockStmt#empty?` returns true if the code block is empty
105
185
  - Method `BackEnd::Engine#after_fun_stmt`
106
- - Method `Backend::NativeFunction#call`
107
- - Method `Backend::NativeFunction#to_str`
108
- - Method `Backend::Function` implementation of a function object.
186
+ - Method `BackEnd::NativeFunction#call`
187
+ - Method `BackEnd::NativeFunction#to_str`
188
+ - Method `BackEnd::LoxFunction` runtime representation of a Lox function.
109
189
 
110
190
  ### Changed
111
191
  - Method `BackEnd::Engine#after_call_expr`
data/README.md CHANGED
@@ -177,8 +177,8 @@ Loxxy supports single line C-style comments.
177
177
  ### Keywords
178
178
  Loxxy implements the following __Lox__ reserved keywords:
179
179
  ```lang-none
180
- and, else, false, for, fun, if,
181
- nil, or, print, true, var, while
180
+ and, class, else, false, for, fun, if,
181
+ nil, or, print, return, this, true, var, while
182
182
  ```
183
183
 
184
184
  ### Datatypes
@@ -1,14 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'lox_fun_stmt'
4
+ require_relative 'lox_this_expr'
4
5
  require_relative 'lox_variable_expr'
5
6
  require_relative 'lox_literal_expr'
6
7
  require_relative 'lox_noop_expr'
8
+ require_relative 'lox_get_expr'
7
9
  require_relative 'lox_call_expr'
8
10
  require_relative 'lox_grouping_expr'
9
11
  require_relative 'lox_unary_expr'
10
12
  require_relative 'lox_binary_expr'
11
13
  require_relative 'lox_logical_expr'
14
+ require_relative 'lox_set_expr'
12
15
  require_relative 'lox_assign_expr'
13
16
  require_relative 'lox_block_stmt'
14
17
  require_relative 'lox_while_stmt'
@@ -17,4 +20,5 @@ require_relative 'lox_print_stmt'
17
20
  require_relative 'lox_if_stmt'
18
21
  require_relative 'lox_for_stmt'
19
22
  require_relative 'lox_var_stmt'
23
+ require_relative 'lox_class_stmt'
20
24
  require_relative 'lox_seq_decl'
@@ -163,6 +163,31 @@ module Loxxy
163
163
  [theChildren[0]]
164
164
  end
165
165
 
166
+ # rule('classDecl' => 'CLASS classNaming class_body')
167
+ def reduce_class_decl(_production, _range, _tokens, theChildren)
168
+ Ast::LoxClassStmt.new(tokens[1].position, theChildren[1], theChildren[2])
169
+ end
170
+
171
+ # rule('classNaming' => 'IDENTIFIER')
172
+ def reduce_class_name(_production, _range, _tokens, theChildren)
173
+ theChildren[0].token.lexeme
174
+ end
175
+
176
+ # rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE')
177
+ def reduce_class_body(_production, _range, _tokens, theChildren)
178
+ theChildren[1].nil? ? [] : theChildren[1]
179
+ end
180
+
181
+ # rule('method_plus' => 'method_plus function')
182
+ def reduce_method_plus_more(_production, _range, _tokens, theChildren)
183
+ theChildren[0] << theChildren[1]
184
+ end
185
+
186
+ # rule('method_plus' => 'function')
187
+ def reduce_method_plus_end(_production, _range, _tokens, theChildren)
188
+ theChildren
189
+ end
190
+
166
191
  # rule('funDecl' => 'FUN function')
167
192
  def reduce_fun_decl(_production, _range, _tokens, theChildren)
168
193
  theChildren[1]
@@ -244,10 +269,22 @@ module Loxxy
244
269
 
245
270
  # rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment')
246
271
  def reduce_assign_expr(_production, _range, tokens, theChildren)
247
- var_name = theChildren[1].token.lexeme.dup
248
- 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])
249
285
  end
250
286
 
287
+
251
288
  # rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
252
289
  # TODO: is it meaningful to implement this rule?
253
290
 
@@ -292,6 +329,11 @@ module Loxxy
292
329
  LoxCallExpr.new(tokens[0].position, args)
293
330
  end
294
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
+
295
337
  # rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
296
338
  def reduce_grouping_expr(_production, _range, tokens, theChildren)
297
339
  subexpr = theChildren[1]
@@ -312,6 +354,11 @@ module Loxxy
312
354
  LoxVariableExpr.new(tokens[0].position, var_name)
313
355
  end
314
356
 
357
+ # rule('primary' => 'THIS')
358
+ def reduce_this_expr(_production, _range, tokens, _children)
359
+ LoxThisExpr.new(tokens[0].position)
360
+ end
361
+
315
362
  # rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
316
363
  def reduce_function(_production, _range, _tokens, theChildren)
317
364
  first_child = theChildren.first
@@ -67,6 +67,14 @@ module Loxxy
67
67
  broadcast(:after_var_stmt, aVarStmt)
68
68
  end
69
69
 
70
+ # Visit event. The visitor is about to visit a class declaration.
71
+ # @param aClassStmt [AST::LOXClassStmt] the for statement node to visit
72
+ def visit_class_stmt(aClassStmt)
73
+ broadcast(:before_class_stmt, aClassStmt)
74
+ traverse_subnodes(aClassStmt) # The methods are visited here...
75
+ broadcast(:after_class_stmt, aClassStmt, self)
76
+ end
77
+
70
78
  # Visit event. The visitor is about to visit a for statement.
71
79
  # @param aForStmt [AST::LOXForStmt] the for statement node to visit
72
80
  def visit_for_stmt(aForStmt)
@@ -123,6 +131,13 @@ module Loxxy
123
131
  broadcast(:after_assign_expr, anAssignExpr, self)
124
132
  end
125
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
+
126
141
  # Visit event. The visitor is about to visit a logical expression.
127
142
  # Since logical expressions may take shorcuts by not evaluating all their
128
143
  # sub-expressiosns, they are responsible for visiting or not their children.
@@ -161,6 +176,12 @@ module Loxxy
161
176
  broadcast(:after_call_expr, aCallExpr, self)
162
177
  end
163
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
+
164
185
  # Visit event. The visitor is about to visit a grouping expression.
165
186
  # @param aGroupingExpr [AST::LoxGroupingExpr] grouping expression to visit
166
187
  def visit_grouping_expr(aGroupingExpr)
@@ -184,6 +205,13 @@ module Loxxy
184
205
  broadcast(:after_variable_expr, aVariableExpr, self)
185
206
  end
186
207
 
208
+ # Visit event. The visitor is about to visit the this keyword.
209
+ # @param aThisExpr [Ast::LoxThisExpr] this expression
210
+ def visit_this_expr(aThisExpr)
211
+ broadcast(:before_this_expr, aThisExpr)
212
+ broadcast(:after_this_expr, aThisExpr, self)
213
+ end
214
+
187
215
  # Visit event. The visitor is about to visit the given terminal datatype value.
188
216
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
189
217
  def visit_builtin(aValue)
@@ -195,7 +223,6 @@ module Loxxy
195
223
  # @param aFunStmt [AST::LoxFunStmt] function declaration to visit
196
224
  def visit_fun_stmt(aFunStmt)
197
225
  broadcast(:before_fun_stmt, aFunStmt, self)
198
- traverse_subnodes(aFunStmt)
199
226
  broadcast(:after_fun_stmt, aFunStmt, self)
200
227
  end
201
228
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxClassStmt < LoxCompoundExpr
8
+ # @return [String] the class name
9
+ attr_reader :name
10
+
11
+ # @return [Array<Ast::LoxFunStmt>] the methods
12
+ attr_reader :body
13
+
14
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
+ # @param condExpr [Loxxy::Ast::LoxNode] iteration condition
16
+ # @param theBody [Array<Loxxy::Ast::LoxNode>]
17
+ def initialize(aPosition, aName, theMethods)
18
+ super(aPosition, [])
19
+ @name = aName.dup
20
+ @body = theMethods
21
+ end
22
+
23
+ # Part of the 'visitee' role in Visitor design pattern.
24
+ # @param visitor [Ast::ASTVisitor] the visitor
25
+ def accept(visitor)
26
+ visitor.visit_class_stmt(self)
27
+ end
28
+ end # class
29
+ end # module
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
- @name = aName
19
+ super(aPosition)
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
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_node'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ # A node in a parse tree that represents the occurrence of 'this' keyword.
8
+ class LoxThisExpr < LoxNode
9
+ # Duck-typing: behaves like a LoxVarExpr
10
+ # @return [String] return the this keyword
11
+ def name
12
+ 'this'
13
+ end
14
+
15
+ # Part of the 'visitee' role in Visitor design pattern.
16
+ # @param _visitor [LoxxyTreeVisitor] the visitor
17
+ def accept(aVisitor)
18
+ aVisitor.visit_this_expr(self)
19
+ end
20
+ end # class
21
+ end # module
22
+ end # module
@@ -3,6 +3,7 @@
3
3
  # Load all the classes implementing AST nodes
4
4
  require_relative '../ast/all_lox_nodes'
5
5
  require_relative 'binary_operator'
6
+ require_relative 'lox_class'
6
7
  require_relative 'lox_function'
7
8
  require_relative 'resolver'
8
9
  require_relative 'symbol_table'
@@ -68,6 +69,19 @@ module Loxxy
68
69
  # Do nothing, subnodes were already evaluated
69
70
  end
70
71
 
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)
81
+ new_var = Variable.new(aClassStmt.name, klass)
82
+ symbol_table.insert(new_var)
83
+ end
84
+
71
85
  def before_var_stmt(aVarStmt)
72
86
  new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
73
87
  symbol_table.insert(new_var)
@@ -146,6 +160,18 @@ module Loxxy
146
160
  variable.assign(value)
147
161
  end
148
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
+
149
175
  def after_logical_expr(aLogicalExpr, visitor)
150
176
  op = aLogicalExpr.operator
151
177
  operand1 = stack.pop # only first operand was evaluated
@@ -221,11 +247,23 @@ module Loxxy
221
247
  raise Loxxy::RuntimeError, msg
222
248
  end
223
249
  callee.call(self, aVisitor)
250
+ when LoxClass
251
+ callee.call(self, aVisitor)
224
252
  else
225
253
  raise Loxxy::RuntimeError, 'Can only call functions and classes.'
226
254
  end
227
255
  end
228
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
+
229
267
  def after_grouping_expr(_groupingExpr)
230
268
  # Do nothing: work was already done by visiting /evaluating the subexpression
231
269
  end
@@ -233,7 +271,7 @@ module Loxxy
233
271
  def after_variable_expr(aVarExpr, aVisitor)
234
272
  var_name = aVarExpr.name
235
273
  var = variable_lookup(aVarExpr)
236
- raise StandardError, "Unknown variable #{var_name}" unless var
274
+ raise StandardError, "Undefined variable '#{var_name}'." unless var
237
275
 
238
276
  var.value.accept(aVisitor) # Evaluate variable value then push on stack
239
277
  end
@@ -243,6 +281,11 @@ module Loxxy
243
281
  stack.push(literalExpr.literal)
244
282
  end
245
283
 
284
+ def after_this_expr(aThisExpr, aVisitor)
285
+ var = variable_lookup(aThisExpr)
286
+ var.value.accept(aVisitor) # Evaluate this value then push on stack
287
+ end
288
+
246
289
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
247
290
  def before_visit_builtin(aValue)
248
291
  stack.push(aValue)
@@ -250,8 +293,12 @@ module Loxxy
250
293
 
251
294
  def after_fun_stmt(aFunStmt, _visitor)
252
295
  function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
253
- new_var = Variable.new(aFunStmt.name, function)
254
- symbol_table.insert(new_var)
296
+ if aFunStmt.is_method
297
+ stack.push function
298
+ else
299
+ new_var = Variable.new(aFunStmt.name, function)
300
+ symbol_table.insert(new_var)
301
+ end
255
302
  end
256
303
 
257
304
  private
@@ -37,7 +37,6 @@ module Loxxy
37
37
  # @return [BackEnd::Variable] the variable
38
38
  def insert(anEntry)
39
39
  e = validated_entry(anEntry)
40
- # e.suffix = default_suffix if e.kind_of?(BackEnd::Variable)
41
40
  defns[e.name] = e
42
41
 
43
42
  e
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../datatype/all_datatypes'
4
+ require_relative 'lox_instance'
5
+
6
+ module Loxxy
7
+ module BackEnd
8
+ # Runtime representation of a Lox class.
9
+ class LoxClass
10
+ # @return [String] The name of the class
11
+ attr_reader :name
12
+
13
+ # @return [Hash{String => LoxFunction}] the list of methods
14
+ attr_reader :meths
15
+ attr_reader :stack
16
+
17
+ # Create a class with given name
18
+ # @param aName [String] The name of the class
19
+ def initialize(aName, theMethods, anEngine)
20
+ @name = aName.dup
21
+ @meths = {}
22
+ theMethods.each do |func|
23
+ meths[func.name] = func
24
+ end
25
+ @stack = anEngine.stack
26
+ end
27
+
28
+ def accept(_visitor)
29
+ stack.push self
30
+ end
31
+
32
+ def arity
33
+ 0
34
+ end
35
+
36
+ def call(engine, _visitor)
37
+ instance = LoxInstance.new(self, engine)
38
+ engine.stack.push(instance)
39
+ end
40
+
41
+ # @param aName [String] the method name to search for
42
+ def find_method(aName)
43
+ meths[aName]
44
+ end
45
+
46
+ # Logical negation.
47
+ # As a function is a truthy thing, its negation is thus false.
48
+ # @return [Datatype::False]
49
+ def !
50
+ Datatype::False.instance
51
+ end
52
+
53
+ # Text representation of a Lox class
54
+ def to_str
55
+ name
56
+ end
57
+ end # class
58
+ end # module
59
+ end # module
@@ -6,7 +6,6 @@ module Loxxy
6
6
  module BackEnd
7
7
  # rubocop: disable Style/AccessorGrouping
8
8
  # Representation of a Lox function.
9
- # It is a named slot that can be associated with a value at the time.
10
9
  class LoxFunction
11
10
  # @return [String] The name of the function (if any)
12
11
  attr_reader :name
@@ -18,7 +17,7 @@ module Loxxy
18
17
  attr_reader :closure
19
18
 
20
19
  # Create a function with given name
21
- # @param aName [String] The name of the variable
20
+ # @param aName [String] The name of the function
22
21
  def initialize(aName, parameterList, aBody, anEngine)
23
22
  @name = aName.dup
24
23
  @parameters = parameterList
@@ -37,7 +36,6 @@ module Loxxy
37
36
  end
38
37
 
39
38
  def call(engine, aVisitor)
40
- # new_env = Environment.new(engine.symbol_table.current_env)
41
39
  new_env = Environment.new(closure)
42
40
  engine.symbol_table.enter_environment(new_env)
43
41
 
@@ -54,6 +52,16 @@ module Loxxy
54
52
  engine.symbol_table.leave_environment
55
53
  end
56
54
 
55
+ def bind(anInstance)
56
+ new_env = Environment.new(closure)
57
+ this = Variable.new('this', anInstance)
58
+ new_env.insert(this)
59
+ bound_method = dup
60
+ bound_method.instance_variable_set(:@closure, new_env)
61
+
62
+ bound_method
63
+ end
64
+
57
65
  # Logical negation.
58
66
  # As a function is a truthy thing, its negation is thus false.
59
67
  # @return [Datatype::False]
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../datatype/all_datatypes'
4
+
5
+ module Loxxy
6
+ module BackEnd
7
+ # Runtime representation of a Lox object (instance).
8
+ class LoxInstance
9
+ # @return BackEnd::LoxClass] the class that this object is an instance of
10
+ attr_reader :klass
11
+
12
+ attr_reader :engine
13
+
14
+ # @return [Hash{String => BuiltinDatatype | LoxFunction | LoxInstance }]
15
+ attr_reader :fields
16
+
17
+ # Create an instance from given class
18
+ # @param aClass [BackEnd::LoxClass] the class this this object belong
19
+ def initialize(aClass, anEngine)
20
+ @klass = aClass
21
+ @engine = anEngine
22
+ @fields = {}
23
+ end
24
+
25
+ def accept(_visitor)
26
+ engine.stack.push self
27
+ end
28
+
29
+ # Text representation of a Lox instance
30
+ def to_str
31
+ "#{klass.to_str} instance"
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.bind(self)
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
56
+ end # class
57
+ end # module
58
+ end # module
@@ -22,9 +22,19 @@ module Loxxy
22
22
  # @return [Hash {LoxNode => Integer}]
23
23
  attr_reader :locals
24
24
 
25
+ # An indicator that tells we're in the middle of a function declaration
26
+ # @return [Symbol] must be one of: :none, :function
27
+ attr_reader :current_function
28
+
29
+ # An indicator that tells we're in the middle of a class declaration
30
+ # @return [Symbol] must be one of: :none, :class
31
+ attr_reader :current_class
32
+
25
33
  def initialize
26
34
  @scopes = []
27
35
  @locals = {}
36
+ @current_function = :none
37
+ @current_class = :none
28
38
  end
29
39
 
30
40
  # Given an abstract syntax parse tree visitor, launch the visit
@@ -48,6 +58,24 @@ module Loxxy
48
58
  end_scope
49
59
  end
50
60
 
61
+ # A class declaration adds a new variable to current scope
62
+ def before_class_stmt(aClassStmt)
63
+ declare(aClassStmt.name)
64
+ end
65
+
66
+ def after_class_stmt(aClassStmt, aVisitor)
67
+ previous_class = current_class
68
+ @current_class = :class
69
+ define(aClassStmt.name)
70
+ begin_scope
71
+ define('this')
72
+ aClassStmt.body.each do |fun_stmt|
73
+ resolve_function(fun_stmt, :method, aVisitor)
74
+ end
75
+ end_scope
76
+ @current_class = previous_class
77
+ end
78
+
51
79
  def before_for_stmt(aForStmt)
52
80
  before_block_stmt(aForStmt)
53
81
  end
@@ -69,6 +97,11 @@ module Loxxy
69
97
  msg = "Error at 'return': Can't return from top-level code."
70
98
  raise StandardError, msg
71
99
  end
100
+
101
+ if current_function == :none
102
+ msg = "Error at 'return': Can't return from outside a function."
103
+ raise StandardError, msg
104
+ end
72
105
  end
73
106
 
74
107
  def after_while_stmt(aWhileStmt, aVisitor)
@@ -90,6 +123,11 @@ module Loxxy
90
123
  resolve_local(anAssignExpr, aVisitor)
91
124
  end
92
125
 
126
+ def after_set_expr(aSetExpr, aVisitor)
127
+ # Evaluate object part
128
+ aSetExpr.object.accept(aVisitor)
129
+ end
130
+
93
131
  # Variable expressions require their variables resolved
94
132
  def before_variable_expr(aVarExpr)
95
133
  var_name = aVarExpr.name
@@ -108,11 +146,28 @@ module Loxxy
108
146
  aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
109
147
  end
110
148
 
149
+ def after_get_expr(aGetExpr, aVisitor)
150
+ # Evaluate object part
151
+ aGetExpr.object.accept(aVisitor)
152
+ end
153
+
154
+ def before_this_expr(_thisExpr)
155
+ if current_class == :none
156
+ msg = "Error at 'this': Can't use 'this' outside of a class."
157
+ raise StandardError, msg
158
+ end
159
+ end
160
+
161
+ def after_this_expr(aThisExpr, aVisitor)
162
+ # 'this' behaves closely to a local variable
163
+ resolve_local(aThisExpr, aVisitor)
164
+ end
165
+
111
166
  # function declaration creates a new scope for its body & binds its parameters for that scope
112
167
  def before_fun_stmt(aFunStmt, aVisitor)
113
168
  declare(aFunStmt.name)
114
169
  define(aFunStmt.name)
115
- resolve_function(aFunStmt, aVisitor)
170
+ resolve_function(aFunStmt, :function, aVisitor)
116
171
  end
117
172
 
118
173
  private
@@ -162,7 +217,9 @@ module Loxxy
162
217
  end
163
218
  end
164
219
 
165
- def resolve_function(aFunStmt, aVisitor)
220
+ def resolve_function(aFunStmt, funVisitState, aVisitor)
221
+ enclosing_function = current_function
222
+ @current_function = funVisitState
166
223
  begin_scope
167
224
 
168
225
  aFunStmt.params&.each do |param_name|
@@ -176,6 +233,7 @@ module Loxxy
176
233
  end
177
234
 
178
235
  end_scope
236
+ @current_function = enclosing_function
179
237
  end
180
238
  end # class
181
239
  end # mmodule
@@ -37,12 +37,14 @@ module Loxxy
37
37
  rule('declaration' => 'varDecl')
38
38
  rule('declaration' => 'statement')
39
39
 
40
- rule('classDecl' => 'CLASS classNaming class_body')
40
+ rule('classDecl' => 'CLASS classNaming class_body').as 'class_decl'
41
41
  rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
42
- rule('classNaming' => 'IDENTIFIER')
43
- rule('class_body' => 'LEFT_BRACE function_star RIGHT_BRACE')
44
- rule('function_star' => 'function_star function')
45
- rule('function_star' => [])
42
+ rule('classNaming' => 'IDENTIFIER').as 'class_name'
43
+ rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE').as 'class_body'
44
+ rule('methods_opt' => 'method_plus')
45
+ rule('methods_opt' => [])
46
+ rule('method_plus' => 'method_plus function').as 'method_plus_more'
47
+ rule('method_plus' => 'function').as 'method_plus_end'
46
48
 
47
49
  rule('funDecl' => 'FUN function').as 'fun_decl'
48
50
 
@@ -85,7 +87,7 @@ module Loxxy
85
87
  rule('expression' => 'assignment')
86
88
  rule('assignment' => 'owner_opt IDENTIFIER EQUAL assignment').as 'assign_expr'
87
89
  rule('assignment' => 'logic_or')
88
- rule('owner_opt' => 'call DOT')
90
+ rule('owner_opt' => 'call DOT').as 'set_expr'
89
91
  rule('owner_opt' => [])
90
92
  rule('logic_or' => 'logic_and')
91
93
  rule('logic_or' => 'logic_and disjunct_plus').as 'logical_expr'
@@ -130,11 +132,11 @@ module Loxxy
130
132
  rule('refinement_plus' => 'refinement_plus refinement').as 'refinement_plus_more'
131
133
  rule('refinement_plus' => 'refinement').as 'refinement_plus_end'
132
134
  rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN').as 'call_arglist'
133
- rule('refinement' => 'DOT IDENTIFIER')
135
+ rule('refinement' => 'DOT IDENTIFIER').as 'get_expr'
134
136
  rule('primary' => 'TRUE').as 'literal_expr'
135
137
  rule('primary' => 'FALSE').as 'literal_expr'
136
138
  rule('primary' => 'NIL').as 'literal_expr'
137
- rule('primary' => 'THIS')
139
+ rule('primary' => 'THIS').as 'this_expr'
138
140
  rule('primary' => 'NUMBER').as 'literal_expr'
139
141
  rule('primary' => 'STRING').as 'literal_expr'
140
142
  rule('primary' => 'IDENTIFIER').as 'variable_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.09'
4
+ VERSION = '0.1.14'
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
@@ -460,6 +460,71 @@ LOX_END
460
460
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')
461
461
  end
462
462
  end # context
463
+
464
+ context 'Object orientation:' do
465
+ let(:duck_class) do
466
+ snippet = <<-LOX_END
467
+ class Duck {
468
+ noise() {
469
+ this.quack();
470
+ }
471
+
472
+ quack() {
473
+ print "quack";
474
+ }
475
+ }
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
486
+ LOX_END
487
+ expect { subject.evaluate(program) }.not_to raise_error
488
+ expect(sample_cfg[:ostream].string).to eq('Duck')
489
+ end
490
+
491
+ it 'should support default instance creation' do
492
+ program = <<-LOX_END
493
+ #{duck_class}
494
+
495
+ var daffy = Duck(); // Default constructor
496
+ print daffy;
497
+ LOX_END
498
+ expect { subject.evaluate(program) }.not_to raise_error
499
+ expect(sample_cfg[:ostream].string).to eq('Duck instance')
500
+ end
501
+
502
+ it 'should support calls to method' do
503
+ program = <<-LOX_END
504
+ #{duck_class}
505
+
506
+ var daffy = Duck(); // Default constructor
507
+ daffy.quack();
508
+ LOX_END
509
+ expect { subject.evaluate(program) }.not_to raise_error
510
+ expect(sample_cfg[:ostream].string).to eq('quack')
511
+ end
512
+
513
+ it "should support the 'this' keyword" do
514
+ program = <<-LOX_END
515
+ class Egotist {
516
+ speak() {
517
+ print this;
518
+ }
519
+ }
520
+
521
+ var method = Egotist().speak;
522
+ method(); // Output: Egotist instance
523
+ LOX_END
524
+ expect { subject.evaluate(program) }.not_to raise_error
525
+ expect(sample_cfg[:ostream].string).to eq('Egotist instance')
526
+ end
527
+ end # context
463
528
  end # describe
464
529
  # rubocop: enable Metrics/BlockLength
465
530
  end # module
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.09
4
+ version: 0.1.14
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-03-28 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
@@ -93,9 +93,11 @@ files:
93
93
  - lib/loxxy/ast/lox_binary_expr.rb
94
94
  - lib/loxxy/ast/lox_block_stmt.rb
95
95
  - lib/loxxy/ast/lox_call_expr.rb
96
+ - lib/loxxy/ast/lox_class_stmt.rb
96
97
  - lib/loxxy/ast/lox_compound_expr.rb
97
98
  - lib/loxxy/ast/lox_for_stmt.rb
98
99
  - lib/loxxy/ast/lox_fun_stmt.rb
100
+ - lib/loxxy/ast/lox_get_expr.rb
99
101
  - lib/loxxy/ast/lox_grouping_expr.rb
100
102
  - lib/loxxy/ast/lox_if_stmt.rb
101
103
  - lib/loxxy/ast/lox_literal_expr.rb
@@ -105,6 +107,8 @@ files:
105
107
  - lib/loxxy/ast/lox_print_stmt.rb
106
108
  - lib/loxxy/ast/lox_return_stmt.rb
107
109
  - lib/loxxy/ast/lox_seq_decl.rb
110
+ - lib/loxxy/ast/lox_set_expr.rb
111
+ - lib/loxxy/ast/lox_this_expr.rb
108
112
  - lib/loxxy/ast/lox_unary_expr.rb
109
113
  - lib/loxxy/ast/lox_var_stmt.rb
110
114
  - lib/loxxy/ast/lox_variable_expr.rb
@@ -113,7 +117,9 @@ files:
113
117
  - lib/loxxy/back_end/engine.rb
114
118
  - lib/loxxy/back_end/entry.rb
115
119
  - lib/loxxy/back_end/environment.rb
120
+ - lib/loxxy/back_end/lox_class.rb
116
121
  - lib/loxxy/back_end/lox_function.rb
122
+ - lib/loxxy/back_end/lox_instance.rb
117
123
  - lib/loxxy/back_end/resolver.rb
118
124
  - lib/loxxy/back_end/symbol_table.rb
119
125
  - lib/loxxy/back_end/unary_operator.rb