loxxy 0.0.16 → 0.0.17

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: 7d7d30d3246aa8a8f06f3456d3996c680e9ddb4ed31bcf9fff99685702e50dd1
4
- data.tar.gz: dc428ddd0fc03e153e70873bc2827b6e4aac813e5fdfba40ef2f28bfa7283fc2
3
+ metadata.gz: 6eed6a7de54b89c276b0f098cd7d824f26c0ce9ba957d4fb9e3a4d12e6dfbc4f
4
+ data.tar.gz: ba7c00f11c5b69322aec0d447c2fef9cf6d6d74b2ea7bb16096581508a786427
5
5
  SHA512:
6
- metadata.gz: d90f2494a324be036dae2f3d38636c571009b60f19d2b3d917b242ab98d231e03bb121f577e5598ac63514cc5817814972cabe927f05af2842a5a97c89eb69d9
7
- data.tar.gz: e09a70a27baa1408904e1bfe512f4f523b8e4efd1aab00412b993c8f4a35b5498f1f6ddb5e67697108c80a91778301e41c15741451e73e1d869496d96441757a
6
+ metadata.gz: 1b2c3826f085ce3f723cdbc70c3bdc3563817270985a321db05f97ca1ff81971bf872db49631d3fa20580932f7a0e2910bf3c6c84daf7873cdcd1f04c3392a99
7
+ data.tar.gz: 4e3d075facaf9c370d78f3f3ac89b7226e375eb32064535e3401c5ef52b8ca43c9d4c6cfb8f027b4037c7baa7c5945b0fd0dbe1e8b1e580fc732c4a38f7c57c6
@@ -250,7 +250,7 @@ Style/IfUnlessModifier:
250
250
  Enabled: false
251
251
 
252
252
  Style/InverseMethods:
253
- Enabled: true
253
+ Enabled: false
254
254
 
255
255
  Style/MissingRespondToMissing:
256
256
  Enabled: false
@@ -1,3 +1,18 @@
1
+ ## [0.0.17] - 2021-01-12
2
+ - The interpreter can evaluate all arithmetic and comparison operations.
3
+ - It implements `==`, `!=` and the unary operations `!`, `-`
4
+
5
+ ## Added
6
+ - Class `AST::LoxUnaryExpr`
7
+ - Method `AST::ASTBuilder#reduce_unary_expr` to support the evaluation of `!` and ``-@`
8
+ - Method `Ast::ASTVisitor#visit_unnary_expr` for visiting unary expressions
9
+ - Method `Backend::Engine#after_unary_expr` evaluating an unary expression
10
+ - In class `Datatype::BuiltinDatatype` the methods `falsey?`, `truthy?`, `!`, `!=`
11
+ - In class `Datatype::Number`the methods `<`, `<=`, ´>´, `>=` and `-@`
12
+
13
+ ## Changed
14
+ - File `README.md` updated.
15
+
1
16
  ## [0.0.16] - 2021-01-11
2
17
  - The interpreter can evaluate product and division of two numbers.
3
18
  - It also implements equality `==` and inequality `!=` operators
@@ -36,7 +51,7 @@
36
51
  - The interpreter can evaluate addition of numbers and string concatenation
37
52
 
38
53
  ## Added
39
- - Method `Ast::ASTVisitor::visit_binary_expr` for visiting binary expressions
54
+ - Method `Ast::ASTVisitor#visit_binary_expr` for visiting binary expressions
40
55
  - Method `Ast::LoxBinaryExpr#accept` for visitor pattern
41
56
  - Method `BackEnd::Engine#after_binary_expr` to trigger execution of binary operator
42
57
  - `Boolean` class hierarchy: added methos `true?` and `false?` to ease spec test writing
data/README.md CHANGED
@@ -154,21 +154,16 @@ The parser knows all the __Lox__ reserved keywords:
154
154
  and, class, else, false, fun, for, if, nil, or,
155
155
  print, return, super, this, true, var, while
156
156
  ```
157
- Of these, the interpreter implements: `false`, `nil`, `true`
157
+ Of these, the interpreter implements: `false`, `nil`, `print`, `true`
158
158
 
159
159
  ### Operators and Special Chars
160
160
  #### Operators
161
- The parser recognizes all the __Lox__ operators, delimiters and separators:
161
+ The __loxxy__ interpreter supports all the __Lox__ unary and binary operators:
162
162
  - Arithmetic operators: `+`, `-`, `*`, `/`
163
163
  - Comparison operators: `>`, `>=`, `<`, `<=`
164
164
  - Equality operators: `==`, `!=`
165
-
166
- Of these, the interpreter implements:
167
- `+` (addition of two numbers or string concatenation)
168
- `-` (difference between two numbers)
169
- `*` (product of two numbers)
170
- `/` (division of two number)
171
- `==` and `!=` (in)equality testing of two Lox values
165
+ - Unary negate (change sign): `-`
166
+ - Unary not: `!`
172
167
 
173
168
  #### Delimiters
174
169
  The parser knows all the __Lox__ grouping delimiters:
@@ -196,9 +191,11 @@ loxxy supports all the standard __Lox__ datatypes:
196
191
  ### Implemented expressions
197
192
  Loxxy implements expressions:
198
193
  - Plain literals only; or,
199
- - Addition, subtraction, product and division of two numbers; or,
194
+ - (In)equality testing between two values; or,
195
+ - Basic arithmetic operations (`+`, `-`, `*`, `/`, `unary -`); or,
196
+ - Comparison between two numbers; or,
200
197
  - Concatenation of two strings,
201
- - (In)equality test of two Lox values
198
+ - Negation `!`
202
199
 
203
200
  ### Implemented statements
204
201
  Loxxy implements the following statements:
@@ -206,7 +203,7 @@ Loxxy implements the following statements:
206
203
  - Print statement
207
204
 
208
205
  ```javascript
209
- // Print statement with nested concatenation
206
+ // Print statement with nested string concatenation
210
207
  print "Hello" + ", " + "world!";
211
208
  ```
212
209
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lox_print_stmt'
4
3
  require_relative 'lox_literal_expr'
5
4
  require_relative 'lox_noop_expr'
5
+ require_relative 'lox_binary_expr'
6
+ require_relative 'lox_unary_expr'
7
+ require_relative 'lox_print_stmt'
@@ -15,7 +15,6 @@ module Loxxy
15
15
  # @return [Hash{String => String}]
16
16
  Name2special = {
17
17
  'AND' => 'and',
18
- 'BANG' => '!',
19
18
  'BANG_EQUAL' => '!=',
20
19
  'COMMA' => ',',
21
20
  'DOT' => '.',
@@ -36,6 +35,11 @@ module Loxxy
36
35
  'SLASH' => '/',
37
36
  'STAR' => '*'
38
37
  }.freeze
38
+
39
+ Name2unary = {
40
+ 'BANG' => '!',
41
+ 'MINUS' => '-@'
42
+ }.freeze
39
43
  end # defined
40
44
 
41
45
  attr_reader :strict
@@ -231,6 +235,13 @@ module Loxxy
231
235
  reduce_binary_plus_end(production, range, tokens, theChildren)
232
236
  end
233
237
 
238
+ # rule('unary' => 'unaryOp unary')
239
+ def reduce_unary_expr(_production, _range, tokens, theChildren)
240
+ operator = Name2unary[theChildren[0].symbol.name].to_sym
241
+ operand = theChildren[1]
242
+ LoxUnaryExpr.new(tokens[0].position, operator, operand)
243
+ end
244
+
234
245
  # rule('primary' => 'FALSE' | TRUE').as 'literal_expr'
235
246
  def reduce_literal_expr(_production, _range, _tokens, theChildren)
236
247
  first_child = theChildren.first
@@ -51,7 +51,7 @@ module Loxxy
51
51
  broadcast(:after_ptree, aParseTree)
52
52
  end
53
53
 
54
- # Visit event. The visitor is about to visit a print statement expression.
54
+ # Visit event. The visitor is about to visit a print statement.
55
55
  # @param aPrintStmt [AST::LOXPrintStmt] the print statement node to visit
56
56
  def visit_print_stmt(aPrintStmt)
57
57
  broadcast(:before_print_stmt, aPrintStmt)
@@ -59,7 +59,7 @@ module Loxxy
59
59
  broadcast(:after_print_stmt, aPrintStmt)
60
60
  end
61
61
 
62
- # Visit event. The visitor is about to visit a print statement expression.
62
+ # Visit event. The visitor is about to visit a binary expression.
63
63
  # @param aBinaryExpr [AST::LOXBinaryExpr] the binary expression node to visit
64
64
  def visit_binary_expr(aBinaryExpr)
65
65
  broadcast(:before_binary_expr, aBinaryExpr)
@@ -67,6 +67,14 @@ module Loxxy
67
67
  broadcast(:after_binary_expr, aBinaryExpr)
68
68
  end
69
69
 
70
+ # Visit event. The visitor is about to visit an unary expression.
71
+ # @param anUnaryExpr [AST::anUnaryExpr] unary expression node to visit
72
+ def visit_unary_expr(anUnaryExpr)
73
+ broadcast(:before_unary_expr, anUnaryExpr)
74
+ traverse_subnodes(anUnaryExpr)
75
+ broadcast(:after_unary_expr, anUnaryExpr)
76
+ end
77
+
70
78
  # Visit event. The visitor is visiting the
71
79
  # given terminal node containing a datatype object.
72
80
  # @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
@@ -5,7 +5,7 @@ require_relative 'lox_compound_expr'
5
5
  module Loxxy
6
6
  module Ast
7
7
  class LoxBinaryExpr < LoxCompoundExpr
8
- # @return [Symbol]
8
+ # @return [Symbol] message name to be sent to receiver
9
9
  attr_reader :operator
10
10
 
11
11
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxUnaryExpr < LoxCompoundExpr
8
+ # @return [Symbol] message name to be sent to receiver
9
+ attr_reader :operator
10
+
11
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
12
+ # @param operand [Loxxy::Ast::LoxNode]
13
+ def initialize(aPosition, anOperator, operand)
14
+ super(aPosition, [operand])
15
+ @operator = anOperator
16
+ end
17
+
18
+ # Part of the 'visitee' role in Visitor design pattern.
19
+ # @param visitor [Ast::ASTVisitor] the visitor
20
+ def accept(visitor)
21
+ visitor.visit_unary_expr(self)
22
+ end
23
+
24
+ alias operands subnodes
25
+ end # class
26
+ end # module
27
+ end # module
@@ -22,7 +22,7 @@ module Loxxy
22
22
  @stack = []
23
23
  end
24
24
 
25
- # Given an abstract syntax parse tree visitor, luanch the visit
25
+ # Given an abstract syntax parse tree visitor, launch the visit
26
26
  # and execute the visit events in the output stream.
27
27
  # @param aVisitor [AST::ASTVisitor]
28
28
  # @return [Loxxy::Datatype::BuiltinDatatype]
@@ -44,8 +44,7 @@ module Loxxy
44
44
  op = aBinaryExpr.operator
45
45
  operand2 = stack.pop
46
46
  operand1 = stack.pop
47
- implemented = %i[+ - * / == !=].include?(op)
48
- if implemented && operand1.respond_to?(op)
47
+ if operand1.respond_to?(op)
49
48
  stack.push operand1.send(op, operand2)
50
49
  else
51
50
  msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
@@ -53,6 +52,17 @@ module Loxxy
53
52
  end
54
53
  end
55
54
 
55
+ def after_unary_expr(anUnaryExpr)
56
+ op = anUnaryExpr.operator
57
+ operand = stack.pop
58
+ if operand.respond_to?(op)
59
+ stack.push operand.send(op)
60
+ else
61
+ msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
62
+ raise StandardError, msg1
63
+ end
64
+ end
65
+
56
66
  # @param literalExpr [Ast::LoxLiteralExpr]
57
67
  def before_literal_expr(literalExpr)
58
68
  stack.push(literalExpr.literal)
@@ -14,6 +14,34 @@ module Loxxy
14
14
  @value = validated_value(aValue)
15
15
  end
16
16
 
17
+ # Is the value considered falsey in Lox?
18
+ # Rule: false and nil are falsey and everything else is truthy.
19
+ # This test used in conditional statements (i.e. if, while)
20
+ def falsey?
21
+ false # Default implementation
22
+ end
23
+
24
+ # Is the value considered truthy in Lox?
25
+ # Rule: false and nil are falsey and everything else is truthy.
26
+ # This test used in conditional statements (i.e. if, while)
27
+ def truthy?
28
+ true # Default implementation
29
+ end
30
+
31
+ # Negation ('not')
32
+ # Returns a boolean with opposite truthiness value.
33
+ # @return [Datatype::Boolean]
34
+ def !
35
+ falsey? ? True.instance : False.instance
36
+ end
37
+
38
+ # Check for inequality of this object with another Lox object
39
+ # @param other [Datatype::BuiltinDatatype, Object]
40
+ # @return [Datatype::Boolean]
41
+ def !=(other)
42
+ !(self == other)
43
+ end
44
+
17
45
  # Method called from Lox to obtain the text representation of the boolean.
18
46
  # @return [String]
19
47
  def to_str
@@ -20,6 +20,20 @@ module Loxxy
20
20
  true
21
21
  end
22
22
 
23
+ # Is the value considered falsey in Lox?
24
+ # Rule: false and nil are falsey and everything else is truthy.
25
+ # This test used in conditional statements (i.e. if, while)
26
+ def falsey?
27
+ true
28
+ end
29
+
30
+ # Is the value considered truthy in Lox?
31
+ # Rule: false and nil are falsey and everything else is truthy.
32
+ # This test used in conditional statements (i.e. if, while)
33
+ def truthy?
34
+ false
35
+ end
36
+
23
37
  # Check for equality of a Lox False with another Lox object
24
38
  # @param other [Datatype::BuiltinDatatype, FalseClass, Object]
25
39
  # @return [Datatype::Boolean]
@@ -27,14 +41,6 @@ module Loxxy
27
41
  falsey = other.kind_of?(False) || other.kind_of?(FalseClass)
28
42
  falsey ? True.instance : False.instance
29
43
  end
30
-
31
- # Check for inequality of a Lox False with another Lox object
32
- # @param other [Datatype::BuiltinDatatype, FalseClass, Object]
33
- # @return [Datatype::Boolean]
34
- def !=(other)
35
- falsey = other.kind_of?(False) || other.kind_of?(FalseClass)
36
- falsey ? False.instance : True.instance
37
- end
38
44
  end # class
39
45
 
40
46
  False.instance.freeze # Make the sole instance immutable
@@ -21,20 +21,6 @@ module Loxxy
21
21
  end
22
22
  end
23
23
 
24
- # Check the inequality of a Lox String with another Lox object.
25
- # @param other [Datatype::LxString, String, Object]
26
- # @return [Datatype::Boolean]
27
- def !=(other)
28
- case other
29
- when LXString
30
- (value != other.value) ? True.instance : False.instance
31
- when String
32
- (value != other) ? True.instance : False.instance
33
- else
34
- True.instance
35
- end
36
- end
37
-
38
24
  # Perform the concatenation of two Lox stings or
39
25
  # one Lox string and a Ruby String
40
26
  # @param other [Loxxy::Datatype::LXString, String]
@@ -22,12 +22,18 @@ module Loxxy
22
22
  is_nil ? True.instance : False.instance
23
23
  end
24
24
 
25
- # Check the inequality with another object.
26
- # @param other [Datatype::BuiltinDatatype, NilClass, Object]
27
- # @return [Datatype::Boolean]
28
- def !=(other)
29
- is_nil = other.kind_of?(Nil) || other.kind_of?(NilClass)
30
- is_nil ? False.instance : True.instance
25
+ # Is the value considered falsey in Lox?
26
+ # Rule: false and nil are falsey and everything else is truthy.
27
+ # This test used in conditional statements (i.e. if, while)
28
+ def falsey?
29
+ true
30
+ end
31
+
32
+ # Is the value considered truthy in Lox?
33
+ # Rule: false and nil are falsey and everything else is truthy.
34
+ # This test used in conditional statements (i.e. if, while)
35
+ def truthy?
36
+ false
31
37
  end
32
38
 
33
39
  # Method called from Lox to obtain the text representation of nil.
@@ -71,6 +71,12 @@ module Loxxy
71
71
  end
72
72
  end
73
73
 
74
+ # Unary minus (return value with changed sign)
75
+ # @return [Loxxy::Datatype::Number]
76
+ def -@
77
+ self.class.new(-value)
78
+ end
79
+
74
80
  # Check the equality of a Lox number object with another object
75
81
  # @param other [Datatype::BuiltinDatatype, Numeric, Object]
76
82
  # @return [Datatype::Boolean]
@@ -85,20 +91,52 @@ module Loxxy
85
91
  end
86
92
  end
87
93
 
88
- # Check the inequality of a Lox number object with another object
89
- # @param other [Datatype::BuiltinDatatype, Numeric, Object]
94
+ # Check whether this Lox number has a greater value than given argument.
95
+ # @param other [Datatype::Number, Numeric]
96
+ # @return [Datatype::Boolean]
97
+ def >(other)
98
+ case other
99
+ when Number
100
+ (value > other.value) ? True.instance : False.instance
101
+ when Numeric
102
+ (value > other) ? True.instance : False.instance
103
+ else
104
+ msg = "'>': Operands must be numbers."
105
+ raise StandardError, msg
106
+ end
107
+ end
108
+
109
+ # Check whether this Lox number has a greater or equal value
110
+ # than given argument.
111
+ # @param other [Datatype::Number, Numeric]
90
112
  # @return [Datatype::Boolean]
91
- def !=(other)
113
+ def >=(other)
92
114
  case other
93
115
  when Number
94
- (value != other.value) ? True.instance : False.instance
116
+ (value >= other.value) ? True.instance : False.instance
95
117
  when Numeric
96
- (value != other) ? True.instance : False.instance
118
+ (value >= other) ? True.instance : False.instance
97
119
  else
98
- True.instance
120
+ msg = "'>': Operands must be numbers."
121
+ raise StandardError, msg
99
122
  end
100
123
  end
101
124
 
125
+ # Check whether this Lox number has a lesser value than given argument.
126
+ # @param other [Datatype::Number, Numeric]
127
+ # @return [Datatype::Boolean]
128
+ def <(other)
129
+ !(self >= other)
130
+ end
131
+
132
+ # Check whether this Lox number has a lesser or equal value
133
+ # than given argument.
134
+ # @param other [Datatype::Number, Numeric]
135
+ # @return [Datatype::Boolean]
136
+ def <=(other)
137
+ !(self > other)
138
+ end
139
+
102
140
  protected
103
141
 
104
142
  def validated_value(aValue)
@@ -20,21 +20,13 @@ module Loxxy
20
20
  true
21
21
  end
22
22
 
23
- # Check for equality of a Lox True with another Lox object
23
+ # Check for equality of a Lox True with another Lox object / Ruby true
24
24
  # @param other [Datatype::True, TrueClass, Object]
25
25
  # @return [Datatype::Boolean]
26
26
  def ==(other)
27
27
  thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
28
28
  thruthy ? True.instance : False.instance
29
29
  end
30
-
31
- # Check for inequality of a Lox True with another Lox object
32
- # @param other [Datatype::BuiltinDatatype, TrueClass, Object]
33
- # @return [Datatype::Boolean]
34
- def !=(other)
35
- thruthy = other.kind_of?(True) || other.kind_of?(TrueClass)
36
- thruthy ? False.instance : True.instance
37
- end
38
30
  end # class
39
31
 
40
32
  True.instance.freeze # Make the sole instance immutable
@@ -121,7 +121,7 @@ module Loxxy
121
121
  rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
122
122
  rule('multOp' => 'SLASH')
123
123
  rule('multOp' => 'STAR')
124
- rule('unary' => 'unaryOp unary')
124
+ rule('unary' => 'unaryOp unary').as 'unary_expr'
125
125
  rule('unary' => 'call')
126
126
  rule('unaryOp' => 'BANG')
127
127
  rule('unaryOp' => 'MINUS')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.16'
4
+ VERSION = '0.0.17'
5
5
  end
@@ -107,6 +107,63 @@ module Loxxy
107
107
  end
108
108
  end
109
109
 
110
+ it 'should evaluate a comparison of two numbers' do
111
+ [
112
+ ['1 < 2;', true],
113
+ ['2 < 2;', false],
114
+ ['2 < 1;', false],
115
+ ['1 <= 2;', true],
116
+ ['2 <= 2;', true],
117
+ ['2 <= 1;', false],
118
+ ['1 > 2;', false],
119
+ ['2 > 2;', false],
120
+ ['2 > 1;', true],
121
+ ['1 >= 2;', false],
122
+ ['2 >= 2;', true],
123
+ ['2 >= 1;', true],
124
+ ['0 < -0;', false],
125
+ ['-0 < 0;', false],
126
+ ['0 > -0;', false],
127
+ ['-0 > 0;', false],
128
+ ['0 <= -0;', true],
129
+ ['-0 <= 0;', true],
130
+ ['0 >= -0;', true],
131
+ ['-0 >= 0;', true]
132
+ ].each do |(source, predicted)|
133
+ lox = Loxxy::Interpreter.new
134
+ result = lox.evaluate(source)
135
+ expect(result.value == predicted).to be_truthy
136
+ end
137
+ end
138
+
139
+ it 'should evaluate the change sign of a number' do
140
+ [
141
+ ['- 3;', -3],
142
+ ['- - 3;', 3],
143
+ ['- - - 3;', -3]
144
+ ].each do |(source, predicted)|
145
+ lox = Loxxy::Interpreter.new
146
+ result = lox.evaluate(source)
147
+ expect(result.value == predicted).to be_truthy
148
+ end
149
+ end
150
+
151
+ it 'should evaluate the negation of an object' do
152
+ [
153
+ ['!true;', false],
154
+ ['!false;', true],
155
+ ['!!true;', true],
156
+ ['!123;', false],
157
+ ['!0;', false],
158
+ ['!nil;', true],
159
+ ['!"";', false]
160
+ ].each do |(source, predicted)|
161
+ lox = Loxxy::Interpreter.new
162
+ result = lox.evaluate(source)
163
+ expect(result.value == predicted).to be_truthy
164
+ end
165
+ end
166
+
110
167
  it 'should print the hello world message' do
111
168
  expect { subject.evaluate(hello_world) }.not_to raise_error
112
169
  expect(sample_cfg[:ostream].string).to eq('Hello, world!')
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.0.16
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-11 00:00:00.000000000 Z
11
+ date: 2021-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -93,6 +93,7 @@ files:
93
93
  - lib/loxxy/ast/lox_node.rb
94
94
  - lib/loxxy/ast/lox_noop_expr.rb
95
95
  - lib/loxxy/ast/lox_print_stmt.rb
96
+ - lib/loxxy/ast/lox_unary_expr.rb
96
97
  - lib/loxxy/back_end/engine.rb
97
98
  - lib/loxxy/datatype/all_datatypes.rb
98
99
  - lib/loxxy/datatype/boolean.rb