loxxy 0.1.17 → 0.2.00

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: 2c7d720e2638407882bfd3f34384748a872086e1b1f7a3849f0e029e8672fc1c
4
- data.tar.gz: c3aa7ebe4a625ef1b0a257cc750430b9a0aa404bb39dab900b409b2987e09dea
3
+ metadata.gz: 6a071e03ebadb94c255a118d3bebd75227a2d36c535495a79ad2525e4fc2bf7c
4
+ data.tar.gz: 35aa1fc822287f7b86628acac39e5c3ca1c12c478cbb330d302705597ff2f13b
5
5
  SHA512:
6
- metadata.gz: 1eb8bb6575dd39834c55ce3ad359fbdb9a2129f91d25bc39ea093c5a4ca2aa38300ab28e673b39991678c304aea64ffda473707f9d317980b976c8f98fc3a55f
7
- data.tar.gz: d3dd5baae665ab27a60a13b7392e9d57ef32ff72a37892d8f08e314888a1bfcc3db9ceeb071a28c00dda44f4bce7afed6aadf6c33c411a93d561a30fef833edd
6
+ metadata.gz: 51b1a4622ee11c20582fa9ffa72f499a64b280c0092b82a8f07d922309c800f787409e8c273a5316d09de594d82026af0d979e2ad7f52ee6d3eb02233549ebf5
7
+ data.tar.gz: 2649d33c23f73351f9ecb3b9b4b9899541398c94fa4effa8ca4f4c766589df575b1f68685a039f595ca1efb83a515d0a3620afa3721a0604e187b321449b9716
data/CHANGELOG.md CHANGED
@@ -1,8 +1,14 @@
1
1
  ## [0.1.17] - 2021-04-11
2
- - TODO
2
+ - `Loxxy` now support custom initializer.
3
+
4
+ ### Changed
5
+ - Method `BackEnd::Class#call` updated for custom initializer.
6
+ - Class `BackEnd::LoxFunction` added an attribute `is_initializer`
7
+ - Class `BackEnd::Resolver#before_return_stmt` added a check that return in initializer may not return a value
3
8
 
4
9
  ### Fixed
5
- - TODO
10
+ - Method `BackEnd::Engine#after_call_expr` now does arity checking also for initalizer.
11
+ - Method `BackEnd::LoxInstance#set` removed the check of field existence that prevented the creation of ... fields
6
12
 
7
13
  ## [0.1.16] - 2021-04-10
8
14
  - Fixed an issue in name lookup. All the `this` test suite is passing.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'lox_fun_stmt'
4
+ require_relative 'lox_super_expr'
4
5
  require_relative 'lox_this_expr'
5
6
  require_relative 'lox_variable_expr'
6
7
  require_relative 'lox_literal_expr'
@@ -165,7 +165,14 @@ module Loxxy
165
165
 
166
166
  # rule('classDecl' => 'CLASS classNaming class_body')
167
167
  def reduce_class_decl(_production, _range, _tokens, theChildren)
168
- Ast::LoxClassStmt.new(tokens[1].position, theChildren[1], theChildren[2])
168
+ if theChildren[1].kind_of?(Array)
169
+ name = theChildren[1].first
170
+ parent = theChildren[1].last
171
+ else
172
+ name = theChildren[1]
173
+ parent = nil
174
+ end
175
+ Ast::LoxClassStmt.new(tokens[1].position, name, parent, theChildren[2])
169
176
  end
170
177
 
171
178
  # rule('classNaming' => 'IDENTIFIER')
@@ -173,6 +180,13 @@ module Loxxy
173
180
  theChildren[0].token.lexeme
174
181
  end
175
182
 
183
+ # rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
184
+ def reduce_class_subclassing(_production, _range, _tokens, theChildren)
185
+ super_token = theChildren[2].token
186
+ super_var = LoxVariableExpr.new(super_token.position, super_token.lexeme)
187
+ [theChildren[0].token.lexeme, super_var]
188
+ end
189
+
176
190
  # rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE')
177
191
  def reduce_class_body(_production, _range, _tokens, theChildren)
178
192
  theChildren[1].nil? ? [] : theChildren[1]
@@ -368,6 +382,11 @@ module Loxxy
368
382
  LoxThisExpr.new(tokens[0].position)
369
383
  end
370
384
 
385
+ # rule('primary' => 'SUPER DOT IDENTIFIER')
386
+ def reduce_super_expr(_production, _range, _tokens, theChildren)
387
+ LoxSuperExpr.new(theChildren[0].token.position, theChildren[2].token.lexeme)
388
+ end
389
+
371
390
  # rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
372
391
  def reduce_function(_production, _range, _tokens, theChildren)
373
392
  first_child = theChildren.first
@@ -212,6 +212,13 @@ module Loxxy
212
212
  broadcast(:after_this_expr, aThisExpr, self)
213
213
  end
214
214
 
215
+ # Visit event. The visitor is about to visit the super keyword.
216
+ # @param aSuperExpr [Ast::LoxSuperExpr] super expression
217
+ def visit_super_expr(aSuperExpr)
218
+ broadcast(:before_super_expr, aSuperExpr)
219
+ broadcast(:after_super_expr, aSuperExpr, self)
220
+ end
221
+
215
222
  # Visit event. The visitor is about to visit the given terminal datatype value.
216
223
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
217
224
  def visit_builtin(aValue)
@@ -8,15 +8,19 @@ module Loxxy
8
8
  # @return [String] the class name
9
9
  attr_reader :name
10
10
 
11
+ # @return [Ast::LoxVariableExpr] variable referencing the superclass (if any)
12
+ attr_reader :superclass
13
+
11
14
  # @return [Array<Ast::LoxFunStmt>] the methods
12
15
  attr_reader :body
13
16
 
14
17
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
18
  # @param condExpr [Loxxy::Ast::LoxNode] iteration condition
16
19
  # @param theBody [Array<Loxxy::Ast::LoxNode>]
17
- def initialize(aPosition, aName, theMethods)
20
+ def initialize(aPosition, aName, aSuperclassName, theMethods)
18
21
  super(aPosition, [])
19
22
  @name = aName.dup
23
+ @superclass = aSuperclassName
20
24
  @body = theMethods
21
25
  end
22
26
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_node'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxSuperExpr < LoxNode
8
+ # @return [Ast::LoxNode] the object to which the property belongs to
9
+ attr_accessor :object
10
+
11
+ # @return [String] Name of a method name
12
+ attr_reader :property
13
+
14
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
+ # @param aMethodName [String] Name of a method
16
+ def initialize(aPosition, aMethodName)
17
+ super(aPosition)
18
+ @property = aMethodName
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_super_expr(self)
25
+ end
26
+
27
+ # Quack like a LoxVariableExpr
28
+ # @return [String] the `super` keyword
29
+ def name
30
+ 'super'
31
+ end
32
+ alias callee= object=
33
+ end # class
34
+ end # module
35
+ end # module
@@ -70,6 +70,21 @@ module Loxxy
70
70
  end
71
71
 
72
72
  def after_class_stmt(aClassStmt, aVisitor)
73
+ if aClassStmt.superclass
74
+ aClassStmt.superclass.accept(aVisitor)
75
+ parent = stack.pop
76
+ unless parent.kind_of?(LoxClass)
77
+ raise StandardError, 'Superclass must be a class.'
78
+ end
79
+ else
80
+ parent = nil
81
+ end
82
+
83
+ if parent # Create an environment specific for 'super'
84
+ super_env = Environment.new(symbol_table.current_env)
85
+ symbol_table.enter_environment(super_env)
86
+ end
87
+
73
88
  # Convert LoxFunStmt into LoxFunction
74
89
  meths = aClassStmt.body.map do |func_node|
75
90
  func_node.is_method = true
@@ -79,7 +94,12 @@ module Loxxy
79
94
  mth
80
95
  end
81
96
 
82
- klass = LoxClass.new(aClassStmt.name, meths, self)
97
+ klass = LoxClass.new(aClassStmt.name, parent, meths, self)
98
+ if parent
99
+ super_var = Variable.new('super', klass)
100
+ symbol_table.insert(super_var)
101
+ symbol_table.leave_environment
102
+ end
83
103
  new_var = Variable.new(aClassStmt.name, klass)
84
104
  symbol_table.insert(new_var)
85
105
  end
@@ -280,6 +300,20 @@ module Loxxy
280
300
  var.value.accept(aVisitor) # Evaluate this value then push on stack
281
301
  end
282
302
 
303
+ def after_super_expr(aSuperExpr, aVisitor)
304
+ offset = resolver.locals[aSuperExpr]
305
+ env = symbol_table.current_env
306
+ (offset - 1).times { env = env.enclosing }
307
+ instance = env.defns['this'].value.accept(aVisitor)[0]
308
+ superklass = variable_lookup(aSuperExpr).value.superclass
309
+ method = superklass.find_method(aSuperExpr.property)
310
+ unless method
311
+ raise StandardError, "Undefined property '#{aSuperExpr.property}'."
312
+ end
313
+
314
+ stack.push method.bind(instance)
315
+ end
316
+
283
317
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
284
318
  def before_visit_builtin(aValue)
285
319
  stack.push(aValue)
@@ -7,17 +7,23 @@ module Loxxy
7
7
  module BackEnd
8
8
  # Runtime representation of a Lox class.
9
9
  class LoxClass
10
+ # rubocop: disable Style/AccessorGrouping
11
+
10
12
  # @return [String] The name of the class
11
13
  attr_reader :name
14
+ attr_reader :superclass
12
15
 
13
16
  # @return [Hash{String => LoxFunction}] the list of methods
14
17
  attr_reader :meths
15
18
  attr_reader :stack
16
19
 
20
+ # rubocop: enable Style/AccessorGrouping
21
+
17
22
  # Create a class with given name
18
23
  # @param aName [String] The name of the class
19
- def initialize(aName, theMethods, anEngine)
24
+ def initialize(aName, aSuperclass, theMethods, anEngine)
20
25
  @name = aName.dup
26
+ @superclass = aSuperclass
21
27
  @meths = {}
22
28
  theMethods.each do |func|
23
29
  meths[func.name] = func
@@ -47,7 +53,12 @@ module Loxxy
47
53
 
48
54
  # @param aName [String] the method name to search for
49
55
  def find_method(aName)
50
- meths[aName]
56
+ found = meths[aName]
57
+ unless found || superclass.nil?
58
+ found = superclass.find_method(aName)
59
+ end
60
+
61
+ found
51
62
  end
52
63
 
53
64
  # Logical negation.
@@ -67,6 +67,16 @@ module Loxxy
67
67
  previous_class = current_class
68
68
  @current_class = :class
69
69
  define(aClassStmt.name)
70
+ if aClassStmt.superclass
71
+ if aClassStmt.name == aClassStmt.superclass.name
72
+ raise StandardError, "'A class can't inherit from itself."
73
+ end
74
+
75
+ @current_class = :subclass
76
+ aClassStmt.superclass.accept(aVisitor)
77
+ begin_scope
78
+ define('super')
79
+ end
70
80
  begin_scope
71
81
  define('this')
72
82
  aClassStmt.body.each do |fun_stmt|
@@ -74,6 +84,7 @@ module Loxxy
74
84
  resolve_function(fun_stmt, mth_type, aVisitor)
75
85
  end
76
86
  end_scope
87
+ end_scope if aClassStmt.superclass
77
88
  @current_class = previous_class
78
89
  end
79
90
 
@@ -169,6 +180,25 @@ module Loxxy
169
180
  resolve_local(aThisExpr, aVisitor)
170
181
  end
171
182
 
183
+ # rubocop: disable Style/CaseLikeIf
184
+ # rubocop: disable Style/StringConcatenation
185
+ def after_super_expr(aSuperExpr, aVisitor)
186
+ msg_prefix = "Error at 'super': Can't use 'super' "
187
+ if current_class == :none
188
+ err_msg = msg_prefix + 'outside of a class.'
189
+ raise StandardError, err_msg
190
+
191
+ elsif current_class == :class
192
+ err_msg = msg_prefix + 'in a class without superclass.'
193
+ raise StandardError, err_msg
194
+
195
+ end
196
+ # 'super' behaves closely to a local variable
197
+ resolve_local(aSuperExpr, aVisitor)
198
+ end
199
+ # rubocop: enable Style/StringConcatenation
200
+ # rubocop: enable Style/CaseLikeIf
201
+
172
202
  # function declaration creates a new scope for its body & binds its parameters for that scope
173
203
  def before_fun_stmt(aFunStmt, aVisitor)
174
204
  declare(aFunStmt.name)
@@ -38,7 +38,7 @@ module Loxxy
38
38
  rule('declaration' => 'stmt')
39
39
 
40
40
  rule('classDecl' => 'CLASS classNaming class_body').as 'class_decl'
41
- rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER')
41
+ rule('classNaming' => 'IDENTIFIER LESS IDENTIFIER').as 'class_subclassing'
42
42
  rule('classNaming' => 'IDENTIFIER').as 'class_name'
43
43
  rule('class_body' => 'LEFT_BRACE methods_opt RIGHT_BRACE').as 'class_body'
44
44
  rule('methods_opt' => 'method_plus')
@@ -143,7 +143,7 @@ module Loxxy
143
143
  rule('primary' => 'STRING').as 'literal_expr'
144
144
  rule('primary' => 'IDENTIFIER').as 'variable_expr'
145
145
  rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN').as 'grouping_expr'
146
- rule('primary' => 'SUPER DOT IDENTIFIER')
146
+ rule('primary' => 'SUPER DOT IDENTIFIER').as 'super_expr'
147
147
 
148
148
  # Utility rules
149
149
  rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block').as 'function'
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.17'
4
+ VERSION = '0.2.00'
5
5
  end
data/loxxy.gemspec CHANGED
@@ -40,8 +40,8 @@ Gem::Specification.new do |spec|
40
40
  spec.version = Loxxy::VERSION
41
41
  spec.authors = ['Dimitri Geshef']
42
42
  spec.email = ['famished.tiger@yahoo.com']
43
- spec.summary = 'An implementation of the Lox programming language. WIP'
44
- spec.description = 'An implementation of the Lox programming language. WIP'
43
+ spec.summary = 'An implementation of the Lox programming language.'
44
+ spec.description = 'An implementation of the Lox programming language.'
45
45
  spec.homepage = 'https://github.com/famished-tiger/loxxy'
46
46
  spec.license = 'MIT'
47
47
  spec.required_ruby_version = '~> 2.4'
@@ -6,6 +6,7 @@ require 'stringio'
6
6
  # Load the class under test
7
7
  require_relative '../lib/loxxy/interpreter'
8
8
 
9
+ # rubocop: disable Metrics/ModuleLength
9
10
  module Loxxy
10
11
  # This spec contains the bare bones test for the Interpreter class.
11
12
  # The execution of Lox code is tested elsewhere.
@@ -591,7 +592,34 @@ LOX_END
591
592
  predicted = 'Enjoy your bacon and toast, Dear Reader.'
592
593
  expect(sample_cfg[:ostream].string).to eq(predicted)
593
594
  end
595
+
596
+ it 'should support class inheritance and super keyword' do
597
+ lox_snippet = <<-LOX_END
598
+ class A {
599
+ method() {
600
+ print "A method";
601
+ }
602
+ }
603
+
604
+ class B < A {
605
+ method() {
606
+ print "B method";
607
+ }
608
+
609
+ test() {
610
+ super.method();
611
+ }
612
+ }
613
+
614
+ class C < B {}
615
+
616
+ C().test();
617
+ LOX_END
618
+ expect { subject.evaluate(lox_snippet) }.not_to raise_error
619
+ expect(sample_cfg[:ostream].string).to eq('A method')
620
+ end
594
621
  end # context
595
622
  end # describe
596
623
  # rubocop: enable Metrics/BlockLength
597
624
  end # module
625
+ # rubocop: enable Metrics/ModuleLength
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.17
4
+ version: 0.2.00
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 00:00:00.000000000 Z
11
+ date: 2021-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -66,7 +66,7 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
- description: An implementation of the Lox programming language. WIP
69
+ description: An implementation of the Lox programming language.
70
70
  email:
71
71
  - famished.tiger@yahoo.com
72
72
  executables:
@@ -108,6 +108,7 @@ files:
108
108
  - lib/loxxy/ast/lox_return_stmt.rb
109
109
  - lib/loxxy/ast/lox_seq_decl.rb
110
110
  - lib/loxxy/ast/lox_set_expr.rb
111
+ - lib/loxxy/ast/lox_super_expr.rb
111
112
  - lib/loxxy/ast/lox_this_expr.rb
112
113
  - lib/loxxy/ast/lox_unary_expr.rb
113
114
  - lib/loxxy/ast/lox_var_stmt.rb
@@ -178,7 +179,7 @@ requirements: []
178
179
  rubygems_version: 3.1.4
179
180
  signing_key:
180
181
  specification_version: 4
181
- summary: An implementation of the Lox programming language. WIP
182
+ summary: An implementation of the Lox programming language.
182
183
  test_files:
183
184
  - spec/back_end/engine_spec.rb
184
185
  - spec/back_end/environment_spec.rb