loxxy 0.0.6 → 0.0.7
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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +16 -0
- data/README.md +12 -64
- data/lib/loxxy/ast/ast_builder.rb +23 -0
- data/lib/loxxy/ast/ast_visitor.rb +125 -0
- data/lib/loxxy/ast/lox_binary_expr.rb +22 -0
- data/lib/loxxy/ast/lox_compound_expr.rb +25 -0
- data/lib/loxxy/ast/lox_literal_expr.rb +25 -0
- data/lib/loxxy/ast/lox_node.rb +22 -0
- data/lib/loxxy/front_end/grammar.rb +39 -30
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/parser_spec.rb +79 -24
- data/spec/front_end/raw_parser_spec.rb +3 -6
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1b591e9ed6ae88b4b4232c01c4f08fd4453fb499a665c368cd64ce2135a6718
|
4
|
+
data.tar.gz: 6bc1f01f9f0a963a2781271c46391e2c2801b61cc1b076e0b79be30450bee2af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5dd208921fc139eb5f48b182abbe64b919c514904dfab073b09c4a9656989db5c95a5e1bd6f72733a46af8351dc6f95ab94612d8016c22e13dbcedd4637db90b
|
7
|
+
data.tar.gz: 63ac04a1cd79335daf5dbb38de2615e89044c49ec74fd174200149ef24d829861c3af0e7bf62862cbffc03940b955a6c9336344696d432e4efaa83d2dc1e9d35
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
## [0.0.7] - 2021-01-06
|
2
|
+
- Lox grammar reworked, initial AST classes created.
|
3
|
+
|
4
|
+
## Added
|
5
|
+
- Class `Parser` this one generates AST's (Abstract Syntax Tree)
|
6
|
+
- Class `AST::ASTVisitor` draft initial implementation.
|
7
|
+
- Class `AST::BinaryExpr` draft initial implementation.
|
8
|
+
- Class `AST::LoxCompoundExpr` draft initial implementation.
|
9
|
+
- Class `AST::LiteralExpr` draft initial implementation.
|
10
|
+
- Class `AST::LoxNode` draft initial implementation.
|
11
|
+
|
12
|
+
## Changed
|
13
|
+
- File `spec_helper.rb`: removed Bundler dependency
|
14
|
+
- Class `AST::ASTBuilder` added initial `reduce_` methods.
|
15
|
+
- File `README.md` Removed example with AST generation since this is in flux.
|
16
|
+
|
1
17
|
## [0.0.6] - 2021-01-03
|
2
18
|
- First iteration of a parser with AST generation.
|
3
19
|
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Loxxy
|
2
2
|
|
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/).
|
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
5
|
|
6
6
|
## Purpose of this project:
|
7
7
|
- To deliver an open source example of a programming language fully implemented in Ruby
|
@@ -14,7 +14,7 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
|
|
14
14
|
- [DONE] Raw parser. It parses Lox programs and generates a parse tree.
|
15
15
|
- [DONE] Tailored parser for generating AST (Abstract Syntax Tree)
|
16
16
|
- [STARTED] Custom AST builder class
|
17
|
-
- [
|
17
|
+
- [STARTED] Hierarchy classes for representing Lox expressions in AST
|
18
18
|
- [TODO] Interpreter or transpiler
|
19
19
|
|
20
20
|
## Example
|
@@ -48,8 +48,7 @@ tree_formatter.render(visitor)
|
|
48
48
|
This is the output produced by the above example:
|
49
49
|
```
|
50
50
|
program
|
51
|
-
+--
|
52
|
-
| +-- declaration_star
|
51
|
+
+-- declaration_plus
|
53
52
|
| +-- declaration
|
54
53
|
| +-- statement
|
55
54
|
| +-- printStmt
|
@@ -58,69 +57,18 @@ program
|
|
58
57
|
| | +-- assignment
|
59
58
|
| | +-- logic_or
|
60
59
|
| | +-- logic_and
|
61
|
-
| |
|
62
|
-
| |
|
63
|
-
| |
|
64
|
-
| |
|
65
|
-
| |
|
66
|
-
| |
|
67
|
-
| |
|
68
|
-
| |
|
69
|
-
| | | | | | | | +-- refinement_star
|
70
|
-
| | | | | | | +-- multiplicative_star
|
71
|
-
| | | | | | +-- additive_star
|
72
|
-
| | | | | +-- comparisonTest_star
|
73
|
-
| | | | +-- equalityTest_star
|
74
|
-
| | | +-- conjunct_star
|
75
|
-
| | +-- disjunct_star
|
60
|
+
| | +-- equality
|
61
|
+
| | +-- comparison
|
62
|
+
| | +-- term
|
63
|
+
| | +-- factor
|
64
|
+
| | +-- unary
|
65
|
+
| | +-- call
|
66
|
+
| | +-- primary
|
67
|
+
| | +-- STRING: '"Hello, world!"'
|
76
68
|
| +-- SEMICOLON: ';'
|
77
69
|
+-- EOF: ''
|
78
70
|
```
|
79
71
|
|
80
|
-
### Example using Parser class
|
81
|
-
Except for the `Parser` class name, this example is identical
|
82
|
-
to the previous one.
|
83
|
-
|
84
|
-
```ruby
|
85
|
-
require 'loxxy'
|
86
|
-
|
87
|
-
|
88
|
-
lox_input = <<-LOX_END
|
89
|
-
// Your first Lox program!
|
90
|
-
print "Hello, world!";
|
91
|
-
LOX_END
|
92
|
-
|
93
|
-
# Show that the parser accepts the above program
|
94
|
-
parser = Loxxy::FrontEnd::Parser.new
|
95
|
-
|
96
|
-
# Now parse the input into an abstract parse tree...
|
97
|
-
ptree = parser.parse(lox_input)
|
98
|
-
|
99
|
-
# Display the parse tree thanks to Rley utility classes...
|
100
|
-
visitor = Rley::ParseTreeVisitor.new(ptree)
|
101
|
-
tree_formatter = Rley::Formatter::Asciitree.new($stdout)
|
102
|
-
tree_formatter.render(visitor)
|
103
|
-
```
|
104
|
-
|
105
|
-
However the output differs significantly:
|
106
|
-
```
|
107
|
-
program
|
108
|
-
+-- declaration_star
|
109
|
-
| +-- printStmt
|
110
|
-
| +-- PRINT: 'print'
|
111
|
-
| +-- logic_or
|
112
|
-
| | +-- logic_and
|
113
|
-
| | +-- equality
|
114
|
-
| | +-- comparison
|
115
|
-
| | +-- term
|
116
|
-
| | +-- factor
|
117
|
-
| | +-- call
|
118
|
-
| | +-- STRING: '"Hello, world!"'
|
119
|
-
| +-- SEMICOLON: ';'
|
120
|
-
+-- EOF: ''
|
121
|
-
```
|
122
|
-
|
123
|
-
|
124
72
|
|
125
73
|
## Installation
|
126
74
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../datatype/all_datatypes'
|
4
|
+
require_relative 'lox_literal_expr'
|
5
|
+
require_relative 'lox_binary_expr'
|
4
6
|
|
5
7
|
module Loxxy
|
6
8
|
module Ast
|
@@ -69,6 +71,27 @@ module Loxxy
|
|
69
71
|
|
70
72
|
node
|
71
73
|
end
|
74
|
+
|
75
|
+
# rule('additive_star' => 'additive_star additionOp factor').as 'additionOp_expr'
|
76
|
+
def reduce_additionOp_expr(_production, _range, _tokens, theChildren)
|
77
|
+
(operand1, operator_node, operand2) = theChildren
|
78
|
+
operator = operator_node.symbol.name == 'MINUS' ? :- : :+
|
79
|
+
LoxBinaryExpr.new(operator_node.token.position, operator, operand1, operand2)
|
80
|
+
end
|
81
|
+
|
82
|
+
# rule('multiplicative_star' => 'multiplicative_star multOp unary').as 'multOp_expr'
|
83
|
+
# def reduce_multOp_expr(_production, _range, _tokens, theChildren)
|
84
|
+
# (operand1, operator, operand2) = theChildren
|
85
|
+
# LoxBinaryExpr.new(operator.token.position, operator, operand1, operand2)
|
86
|
+
# end
|
87
|
+
|
88
|
+
# rule('primary' => 'FALSE' | TRUE').as 'literal_expr'
|
89
|
+
def reduce_literal_expr(_production, _range, _tokens, theChildren)
|
90
|
+
first_child = theChildren.first
|
91
|
+
pos = first_child.token.position
|
92
|
+
literal = first_child.token.value
|
93
|
+
LoxLiteralExpr.new(pos, literal)
|
94
|
+
end
|
72
95
|
end # class
|
73
96
|
end # module
|
74
97
|
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' => '
|
29
|
+
rule('program' => 'EOF')
|
30
|
+
rule('program' => 'declaration_plus EOF')
|
30
31
|
|
31
32
|
# Declarations: bind an identifier to something
|
32
|
-
rule('
|
33
|
-
rule('
|
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
|
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
|
89
|
-
rule('
|
90
|
-
rule('
|
91
|
-
rule('
|
92
|
-
rule('
|
93
|
-
rule('
|
94
|
-
rule('
|
95
|
-
rule('
|
96
|
-
rule('
|
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')
|
100
|
+
rule('equalityTest_plus' => 'equalityTest_plus equalityTest comparison')
|
101
|
+
rule('equalityTest_star' => 'equalityTest comparison')
|
97
102
|
rule('equalityTest' => 'BANG_EQUAL')
|
98
103
|
rule('equalityTest' => 'EQUAL_EQUAL')
|
99
|
-
rule('comparison' => 'term
|
100
|
-
rule('
|
101
|
-
rule('
|
104
|
+
rule('comparison' => 'term')
|
105
|
+
rule('comparison' => 'term comparisonTest_plus')
|
106
|
+
rule('comparisonTest_plus' => 'comparisonTest_plus comparisonTest term')
|
107
|
+
rule('comparisonTest_plus' => 'comparisonTest term')
|
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
|
107
|
-
rule('
|
108
|
-
rule('
|
112
|
+
rule('term' => 'factor')
|
113
|
+
rule('term' => 'factor additive_plus')
|
114
|
+
rule('additive_plus' => 'additive_plus additionOp factor').as 'additionOp_expr'
|
115
|
+
rule('additive_plus' => 'additionOp factor')
|
109
116
|
rule('additionOp' => 'MINUS')
|
110
117
|
rule('additionOp' => 'PLUS')
|
111
|
-
rule('factor' => 'unary
|
112
|
-
rule('
|
113
|
-
rule('
|
118
|
+
rule('factor' => 'unary')
|
119
|
+
rule('factor' => 'multiplicative_plus')
|
120
|
+
rule('multiplicative_plus' => 'multiplicative_plus multOp unary') # .as 'multOp_expr'
|
121
|
+
rule('multiplicative_plus' => 'multOp unary')
|
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
|
121
|
-
rule('
|
122
|
-
rule('
|
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')
|
data/lib/loxxy/version.rb
CHANGED
@@ -10,6 +10,18 @@ module Loxxy
|
|
10
10
|
describe Parser do
|
11
11
|
subject { Parser.new }
|
12
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
|
+
|
13
25
|
context 'Initialization:' do
|
14
26
|
it 'should be initialized without argument' do
|
15
27
|
expect { Parser.new }.not_to raise_error
|
@@ -25,11 +37,7 @@ module Loxxy
|
|
25
37
|
# Parse results MUST to comply to grammar rule:
|
26
38
|
# program => declaration_star EOF
|
27
39
|
# where the declaration_star MUST be empty
|
28
|
-
expect(aParseTree.root.symbol.name).to eq('
|
29
|
-
expect(aParseTree.root.subnodes.size).to eq(1)
|
30
|
-
eof = aParseTree.root.subnodes[0]
|
31
|
-
expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
|
32
|
-
expect(eof.symbol.name).to eq('EOF')
|
40
|
+
expect(aParseTree.root.symbol.name).to eq('EOF')
|
33
41
|
end
|
34
42
|
|
35
43
|
it 'should cope with an empty input' do
|
@@ -52,18 +60,59 @@ module Loxxy
|
|
52
60
|
end
|
53
61
|
end # context
|
54
62
|
|
55
|
-
context 'Parsing
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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)
|
63
88
|
end
|
89
|
+
end
|
64
90
|
|
65
|
-
|
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
|
66
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
|
67
116
|
it 'should parse a hello world program' do
|
68
117
|
program = <<-LOX_END
|
69
118
|
// Your first Lox program!
|
@@ -72,25 +121,31 @@ LOX_END
|
|
72
121
|
ptree = subject.parse(program)
|
73
122
|
root = ptree.root
|
74
123
|
expect(root.symbol.name).to eq('program')
|
75
|
-
(
|
76
|
-
expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
|
77
|
-
expect(decls.symbol.name).to eq('declaration_star')
|
78
|
-
prnt_stmt = decls.subnodes[0]
|
124
|
+
(prnt_stmt, eof) = root.subnodes
|
79
125
|
expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
|
126
|
+
expect(prnt_stmt.symbol.name).to eq('printStmt')
|
80
127
|
expect(prnt_stmt.subnodes.size).to eq(3)
|
81
128
|
expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
|
82
129
|
expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
|
83
|
-
expect(prnt_stmt.subnodes[1]).to be_kind_of(
|
84
|
-
|
85
|
-
|
86
|
-
# expect(leaf_node.symbol.name).to eq('STRING')
|
87
|
-
# expect(leaf_node.token.value).to eq('Hello, world!')
|
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!')
|
88
133
|
expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
|
89
134
|
expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
|
90
135
|
expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
|
91
136
|
expect(eof.symbol.name).to eq('EOF')
|
92
137
|
end
|
93
|
-
end
|
138
|
+
end # context
|
139
|
+
|
140
|
+
context 'Parsing literals:' do
|
141
|
+
it 'should parse the addition of two number literals' do
|
142
|
+
# input = '123 + 456;'
|
143
|
+
# ptree = subject.parse(input)
|
144
|
+
# term = walk_subnodes(ptree.root, [0, 0, 0, 0, 0, 0, 0])
|
145
|
+
# expect(leaf).to be_kind_of(Ast::LoxBinaryExpr)
|
146
|
+
# expect(leaf.literal).to be_equal(Datatype::False.instance)
|
147
|
+
end
|
148
|
+
end # context
|
94
149
|
end # describe
|
95
150
|
end # module
|
96
151
|
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
|
-
|
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('
|
80
|
-
stmt = decls.subnodes[
|
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]
|
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.
|
4
|
+
version: 0.0.7
|
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
|
+
date: 2021-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -85,6 +85,11 @@ files:
|
|
85
85
|
- Rakefile
|
86
86
|
- lib/loxxy.rb
|
87
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
|
88
93
|
- lib/loxxy/datatype/all_datatypes.rb
|
89
94
|
- lib/loxxy/datatype/boolean.rb
|
90
95
|
- lib/loxxy/datatype/builtin_datatype.rb
|