loxxy 0.1.17 → 0.2.00

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