loxxy 0.0.5 → 0.0.10

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: 5db00fee0e8e7ef89a48cacc4e9ad0baf7ae5ee2cb419ba76964199bd7552199
4
- data.tar.gz: ddb8e9c7ac267f32bed3f77ada05a6af38a872b5f892452a496b17522b608977
3
+ metadata.gz: e4f33203580d2b83ee7139e449787b439c0fb56c5a8c3dcc8035d114ffd957d3
4
+ data.tar.gz: 46c9fb2f55849c6aa9b9e4d58588746ddb3ac3353f7ed09922d9394a9fc512c6
5
5
  SHA512:
6
- metadata.gz: 44e0049b0bc01e5d35bed7b91c32a7a3b977e6ba4e806439958bad2dc8aae7ead017a310c8523bd4cb628c14951946981bee42f4001d68b701159fcdaff53419
7
- data.tar.gz: c90236a82f0a4872b209b8462d5f13377815fd4fc41babf256ff4894ea6900e343c6f192194e1b713cb9f8a8d2bffc326e7ea4f00726193e0a792dc9e1457e41
6
+ metadata.gz: 30f554af6338b50297ce6befaa62c86c8406df681e25098cc95dc28da64bb6feabbb9e085846eb8032f51d8d3655332105eb1d7165903bbd0d6be24cb13c4d0b
7
+ data.tar.gz: d9d9a69a010d16807f6c4bf24b15e8135367bb62b6d2e052943e0d6030751f282c777f652dd3fced08093aee8abae228e63d365cae156fa6e87ac7f52fc7e2e6
@@ -170,6 +170,9 @@ Naming/BlockParameterName:
170
170
 
171
171
  Naming/MethodParameterName:
172
172
  Enabled: false
173
+
174
+ Naming/MethodName:
175
+ Enabled: false
173
176
 
174
177
  Naming/VariableName:
175
178
  Enabled: false
@@ -1,3 +1,61 @@
1
+ ## [0.0.10] - 2021-01-08
2
+ - AST node generation for equality expression.
3
+
4
+ ## Changed
5
+ - Class `AST::ASTBuilder` refactoring and added `reduce_` methods for equality operations.
6
+ - File `grammar.rb`added name to equality rules
7
+ - File `README.md` added gem version and license badges, expanded roadmap section.
8
+
9
+ ## Fixed
10
+ - File `grammar.rb`: a rule had still the discarded non-terminal `equalityTest_star` in its lhs.
11
+
12
+ ## [0.0.9] - 2021-01-07
13
+ - AST node generation for comparison expression.
14
+
15
+ ## Changed
16
+ - Class `AST::ASTBuilder` added `reduce_` methods for comparison operations.
17
+ - File `grammar.rb`added name to comparison rules
18
+
19
+ ## [0.0.8] - 2021-01-07
20
+ - AST node generation for arithmetic operations of number literals.
21
+
22
+ ## Changed
23
+ - Class `AST::ASTBuilder` added `reduce_` methods for arithmetic operations.
24
+ - File `grammar.rb`added name to arithmetic rules
25
+
26
+ ## Fixed
27
+ - File `grammar.rb`: second rule for `factor` had a missing member in rhs.
28
+
29
+ ## [0.0.7] - 2021-01-06
30
+ - Lox grammar reworked, initial AST classes created.
31
+
32
+ ## Added
33
+ - Class `Parser` this one generates AST's (Abstract Syntax Tree)
34
+ - Class `AST::ASTVisitor` draft initial implementation.
35
+ - Class `AST::BinaryExpr` draft initial implementation.
36
+ - Class `AST::LoxCompoundExpr` draft initial implementation.
37
+ - Class `AST::LiteralExpr` draft initial implementation.
38
+ - Class `AST::LoxNode` draft initial implementation.
39
+
40
+ ## Changed
41
+ - File `spec_helper.rb`: removed Bundler dependency
42
+ - Class `AST::ASTBuilder` added initial `reduce_` methods.
43
+ - File `README.md` Removed example with AST generation since this is in flux.
44
+
45
+ ## [0.0.6] - 2021-01-03
46
+ - First iteration of a parser with AST generation.
47
+
48
+ ## Added
49
+ - Class `Parser` this one generates AST's (Abstract Syntax Tree)
50
+ - Class `AST::ASTBuilder` default code to generate an AST.
51
+
52
+ ## Changed
53
+ - File `spec_helper.rb`: removed Bundler dependency
54
+ - File `README.md` Added example with AST visualization.
55
+
56
+ ## Fixed
57
+ - File `grammar.rb` ensure that the constant `Grammar` is created once only.
58
+
1
59
  ## [0.0.5] - 2021-01-02
2
60
  - Improved example in `README.md`, code re-styling to please `Rubocop` 1.7
3
61
 
data/README.md CHANGED
@@ -1,27 +1,70 @@
1
- # Loxxy
1
+ # loxxy
2
+ [![Gem Version](https://badge.fury.io/rb/loxxy.svg)](https://badge.fury.io/rb/loxxy)
3
+ [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://github.com/famished-tiger/loxxy/blob/main/LICENSE.txt)
2
4
 
3
- A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html),
4
- a simple language used in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/).
5
+
6
+ A Ruby implementation of the [Lox programming language](https://craftinginterpreters.com/the-lox-language.html ),
7
+ a simple language used in Bob Nystrom's online book [Crafting Interpreters](https://craftinginterpreters.com/ ).
5
8
 
6
9
  ## Purpose of this project:
7
10
  - To deliver an open source example of a programming language fully implemented in Ruby
8
11
  (from the scanner, parser, code generation).
9
- - The implementation should be mature enough to run (LoxLox)[https://github.com/benhoyt/loxlox],
12
+ - The implementation should be mature enough to run [LoxLox](https://github.com/benhoyt/loxlox),
10
13
  a Lox interpreter written in Lox.
11
14
 
12
- ## Roadmap
13
- - [DONE] Scanner (tokenizer)
14
- - [DONE] Raw parser. It parses Lox programs and generates a parse tree.
15
- - [TODO] Tailored parser for generating AST (Abstract Syntax Tree)
16
- - [TODO] Custom AST builder class
17
- - [TODO] Hierarchy classes for representing Lox expressions in AST
18
- - [TODO] Interpreter or transpiler
19
-
20
-
21
- ## Example
22
- At this, stage, the raw parser is able to recognize Lox input and to generate
23
- a concrete parse tree from it.
15
+ ## Current status
16
+ The __loxxy__ gem hosts two distinct parsers classes (`RawParser` and `Parser`).
17
+ - A `RawParser` instance is able to recognize valid Lox input and to generate
18
+ a concrete parse tree from it.
19
+ - A `Parser` instance can also parse Lox source code but will generate an AST
20
+ (Abstract Syntax Tree) that will be used by the future tree-walking interpreter.
21
+ Currently it generates AST for arithmetic expressions with literal numbers only.
24
22
 
23
+ ## Roadmap
24
+ ### Done
25
+ - Scanner (tokenizer)
26
+ - Lox grammar (in format required by Rley gem)
27
+ - Raw parser. It parses Lox programs and generates a raw parse tree.
28
+ - Tailored parser for generating AST (Abstract Syntax Tree)
29
+
30
+ ### Started
31
+ - Custom AST builder class
32
+ - Classes for representing Lox expressions in AST
33
+
34
+ ### TODO
35
+ AST Node generation
36
+ Goal: parser should generate AST for any input Lox program
37
+ - [X] Equality operator
38
+ - [] Logical operator (and, or)
39
+ - [] Unary expressions (negate, not)
40
+ - [] Grouping expressions
41
+ - [] Print statement
42
+ - [] Simple assignment expressions
43
+ - [] Variable declaration
44
+ - [] Dot notation
45
+ - [] Block statement
46
+ - [] If statement
47
+ - [] For statement
48
+ - [] While statement
49
+ - [] Function declaration
50
+ - [] Call expression
51
+ - [] Return statement
52
+ - [] Class declaration
53
+ - [] AST generation covers complete grammar
54
+
55
+ Tree-walking:
56
+ - [] Tree visitor recognizes all AST node types
57
+
58
+ Interpreter:
59
+ - Keywords: symbol table, scope, activation record, class & object model, Lox test suite
60
+ - [] Milestone: Interpreter handles expressions but function calls
61
+ - [] Milestone: Interpreter handles `for`, `if`, `print`, `while` and block statements
62
+ - [] Milestone: Interpreter handles variable declarations (global, block)
63
+ - [] Milestone: Interpreter handles function declarations and calls
64
+ - [] Milestone: Interpreter supports class and object definition
65
+ - [] Milestone: Lox interpreter complete
66
+
67
+ ## Example using RawParser class
25
68
  ```ruby
26
69
  require 'loxxy'
27
70
 
@@ -45,8 +88,7 @@ tree_formatter.render(visitor)
45
88
  This is the output produced by the above example:
46
89
  ```
47
90
  program
48
- +-- declaration_star
49
- | +-- declaration_star
91
+ +-- declaration_plus
50
92
  | +-- declaration
51
93
  | +-- statement
52
94
  | +-- printStmt
@@ -55,29 +97,18 @@ program
55
97
  | | +-- assignment
56
98
  | | +-- logic_or
57
99
  | | +-- logic_and
58
- | | | +-- equality
59
- | | | | +-- comparison
60
- | | | | | +-- term
61
- | | | | | | +-- factor
62
- | | | | | | | +-- unary
63
- | | | | | | | | +-- call
64
- | | | | | | | | +-- primary
65
- | | | | | | | | | +-- STRING: '"Hello, world!"'
66
- | | | | | | | | +-- refinement_star
67
- | | | | | | | +-- multiplicative_star
68
- | | | | | | +-- additive_star
69
- | | | | | +-- comparisonTest_star
70
- | | | | +-- equalityTest_star
71
- | | | +-- conjunct_star
72
- | | +-- disjunct_star
100
+ | | +-- equality
101
+ | | +-- comparison
102
+ | | +-- term
103
+ | | +-- factor
104
+ | | +-- unary
105
+ | | +-- call
106
+ | | +-- primary
107
+ | | +-- STRING: '"Hello, world!"'
73
108
  | +-- SEMICOLON: ';'
74
109
  +-- EOF: ''
75
110
  ```
76
111
 
77
- Soon, the `loxxy` will host another, tailored parser, that will generate
78
- abstract syntax tree which much more convenient for further processing
79
- (like implementing an interpreter).
80
-
81
112
  ## Installation
82
113
 
83
114
  Add this line to your application's Gemfile:
@@ -99,9 +130,9 @@ Or install it yourself as:
99
130
  TODO: Write usage instructions here
100
131
 
101
132
  ## Other Lox implementations in Ruby
102
- An impressive list of Lox implementations can be found [here](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations
133
+ An impressive list of Lox implementations can be found [here](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations)
103
134
 
104
- For Ruby, ther is the [lox](https://github.com/rdodson41/ruby-lox) gem.
135
+ For Ruby, there is the [lox](https://github.com/rdodson41/ruby-lox) gem.
105
136
  There are other Ruby-based projects as well:
106
137
  - [SlowLox](https://github.com/ArminKleinert/SlowLox), described as a "1-to-1 conversion of JLox to Ruby"
107
138
  - [rulox](https://github.com/LevitatingBusinessMan/rulox)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'loxxy/version'
4
4
  require_relative 'loxxy/front_end/raw_parser'
5
+ require_relative 'loxxy/front_end/parser'
5
6
 
6
7
  module Loxxy
7
8
  class Error < StandardError; end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../datatype/all_datatypes'
4
+ require_relative 'lox_literal_expr'
5
+ require_relative 'lox_binary_expr'
6
+
7
+ module Loxxy
8
+ module Ast
9
+ # The purpose of ASTBuilder is to build piece by piece an AST
10
+ # (Abstract Syntax Tree) from a sequence of input tokens and
11
+ # visit events produced by walking over a GFGParsing object.
12
+ class ASTBuilder < Rley::ParseRep::ASTBaseBuilder
13
+ # Mapping Token name => operator | separator | delimiter characters
14
+ # @return [Hash{String => String}]
15
+ Name2special = {
16
+ 'AND' => 'and',
17
+ 'BANG' => '!',
18
+ 'BANG_EQUAL' => '!=',
19
+ 'COMMA' => ',',
20
+ 'DOT' => '.',
21
+ 'EQUAL' => '=',
22
+ 'EQUAL_EQUAL' => '==',
23
+ 'GREATER' => '>',
24
+ 'GREATER_EQUAL' => '>=',
25
+ 'LEFT_BRACE' => '{',
26
+ 'LEFT_PAREN' => '(',
27
+ 'LESS' => '<',
28
+ 'LESS_EQUAL' => '<=',
29
+ 'MINUS' => '-',
30
+ 'OR' => 'or',
31
+ 'PLUS' => '+',
32
+ 'RIGHT_BRACE' => '}',
33
+ 'RIGHT_PAREN' => ')',
34
+ 'SEMICOLON' => ';',
35
+ 'SLASH' => '/',
36
+ 'STAR' => '*'
37
+ }.freeze
38
+
39
+ attr_reader :strict
40
+
41
+ # Create a new AST builder instance.
42
+ # @param theTokens [Array<Rley::Lexical::Token>] The sequence of input tokens.
43
+ def initialize(theTokens)
44
+ super(theTokens)
45
+ @strict = false
46
+ end
47
+
48
+ protected
49
+
50
+ def terminal2node
51
+ Terminal2NodeClass
52
+ end
53
+
54
+ # Method override
55
+ def new_leaf_node(_production, _terminal, aTokenPosition, aToken)
56
+ Rley::PTree::TerminalNode.new(aToken, aTokenPosition)
57
+ end
58
+
59
+ # Factory method for creating a parent node object.
60
+ # @param aProduction [Production] Production rule
61
+ # @param aRange [Range] Range of tokens matched by the rule
62
+ # @param theTokens [Array] The input tokens
63
+ # @param theChildren [Array] Children nodes (one per rhs symbol)
64
+ def new_parent_node(aProduction, aRange, theTokens, theChildren)
65
+ mth_name = method_name(aProduction.name)
66
+ if respond_to?(mth_name, true)
67
+ node = send(mth_name, aProduction, aRange, theTokens, theChildren)
68
+ else
69
+ # Default action...
70
+ node = case aProduction.rhs.size
71
+ when 0
72
+ return_epsilon(aRange, theTokens, theChildren)
73
+ when 1
74
+ return_first_child(aRange, theTokens, theChildren)
75
+ else
76
+ if strict
77
+ msg = "Don't know production '#{aProduction.name}'"
78
+ raise StandardError, msg
79
+ else
80
+ node = Rley::PTree::NonTerminalNode.new(aProduction.lhs, aRange)
81
+ theChildren&.reverse_each do |child|
82
+ node.add_subnode(child) if child
83
+ end
84
+
85
+ node
86
+ end
87
+ end
88
+ end
89
+
90
+ node
91
+ end
92
+
93
+ # rule('lhs' => 'nonterm_i nonterm_k_plus')
94
+ def reduce_binary_operator(_production, _range, tokens, theChildren)
95
+ operand1 = theChildren[0]
96
+
97
+ # Second child is array with couples [operator, operand2]
98
+ theChildren[1].each do |(operator, operand2)|
99
+ operand1 = LoxBinaryExpr.new(tokens[0].position, operator, operand1, operand2)
100
+ end
101
+
102
+ operand1
103
+ end
104
+
105
+ # rule('something_plus' => 'something_plus operator symbol')
106
+ def reduce_binary_plus_more(_production, _range, _tokens, theChildren)
107
+ result = theChildren[0]
108
+ operator = Name2special[theChildren[1].symbol.name].to_sym
109
+ operand2 = theChildren[2]
110
+ result << [operator, operand2]
111
+ end
112
+
113
+ # rule('something_plus' => 'something_plus symbol')
114
+ def reduce_binary_plus_end(_production, _range, _tokens, theChildren)
115
+ operator = Name2special[theChildren[0].symbol.name].to_sym
116
+ operand2 = theChildren[1]
117
+ [[operator, operand2]]
118
+ end
119
+
120
+ # rule('equality' => 'comparison equalityTest_plus')
121
+ def reduce_equality_plus(production, range, tokens, theChildren)
122
+ reduce_binary_operator(production, range, tokens, theChildren)
123
+ end
124
+
125
+ # rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
126
+ def reduce_equality_t_plus_more(production, range, tokens, theChildren)
127
+ reduce_binary_plus_more(production, range, tokens, theChildren)
128
+ end
129
+
130
+ # rule('equalityTest_star' => 'equalityTest comparison')
131
+ def reduce_equality_t_plus_end(production, range, tokens, theChildren)
132
+ reduce_binary_plus_end(production, range, tokens, theChildren)
133
+ end
134
+
135
+ # rule('comparison' => 'term comparisonTest_plus')
136
+ def reduce_comparison_plus(production, range, tokens, theChildren)
137
+ reduce_binary_operator(production, range, tokens, theChildren)
138
+ end
139
+
140
+ # rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
141
+ # TODO: is it meaningful to implement this rule?
142
+
143
+ # rule('comparisonTest_plus' => 'comparisonTest term')
144
+ def reduce_comparison_t_plus_end(production, range, tokens, theChildren)
145
+ reduce_binary_plus_end(production, range, tokens, theChildren)
146
+ end
147
+
148
+ # rule('term' => 'factor additive_plus')
149
+ def reduce_term_additive(production, range, tokens, theChildren)
150
+ reduce_binary_operator(production, range, tokens, theChildren)
151
+ end
152
+
153
+ # rule('additive_star' => 'additive_star additionOp factor').as 'additionOp_expr'
154
+ def reduce_additive_plus_more(production, range, tokens, theChildren)
155
+ reduce_binary_plus_more(production, range, tokens, theChildren)
156
+ end
157
+
158
+ # rule('additive_plus' => 'additionOp factor')
159
+ def reduce_additive_plus_end(production, range, tokens, theChildren)
160
+ reduce_binary_plus_end(production, range, tokens, theChildren)
161
+ end
162
+
163
+ # rule('factor' => 'multiplicative_plus')
164
+ def reduce_factor_multiplicative(production, range, tokens, theChildren)
165
+ reduce_binary_operator(production, range, tokens, theChildren)
166
+ end
167
+
168
+ # rule('multiplicative_plus' => 'multiplicative_plus multOp unary')
169
+ def reduce_multiplicative_plus_more(production, range, tokens, theChildren)
170
+ reduce_binary_plus_more(production, range, tokens, theChildren)
171
+ end
172
+
173
+ # rule('multiplicative_plus' => 'multOp unary')
174
+ def reduce_multiplicative_plus_end(production, range, tokens, theChildren)
175
+ reduce_binary_plus_end(production, range, tokens, theChildren)
176
+ end
177
+
178
+ # rule('primary' => 'FALSE' | TRUE').as 'literal_expr'
179
+ def reduce_literal_expr(_production, _range, _tokens, theChildren)
180
+ first_child = theChildren.first
181
+ pos = first_child.token.position
182
+ literal = first_child.token.value
183
+ LoxLiteralExpr.new(pos, literal)
184
+ end
185
+ end # class
186
+ end # module
187
+ end # module
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loxxy
4
+ module Ast
5
+ class ASTVisitor
6
+ # Link to the top node to visit
7
+ attr_reader(:top)
8
+
9
+ # List of objects that subscribed to the visit event notification.
10
+ attr_reader(:subscribers)
11
+
12
+ # attr_reader(:runtime)
13
+
14
+ # Build a visitor for the given top.
15
+ # @param aRoot [AST::LoxNode] the parse tree to visit.
16
+ def initialize(aTop)
17
+ raise StandardError if aTop.nil?
18
+
19
+ @top = aTop
20
+ @subscribers = []
21
+ end
22
+
23
+ # Add a subscriber for the visit event notifications.
24
+ # @param aSubscriber [Object]
25
+ def subscribe(aSubscriber)
26
+ subscribers << aSubscriber
27
+ end
28
+
29
+ # Remove the given object from the subscription list.
30
+ # The object won't be notified of visit events.
31
+ # @param aSubscriber [Object]
32
+ def unsubscribe(aSubscriber)
33
+ subscribers.delete_if { |entry| entry == aSubscriber }
34
+ end
35
+
36
+ # The signal to begin the visit of the top.
37
+ def start # (aRuntime)
38
+ # @runtime = aRuntime
39
+ top.accept(self)
40
+ end
41
+
42
+ # Visit event. The visitor is visiting the
43
+ # given terminal node containing a datatype object.
44
+ # @param aLiteralExpr [AST::LoxLiteralExpr] the leaf node to visit.
45
+ def visit_literal_expr(aLiteralExpr)
46
+ broadcast(:before_literal_expr, aLiteralExpr)
47
+ broadcast(:after_literal_expr, aLiteralExpr)
48
+ end
49
+
50
+ =begin
51
+
52
+
53
+ def visit_compound_datum(aCompoundDatum)
54
+ broadcast(:before_compound_datum, aCompoundDatum)
55
+ traverse_children(aCompoundDatum)
56
+ broadcast(:after_compound_datum, aCompoundDatum)
57
+ end
58
+
59
+ # Visit event. The visitor is visiting the
60
+ # given empty list object.
61
+ # @param anEmptyList [SkmEmptyList] the empty list object to visit.
62
+ def visit_empty_list(anEmptyList)
63
+ broadcast(:before_empty_list, anEmptyList)
64
+ broadcast(:after_empty_list, anEmptyList)
65
+ end
66
+
67
+ def visit_pair(aPair)
68
+ broadcast(:before_pair, aPair)
69
+ traverse_car_cdr(aPair)
70
+ broadcast(:after_pair, aPair)
71
+ end
72
+ =end
73
+ =begin
74
+ # Visit event. The visitor is about to visit the given non terminal node.
75
+ # @param aNonTerminalNode [NonTerminalNode] the node to visit.
76
+ def visit_nonterminal(aNonTerminalNode)
77
+ if @traversal == :post_order
78
+ broadcast(:before_non_terminal, aNonTerminalNode)
79
+ traverse_subnodes(aNonTerminalNode)
80
+ else
81
+ traverse_subnodes(aNonTerminalNode)
82
+ broadcast(:before_non_terminal, aNonTerminalNode)
83
+ end
84
+ broadcast(:after_non_terminal, aNonTerminalNode)
85
+ end
86
+ =end
87
+
88
+ private
89
+
90
+ def traverse_children(aParent)
91
+ children = aParent.children
92
+ broadcast(:before_children, aParent, children)
93
+
94
+ # Let's proceed with the visit of children
95
+ children.each { |a_child| a_child.accept(self) }
96
+
97
+ broadcast(:after_children, aParent, children)
98
+ end
99
+
100
+ def traverse_car_cdr(aPair)
101
+ if aPair.car
102
+ broadcast(:before_car, aPair, aPair.car)
103
+ aPair.car.accept(self)
104
+ broadcast(:after_car, aPair, aPair.car)
105
+ end
106
+ if aPair.cdr
107
+ broadcast(:before_cdr, aPair, aPair.cdr)
108
+ aPair.cdr.accept(self)
109
+ broadcast(:after_cdr, aPair, aPair.cdr)
110
+ end
111
+ end
112
+
113
+ # Send a notification to all subscribers.
114
+ # @param msg [Symbol] event to notify
115
+ # @param args [Array] arguments of the notification.
116
+ def broadcast(msg, *args)
117
+ subscribers.each do |subscr|
118
+ next unless subscr.respond_to?(msg) || subscr.respond_to?(:accept_all)
119
+
120
+ subscr.send(msg, runtime, *args)
121
+ end
122
+ end
123
+ end # class
124
+ end # module
125
+ end # module
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_compound_expr'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxBinaryExpr < LoxCompoundExpr
8
+ # @return [Symbol]
9
+ attr_reader :operator
10
+
11
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
12
+ # @param operand1 [Loxxy::Ast::LoxNode]
13
+ # @param operand2 [Loxxy::Ast::LoxNode]
14
+ def initialize(aPosition, anOperator, operand1, operand2)
15
+ super(aPosition, [operand1, operand2])
16
+ @operator = anOperator
17
+ end
18
+
19
+ alias operands subnodes
20
+ end # class
21
+ end # module
22
+ end # module
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_node'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxCompoundExpr < LoxNode
8
+ # @return [Array<Ast::LoxNode>]
9
+ attr_reader :subnodes
10
+
11
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
12
+ # @param theSubnodes [Array<Ast::LoxNode>]
13
+ def initialize(aPosition, theSubnodes)
14
+ super(aPosition)
15
+ @subnodes = theSubnodes
16
+ end
17
+
18
+ # Part of the 'visitee' role in Visitor design pattern.
19
+ # @param visitor [ASTVisitor] the visitor
20
+ def accept(visitor)
21
+ visitor.visit_compound_expr(self)
22
+ end
23
+ end # class
24
+ end # module
25
+ end # module
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lox_node'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ class LoxLiteralExpr < LoxNode
8
+ # @return [Loxxy::Datatype::BuiltinDatatype]
9
+ attr_reader :literal
10
+
11
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
12
+ # @param aLiteral [Loxxy::Datatype::BuiltinDatatype]
13
+ def initialize(aPosition, aLiteral)
14
+ super(aPosition)
15
+ @literal = aLiteral
16
+ end
17
+
18
+ # Part of the 'visitee' role in Visitor design pattern.
19
+ # @param visitor [ASTVisitor] the visitor
20
+ def accept(visitor)
21
+ visitor.visit_literal_expr(self)
22
+ end
23
+ end # class
24
+ end # module
25
+ end # module
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loxxy
4
+ module Ast
5
+ class LoxNode
6
+ # return [Rley::Lexical::Position] Position of the entry in the input stream.
7
+ attr_reader :position
8
+
9
+ # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
10
+ def initialize(aPosition)
11
+ @position = aPosition
12
+ end
13
+
14
+ # Abstract method.
15
+ # Part of the 'visitee' role in Visitor design pattern.
16
+ # @param _visitor [LoxxyTreeVisitor] the visitor
17
+ def accept(_visitor)
18
+ raise NotImplementedError
19
+ end
20
+ end # class
21
+ end # module
22
+ end # module
@@ -26,11 +26,12 @@ module Loxxy
26
26
  add_terminals('EOF')
27
27
 
28
28
  # Top-level rule that matches an entire Lox program
29
- rule('program' => 'declaration_star EOF')
29
+ rule('program' => 'EOF')
30
+ rule('program' => 'declaration_plus EOF')
30
31
 
31
32
  # Declarations: bind an identifier to something
32
- rule('declaration_star' => 'declaration_star declaration')
33
- rule('declaration_star' => [])
33
+ rule('declaration_plus' => 'declaration_plus declaration')
34
+ rule('declaration_plus' => 'declaration')
34
35
  rule('declaration' => 'classDecl')
35
36
  rule('declaration' => 'funDecl')
36
37
  rule('declaration' => 'varDecl')
@@ -75,7 +76,8 @@ module Loxxy
75
76
  rule('printStmt' => 'PRINT expression SEMICOLON')
76
77
  rule('returnStmt' => 'RETURN expression_opt SEMICOLON')
77
78
  rule('whileStmt' => 'WHILE LEFT_PAREN expression RIGHT_PAREN statement')
78
- rule('block' => 'LEFT_BRACE declaration_star RIGHT_BRACE')
79
+ rule('block' => 'LEFT_BRACE declaration_plus RIGHT_BRACE')
80
+ rule('block' => 'LEFT_BRACE RIGHT_BRACE')
79
81
 
80
82
  # Expressions: produce values
81
83
  rule('expression_opt' => 'expression')
@@ -85,49 +87,56 @@ module Loxxy
85
87
  rule('assignment' => 'logic_or')
86
88
  rule('owner_opt' => 'call DOT')
87
89
  rule('owner_opt' => [])
88
- rule('logic_or' => 'logic_and disjunct_star')
89
- rule('disjunct_star' => 'disjunct_star OR logic_and')
90
- rule('disjunct_star' => [])
91
- rule('logic_and' => 'equality conjunct_star')
92
- rule('conjunct_star' => 'conjunct_star AND equality')
93
- rule('conjunct_star' => [])
94
- rule('equality' => 'comparison equalityTest_star')
95
- rule('equalityTest_star' => 'equalityTest_star equalityTest comparison')
96
- rule('equalityTest_star' => [])
90
+ rule('logic_or' => 'logic_and')
91
+ rule('logic_or' => 'logic_and disjunct_plus')
92
+ rule('disjunct_plus' => 'disjunct_plus OR logic_and')
93
+ rule('disjunct_plus' => 'OR logic_and')
94
+ rule('logic_and' => 'equality')
95
+ rule('logic_and' => 'equality conjunct_plus')
96
+ rule('conjunct_plus' => 'conjunct_plus AND equality')
97
+ rule('conjunct_' => 'AND equality')
98
+ rule('equality' => 'comparison')
99
+ rule('equality' => 'comparison equalityTest_plus').as 'equality_plus'
100
+ rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison').as 'equality_t_plus_more'
101
+ rule('equalityTest_plus' => 'equalityTest comparison').as 'equality_t_plus_end'
97
102
  rule('equalityTest' => 'BANG_EQUAL')
98
103
  rule('equalityTest' => 'EQUAL_EQUAL')
99
- rule('comparison' => 'term comparisonTest_star')
100
- rule('comparisonTest_star' => 'comparisonTest_star comparisonTest term')
101
- rule('comparisonTest_star' => [])
104
+ rule('comparison' => 'term')
105
+ rule('comparison' => 'term comparisonTest_plus').as 'comparison_plus'
106
+ rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term').as 'comparison_t_plus_more'
107
+ rule('comparisonTest_plus' => 'comparisonTest term').as 'comparison_t_plus_end'
102
108
  rule('comparisonTest' => 'GREATER')
103
109
  rule('comparisonTest' => 'GREATER_EQUAL')
104
110
  rule('comparisonTest' => 'LESS')
105
111
  rule('comparisonTest' => 'LESS_EQUAL')
106
- rule('term' => 'factor additive_star')
107
- rule('additive_star' => 'additive_star additionOp factor')
108
- rule('additive_star' => [])
112
+ rule('term' => 'factor')
113
+ rule('term' => 'factor additive_plus').as 'term_additive'
114
+ rule('additive_plus' => 'additive_plus additionOp factor').as 'additive_plus_more'
115
+ rule('additive_plus' => 'additionOp factor').as 'additive_plus_end'
109
116
  rule('additionOp' => 'MINUS')
110
117
  rule('additionOp' => 'PLUS')
111
- rule('factor' => 'unary multiplicative_star')
112
- rule('multiplicative_star' => 'multiplicative_star multOp unary')
113
- rule('multiplicative_star' => [])
118
+ rule('factor' => 'unary')
119
+ rule('factor' => 'unary multiplicative_plus').as 'factor_multiplicative'
120
+ rule('multiplicative_plus' => 'multiplicative_plus multOp unary').as 'multiplicative_plus_more'
121
+ rule('multiplicative_plus' => 'multOp unary').as 'multiplicative_plus_end'
114
122
  rule('multOp' => 'SLASH')
115
123
  rule('multOp' => 'STAR')
116
124
  rule('unary' => 'unaryOp unary')
117
125
  rule('unary' => 'call')
118
126
  rule('unaryOp' => 'BANG')
119
127
  rule('unaryOp' => 'MINUS')
120
- rule('call' => 'primary refinement_star')
121
- rule('refinement_star' => 'refinement_star refinement')
122
- rule('refinement_star' => [])
128
+ rule('call' => 'primary')
129
+ rule('call' => 'primary refinement_plus')
130
+ rule('refinement_plus' => 'refinement_plus refinement')
131
+ rule('refinement_plus' => 'refinement')
123
132
  rule('refinement' => 'LEFT_PAREN arguments_opt RIGHT_PAREN')
124
133
  rule('refinement' => 'DOT IDENTIFIER')
125
- rule('primary' => 'TRUE')
126
- rule('primary' => 'FALSE')
127
- rule('primary' => 'NIL')
134
+ rule('primary' => 'TRUE').as 'literal_expr'
135
+ rule('primary' => 'FALSE').as 'literal_expr'
136
+ rule('primary' => 'NIL').as 'literal_expr'
128
137
  rule('primary' => 'THIS')
129
- rule('primary' => 'NUMBER')
130
- rule('primary' => 'STRING')
138
+ rule('primary' => 'NUMBER').as 'literal_expr'
139
+ rule('primary' => 'STRING').as 'literal_expr'
131
140
  rule('primary' => 'IDENTIFIER')
132
141
  rule('primary' => 'LEFT_PAREN expression RIGHT_PAREN')
133
142
  rule('primary' => 'SUPER DOT IDENTIFIER')
@@ -144,8 +153,10 @@ module Loxxy
144
153
  rule('arguments' => 'expression')
145
154
  end
146
155
 
147
- # And now build the grammar and make it accessible via a constant
148
- # @return [Rley::Syntax::Grammar]
149
- Grammar = builder.grammar
156
+ unless defined?(Grammar)
157
+ # And now build the grammar and make it accessible via a constant
158
+ # @return [Rley::Syntax::Grammar]
159
+ Grammar = builder.grammar
160
+ end
150
161
  end # module
151
162
  end # module
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'scanner'
4
+ require_relative 'grammar'
5
+ require_relative '../ast/ast_builder'
6
+
7
+ module Loxxy
8
+ module FrontEnd
9
+ # A Lox parser that produce concrete parse trees.
10
+ # Concrete parse trees are the default kind of parse tree
11
+ # generated by the Rley library.
12
+ # They consist of two node types only:
13
+ # - NonTerminalNode
14
+ # - TerminalNode
15
+ # A NonTerminalNode has zero or more child nodes (called subnodes)
16
+ # A TerminalNode is leaf node, that is, it has no child node.
17
+ # While concrete parse tree nodes can be generated out of the box,
18
+ # they have the following drawbacks:
19
+ # - Generic node classes that aren't always suited for the needs of
20
+ # the language being processing.
21
+ # - Concrete parse tree tend to be deeply nested, which may complicate
22
+ # further processing.
23
+ class Parser
24
+ # @return [Rley::Engine] A facade object for the Rley parsing library
25
+ attr_reader(:engine)
26
+
27
+ def initialize
28
+ # Create a Rley facade object
29
+ @engine = Rley::Engine.new do |cfg|
30
+ cfg.diagnose = true
31
+ cfg.repr_builder = Ast::ASTBuilder
32
+ end
33
+
34
+ # Step 1. Load Lox grammar
35
+ @engine.use_grammar(Loxxy::FrontEnd::Grammar)
36
+ end
37
+
38
+ # Parse the given Lox program into a parse tree.
39
+ # @param source [String] Lox program to parse
40
+ # @return [Rley::ParseTree] A parse tree equivalent to the Lox input.
41
+ def parse(source)
42
+ lexer = Scanner.new(source)
43
+ result = engine.parse(lexer.tokens)
44
+
45
+ unless result.success?
46
+ # Stop if the parse failed...
47
+ line1 = "Parsing failed\n"
48
+ line2 = "Reason: #{result.failure_reason.message}"
49
+ raise StandardError, line1 + line2
50
+ end
51
+
52
+ return engine.convert(result) # engine.to_ptree(result)
53
+ end
54
+ end # class
55
+ end # module
56
+ end # module
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.10'
5
5
  end
@@ -0,0 +1,308 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec_helper' # Use the RSpec framework
4
+
5
+ # Load the class under test
6
+ require_relative '../../lib/loxxy/front_end/parser'
7
+
8
+ module Loxxy
9
+ module FrontEnd
10
+ describe Parser do
11
+ subject { Parser.new }
12
+
13
+ # Utility method to walk towards deeply nested node
14
+ # @param aNTNode [Rley::PTree::NonTerminalNode]
15
+ # @param subnodePath[Array<Integer>] An Array of subnode indices
16
+ def walk_subnodes(aNTNode, subnodePath)
17
+ curr_node = aNTNode
18
+ subnodePath.each do |index|
19
+ curr_node = curr_node.subnodes[index]
20
+ end
21
+
22
+ curr_node
23
+ end
24
+
25
+ context 'Initialization:' do
26
+ it 'should be initialized without argument' do
27
+ expect { Parser.new }.not_to raise_error
28
+ end
29
+
30
+ it 'should have its parse engine initialized' do
31
+ expect(subject.engine).to be_kind_of(Rley::Engine)
32
+ end
33
+ end # context
34
+
35
+ context 'Parsing blank files:' do
36
+ def check_empty_input_result(aParseTree)
37
+ # Parse results MUST to comply to grammar rule:
38
+ # program => declaration_star EOF
39
+ # where the declaration_star MUST be empty
40
+ expect(aParseTree.root.symbol.name).to eq('EOF')
41
+ end
42
+
43
+ it 'should cope with an empty input' do
44
+ ptree = subject.parse('')
45
+ check_empty_input_result(ptree)
46
+ end
47
+
48
+ it 'should cope with whitespaces only input' do
49
+ ptree = subject.parse(' ' * 80 + "\n" * 20)
50
+ check_empty_input_result(ptree)
51
+ end
52
+
53
+ it 'should cope with comments only input' do
54
+ input = +''
55
+ %w[First Second Third].each do |ordinal|
56
+ input << "// #{ordinal} comment line\r\n"
57
+ end
58
+ ptree = subject.parse(input)
59
+ check_empty_input_result(ptree)
60
+ end
61
+ end # context
62
+
63
+ context 'Parsing literals:' do
64
+ it 'should parse a false literal' do
65
+ input = 'false;'
66
+ ptree = subject.parse(input)
67
+ leaf = walk_subnodes(ptree.root, [0, 0])
68
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
69
+ expect(leaf.literal).to be_equal(Datatype::False.instance)
70
+ end
71
+
72
+ it 'should parse a true literal' do
73
+ input = 'true;'
74
+ ptree = subject.parse(input)
75
+ leaf = walk_subnodes(ptree.root, [0, 0])
76
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
77
+ expect(leaf.literal).to be_equal(Datatype::True.instance)
78
+ end
79
+
80
+ it 'should parse number literals' do
81
+ inputs = %w[1234; 12.34;]
82
+ inputs.each do |source|
83
+ ptree = subject.parse(source)
84
+ leaf = walk_subnodes(ptree.root, [0, 0])
85
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
86
+ expect(leaf.literal).to be_kind_of(Datatype::Number)
87
+ expect(leaf.literal.value).to eq(source.to_f)
88
+ end
89
+ end
90
+
91
+ it 'should parse string literals' do
92
+ inputs = [
93
+ '"I am a string";',
94
+ '"";',
95
+ '"123";'
96
+ ]
97
+ inputs.each do |source|
98
+ ptree = subject.parse(source)
99
+ leaf = walk_subnodes(ptree.root, [0, 0])
100
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
101
+ expect(leaf.literal).to be_kind_of(Datatype::LXString)
102
+ expect(leaf.literal.value).to eq(source.gsub(/(^")|(";$)/, ''))
103
+ end
104
+ end
105
+
106
+ it 'should parse a nil literal' do
107
+ input = 'nil;'
108
+ ptree = subject.parse(input)
109
+ leaf = walk_subnodes(ptree.root, [0, 0])
110
+ expect(leaf).to be_kind_of(Ast::LoxLiteralExpr)
111
+ expect(leaf.literal).to be_equal(Datatype::Nil.instance)
112
+ end
113
+ end
114
+
115
+ context 'Parsing expressions:' do
116
+ it 'should parse a hello world program' do
117
+ program = <<-LOX_END
118
+ // Your first Lox program!
119
+ print "Hello, world!";
120
+ LOX_END
121
+ ptree = subject.parse(program)
122
+ root = ptree.root
123
+ expect(root.symbol.name).to eq('program')
124
+ (prnt_stmt, eof) = root.subnodes
125
+ expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
126
+ expect(prnt_stmt.symbol.name).to eq('printStmt')
127
+ expect(prnt_stmt.subnodes.size).to eq(3)
128
+ expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
129
+ expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
130
+ expect(prnt_stmt.subnodes[1]).to be_kind_of(Loxxy::Ast::LoxLiteralExpr)
131
+ expect(prnt_stmt.subnodes[1].literal).to be_kind_of(Loxxy::Datatype::LXString)
132
+ expect(prnt_stmt.subnodes[1].literal.value).to eq('Hello, world!')
133
+ expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
134
+ expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
135
+ expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
136
+ expect(eof.symbol.name).to eq('EOF')
137
+ end
138
+ end # context
139
+
140
+ context 'Parsing arithmetic operations' do
141
+ it 'should parse the addition of two number literals' do
142
+ input = '123 + 456;'
143
+ ptree = subject.parse(input)
144
+ parent = ptree.root.subnodes[0]
145
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
146
+ expect(parent.symbol.name).to eq('exprStmt')
147
+ expr = parent.subnodes[0]
148
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
149
+ expect(expr.operator).to eq(:+)
150
+ expect(expr.operands[0].literal.value).to eq(123)
151
+ expect(expr.operands[1].literal.value).to eq(456)
152
+ end
153
+
154
+ it 'should parse the subtraction of two number literals' do
155
+ input = '4 - 3;'
156
+ ptree = subject.parse(input)
157
+ parent = ptree.root.subnodes[0]
158
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
159
+ expect(parent.symbol.name).to eq('exprStmt')
160
+ expr = parent.subnodes[0]
161
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
162
+ expect(expr.operator).to eq(:-)
163
+ expect(expr.operands[0].literal.value).to eq(4)
164
+ expect(expr.operands[1].literal.value).to eq(3)
165
+ end
166
+
167
+ it 'should parse multiple additive operations' do
168
+ input = '5 + 2 - 3;'
169
+ ptree = subject.parse(input)
170
+ parent = ptree.root.subnodes[0]
171
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
172
+ expect(parent.symbol.name).to eq('exprStmt')
173
+ expr = parent.subnodes[0]
174
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
175
+ expect(expr.operator).to eq(:-)
176
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
177
+ expect(expr.operands[0].operator).to eq(:+)
178
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
179
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
180
+ expect(expr.operands[1].literal.value).to eq(3)
181
+ end
182
+
183
+ it 'should parse the division of two number literals' do
184
+ input = '8 / 2;'
185
+ ptree = subject.parse(input)
186
+ parent = ptree.root.subnodes[0]
187
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
188
+ expect(parent.symbol.name).to eq('exprStmt')
189
+ expr = parent.subnodes[0]
190
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
191
+ expect(expr.operator).to eq(:/)
192
+ expect(expr.operands[0].literal.value).to eq(8)
193
+ expect(expr.operands[1].literal.value).to eq(2)
194
+ end
195
+
196
+ it 'should parse the product of two number literals' do
197
+ input = '12.34 * 0.3;'
198
+ ptree = subject.parse(input)
199
+ parent = ptree.root.subnodes[0]
200
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
201
+ expect(parent.symbol.name).to eq('exprStmt')
202
+ expr = parent.subnodes[0]
203
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
204
+ expect(expr.operator).to eq(:*)
205
+ expect(expr.operands[0].literal.value).to eq(12.34)
206
+ expect(expr.operands[1].literal.value).to eq(0.3)
207
+ end
208
+
209
+ it 'should parse multiple multiplicative operations' do
210
+ input = '5 * 2 / 3;'
211
+ ptree = subject.parse(input)
212
+ parent = ptree.root.subnodes[0]
213
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
214
+ expect(parent.symbol.name).to eq('exprStmt')
215
+ expr = parent.subnodes[0]
216
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
217
+ expect(expr.operator).to eq(:/)
218
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
219
+ expect(expr.operands[0].operator).to eq(:*)
220
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
221
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
222
+ expect(expr.operands[1].literal.value).to eq(3)
223
+ end
224
+
225
+ it 'should parse combination of terms and factors' do
226
+ input = '5 + 2 / 3;'
227
+ ptree = subject.parse(input)
228
+ parent = ptree.root.subnodes[0]
229
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
230
+ expect(parent.symbol.name).to eq('exprStmt')
231
+ expr = parent.subnodes[0]
232
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
233
+ expect(expr.operator).to eq(:+)
234
+ expect(expr.operands[0].literal.value).to eq(5)
235
+ expect(expr.operands[1]).to be_kind_of(Ast::LoxBinaryExpr)
236
+ expect(expr.operands[1].operator).to eq(:/)
237
+ expect(expr.operands[1].operands[0].literal.value).to eq(2)
238
+ expect(expr.operands[1].operands[1].literal.value).to eq(3)
239
+ end
240
+ end # context
241
+
242
+ context 'Parsing string concatenation' do
243
+ it 'should parse the concatenation of two string literals' do
244
+ input = '"Lo" + "ve";'
245
+ ptree = subject.parse(input)
246
+ parent = ptree.root.subnodes[0]
247
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
248
+ expect(parent.symbol.name).to eq('exprStmt')
249
+ expr = parent.subnodes[0]
250
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
251
+ expect(expr.operator).to eq(:+)
252
+ expect(expr.operands[0].literal.value).to eq('Lo')
253
+ expect(expr.operands[1].literal.value).to eq('ve')
254
+ end
255
+ end # context
256
+
257
+ context 'Parsing comparison expressions' do
258
+ it 'should parse the comparison of two number literals' do
259
+ %w[> >= < <=].each do |predicate|
260
+ input = "3 #{predicate} 2;"
261
+ ptree = subject.parse(input)
262
+ parent = ptree.root.subnodes[0]
263
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
264
+ expect(parent.symbol.name).to eq('exprStmt')
265
+ expr = parent.subnodes[0]
266
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
267
+ expect(expr.operator).to eq(predicate.to_sym)
268
+ expect(expr.operands[0].literal.value).to eq(3)
269
+ expect(expr.operands[1].literal.value).to eq(2)
270
+ end
271
+ end
272
+ end # context
273
+
274
+ context 'Parsing equality expressions' do
275
+ it 'should parse the equality of two number literals' do
276
+ %w[!= ==].each do |predicate|
277
+ input = "3 #{predicate} 2;"
278
+ ptree = subject.parse(input)
279
+ parent = ptree.root.subnodes[0]
280
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
281
+ expect(parent.symbol.name).to eq('exprStmt')
282
+ expr = parent.subnodes[0]
283
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
284
+ expect(expr.operator).to eq(predicate.to_sym)
285
+ expect(expr.operands[0].literal.value).to eq(3)
286
+ expect(expr.operands[1].literal.value).to eq(2)
287
+ end
288
+ end
289
+
290
+ it 'should parse combination of equality expressions' do
291
+ input = '5 != 2 == false; // A bit contrived example'
292
+ ptree = subject.parse(input)
293
+ parent = ptree.root.subnodes[0]
294
+ expect(parent).to be_kind_of(Rley::PTree::NonTerminalNode)
295
+ expect(parent.symbol.name).to eq('exprStmt')
296
+ expr = parent.subnodes[0]
297
+ expect(expr).to be_kind_of(Ast::LoxBinaryExpr)
298
+ expect(expr.operator).to eq(:==)
299
+ expect(expr.operands[0]).to be_kind_of(Ast::LoxBinaryExpr)
300
+ expect(expr.operands[0].operator).to eq(:!=)
301
+ expect(expr.operands[0].operands[0].literal.value).to eq(5)
302
+ expect(expr.operands[0].operands[1].literal.value).to eq(2)
303
+ expect(expr.operands[1].literal.value).to be_falsey
304
+ end
305
+ end # context
306
+ end # describe
307
+ end # module
308
+ end # module
@@ -26,10 +26,7 @@ module Loxxy
26
26
  # program => declaration_star EOF
27
27
  # where the declaration_star MUST be empty
28
28
  expect(aParseTree.root.symbol.name).to eq('program')
29
- (decls, eof) = aParseTree.root.subnodes
30
- expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
31
- expect(decls.symbol.name).to eq('declaration_star')
32
- expect(decls.subnodes).to be_empty
29
+ eof = aParseTree.root.subnodes.first
33
30
  expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
34
31
  expect(eof.symbol.name).to eq('EOF')
35
32
  end
@@ -76,8 +73,8 @@ LOX_END
76
73
  expect(root.symbol.name).to eq('program')
77
74
  (decls, eof) = root.subnodes
78
75
  expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
79
- expect(decls.symbol.name).to eq('declaration_star')
80
- stmt = decls.subnodes[1].subnodes[0]
76
+ expect(decls.symbol.name).to eq('declaration_plus')
77
+ stmt = decls.subnodes[0].subnodes[0]
81
78
  expect(stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
82
79
  expect(stmt.symbol.name).to eq('statement')
83
80
  prnt_stmt = stmt.subnodes[0]
@@ -223,6 +223,18 @@ LOX_END
223
223
  ]
224
224
  match_expectations(subject, expectations)
225
225
  end
226
+
227
+ it 'should cope with single slash (divide) expression' do
228
+ subject.start_with('8 / 2')
229
+
230
+ expectations = [
231
+ # [token lexeme]
232
+ %w[NUMBER 8],
233
+ %w[SLASH /],
234
+ %w[NUMBER 2]
235
+ ]
236
+ match_expectations(subject, expectations)
237
+ end
226
238
  end # context
227
239
  end # describe
228
240
  end # module
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/setup'
3
+ require 'rspec' # Use the RSpec framework
4
4
  require 'loxxy'
5
5
 
6
6
  RSpec.configure do |config|
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.5
4
+ version: 0.0.10
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-02 00:00:00.000000000 Z
11
+ date: 2021-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -84,6 +84,12 @@ files:
84
84
  - README.md
85
85
  - Rakefile
86
86
  - lib/loxxy.rb
87
+ - lib/loxxy/ast/ast_builder.rb
88
+ - lib/loxxy/ast/ast_visitor.rb
89
+ - lib/loxxy/ast/lox_binary_expr.rb
90
+ - lib/loxxy/ast/lox_compound_expr.rb
91
+ - lib/loxxy/ast/lox_literal_expr.rb
92
+ - lib/loxxy/ast/lox_node.rb
87
93
  - lib/loxxy/datatype/all_datatypes.rb
88
94
  - lib/loxxy/datatype/boolean.rb
89
95
  - lib/loxxy/datatype/builtin_datatype.rb
@@ -94,11 +100,13 @@ files:
94
100
  - lib/loxxy/datatype/true.rb
95
101
  - lib/loxxy/front_end/grammar.rb
96
102
  - lib/loxxy/front_end/literal.rb
103
+ - lib/loxxy/front_end/parser.rb
97
104
  - lib/loxxy/front_end/raw_parser.rb
98
105
  - lib/loxxy/front_end/scanner.rb
99
106
  - lib/loxxy/version.rb
100
107
  - loxxy.gemspec
101
108
  - spec/datatype/lx_string_spec.rb
109
+ - spec/front_end/parser_spec.rb
102
110
  - spec/front_end/raw_parser_spec.rb
103
111
  - spec/front_end/scanner_spec.rb
104
112
  - spec/loxxy_spec.rb
@@ -129,6 +137,7 @@ specification_version: 4
129
137
  summary: An implementation of the Lox programming language. WIP
130
138
  test_files:
131
139
  - spec/datatype/lx_string_spec.rb
140
+ - spec/front_end/parser_spec.rb
132
141
  - spec/front_end/raw_parser_spec.rb
133
142
  - spec/front_end/scanner_spec.rb
134
143
  - spec/loxxy_spec.rb