loxxy 0.0.4 → 0.0.9
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 +13 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +54 -0
- data/README.md +43 -15
- data/lib/loxxy.rb +1 -0
- data/lib/loxxy/ast/ast_builder.rb +157 -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/datatype/builtin_datatype.rb +3 -4
- data/lib/loxxy/datatype/false.rb +3 -3
- data/lib/loxxy/datatype/lx_string.rb +4 -9
- data/lib/loxxy/datatype/nil.rb +3 -3
- data/lib/loxxy/datatype/number.rb +2 -10
- data/lib/loxxy/datatype/true.rb +4 -4
- data/lib/loxxy/front_end/grammar.rb +45 -35
- data/lib/loxxy/front_end/literal.rb +1 -1
- data/lib/loxxy/front_end/parser.rb +56 -0
- data/lib/loxxy/front_end/raw_parser.rb +2 -3
- data/lib/loxxy/front_end/scanner.rb +4 -6
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +2 -2
- data/spec/datatype/lx_string_spec.rb +35 -35
- data/spec/front_end/parser_spec.rb +275 -0
- data/spec/front_end/raw_parser_spec.rb +6 -7
- data/spec/front_end/scanner_spec.rb +14 -2
- data/spec/loxxy_spec.rb +1 -1
- data/spec/spec_helper.rb +5 -3
- metadata +12 -2
@@ -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
|
@@ -8,15 +8,14 @@ module Loxxy
|
|
8
8
|
class BuiltinDatatype
|
9
9
|
# @return [Object] The Ruby representation
|
10
10
|
attr_reader :value
|
11
|
-
|
11
|
+
|
12
12
|
# Constructor. Initialize a Lox value from one of its built-in data type.
|
13
13
|
def initialize(aValue)
|
14
14
|
@value = validated_value(aValue)
|
15
15
|
end
|
16
|
-
|
17
|
-
|
16
|
+
|
18
17
|
protected
|
19
|
-
|
18
|
+
|
20
19
|
def validated_value(aValue)
|
21
20
|
aValue
|
22
21
|
end
|
data/lib/loxxy/datatype/false.rb
CHANGED
@@ -8,13 +8,13 @@ module Loxxy
|
|
8
8
|
# Class for representing a Lox false value.
|
9
9
|
class False < Boolean
|
10
10
|
include Singleton # Make a singleton class
|
11
|
-
|
11
|
+
|
12
12
|
# Build the sole instance
|
13
13
|
def initialize
|
14
14
|
super(false)
|
15
15
|
end
|
16
16
|
end # class
|
17
|
-
|
18
|
-
False.instance.freeze
|
17
|
+
|
18
|
+
False.instance.freeze # Make the sole instance immutable
|
19
19
|
end # module
|
20
20
|
end # module
|
@@ -6,12 +6,6 @@ module Loxxy
|
|
6
6
|
module Datatype
|
7
7
|
# Class for representing a Lox string of characters value.
|
8
8
|
class LXString < BuiltinDatatype
|
9
|
-
|
10
|
-
# Build the sole instance
|
11
|
-
def initialize(aValue)
|
12
|
-
super(aValue)
|
13
|
-
end
|
14
|
-
|
15
9
|
# Compare a Lox String with another Lox (or genuine Ruby) String
|
16
10
|
# @param other [Datatype::LxString, String]
|
17
11
|
# @return [Boolean]
|
@@ -22,7 +16,8 @@ module Loxxy
|
|
22
16
|
when String
|
23
17
|
value == other
|
24
18
|
else
|
25
|
-
|
19
|
+
err_msg = "Cannot compare a #{self.class} with #{other.class}"
|
20
|
+
raise StandardError, err_msg
|
26
21
|
end
|
27
22
|
end
|
28
23
|
|
@@ -34,8 +29,8 @@ module Loxxy
|
|
34
29
|
end
|
35
30
|
|
36
31
|
# Remove double quotes delimiter
|
37
|
-
aValue.gsub(/(^")|("$)/, '')
|
32
|
+
aValue.gsub(/(^")|("$)/, '')
|
38
33
|
end
|
39
34
|
end # class
|
40
35
|
end # module
|
41
|
-
end # module
|
36
|
+
end # module
|
data/lib/loxxy/datatype/nil.rb
CHANGED
@@ -8,13 +8,13 @@ module Loxxy
|
|
8
8
|
# Class for representing a Lox nil "value".
|
9
9
|
class Nil < BuiltinDatatype
|
10
10
|
include Singleton # Make a singleton class
|
11
|
-
|
11
|
+
|
12
12
|
# Build the sole instance
|
13
13
|
def initialize
|
14
14
|
super(nil)
|
15
15
|
end
|
16
16
|
end # class
|
17
|
-
|
18
|
-
Nil.instance.freeze
|
17
|
+
|
18
|
+
Nil.instance.freeze # Make the sole instance immutable
|
19
19
|
end # module
|
20
20
|
end # module
|
@@ -6,19 +6,11 @@ module Loxxy
|
|
6
6
|
module Datatype
|
7
7
|
# Class for representing a Lox numeric value.
|
8
8
|
class Number < BuiltinDatatype
|
9
|
-
|
10
|
-
# Build the sole instance
|
11
|
-
def initialize(aValue)
|
12
|
-
super(aValue)
|
13
|
-
end
|
14
|
-
|
15
9
|
protected
|
16
10
|
|
17
11
|
def validated_value(aValue)
|
18
12
|
case aValue
|
19
|
-
when Integer
|
20
|
-
result = aValue
|
21
|
-
when Numeric
|
13
|
+
when Integer, Numeric
|
22
14
|
result = aValue
|
23
15
|
when /^-?\d+$/
|
24
16
|
result = aValue.to_i
|
@@ -32,4 +24,4 @@ module Loxxy
|
|
32
24
|
end
|
33
25
|
end # class
|
34
26
|
end # module
|
35
|
-
end # module
|
27
|
+
end # module
|
data/lib/loxxy/datatype/true.rb
CHANGED
@@ -8,13 +8,13 @@ module Loxxy
|
|
8
8
|
# Class for representing a Lox true value.
|
9
9
|
class True < Boolean
|
10
10
|
include Singleton # Make a singleton class
|
11
|
-
|
11
|
+
|
12
12
|
# Build the sole instance
|
13
13
|
def initialize
|
14
14
|
super(true)
|
15
|
-
end
|
15
|
+
end
|
16
16
|
end # class
|
17
|
-
|
18
|
-
True.instance.freeze
|
17
|
+
|
18
|
+
True.instance.freeze # Make the sole instance immutable
|
19
19
|
end # module
|
20
20
|
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,53 +87,60 @@ 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').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
|
107
|
-
rule('
|
108
|
-
rule('
|
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
|
112
|
-
rule('
|
113
|
-
rule('
|
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
|
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')
|
134
|
-
|
143
|
+
|
135
144
|
# Utility rules
|
136
145
|
rule('function' => 'IDENTIFIER LEFT_PAREN params_opt RIGHT_PAREN block')
|
137
146
|
rule('params_opt' => 'parameters')
|
@@ -142,11 +151,12 @@ module Loxxy
|
|
142
151
|
rule('arguments_opt' => [])
|
143
152
|
rule('arguments' => 'arguments COMMA expression')
|
144
153
|
rule('arguments' => 'expression')
|
145
|
-
|
146
154
|
end
|
147
155
|
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
151
161
|
end # module
|
152
162
|
end # module
|
@@ -14,7 +14,7 @@ module Loxxy
|
|
14
14
|
# @param theLexeme [String] the lexeme (= piece of text from input)
|
15
15
|
# @param aTerminal [Rley::Syntax::Terminal, String]
|
16
16
|
# The terminal symbol corresponding to the lexeme.
|
17
|
-
# @param aPosition [Rley::Lexical::Position] The position of lexeme
|
17
|
+
# @param aPosition [Rley::Lexical::Position] The position of lexeme
|
18
18
|
# in input text.
|
19
19
|
def initialize(aValue, aLexeme, aTerminal, aPosition)
|
20
20
|
super(aLexeme, aTerminal, aPosition)
|
@@ -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
|
@@ -15,7 +15,7 @@ module Loxxy
|
|
15
15
|
# A TerminalNode is leaf node, that is, it has no child node.
|
16
16
|
# While concrete parse tree nodes can be generated out of the box,
|
17
17
|
# they have the following drawbacks:
|
18
|
-
# - Generic node classes that aren't always suited for the needs of
|
18
|
+
# - Generic node classes that aren't always suited for the needs of
|
19
19
|
# the language being processing.
|
20
20
|
# - Concrete parse tree tend to be deeply nested, which may complicate
|
21
21
|
# further processing.
|
@@ -27,7 +27,6 @@ module Loxxy
|
|
27
27
|
# Create a Rley facade object
|
28
28
|
@engine = Rley::Engine.new do |cfg|
|
29
29
|
cfg.diagnose = true
|
30
|
-
# cfg.repr_builder = SkmBuilder
|
31
30
|
end
|
32
31
|
|
33
32
|
# Step 1. Load Lox grammar
|
@@ -52,4 +51,4 @@ module Loxxy
|
|
52
51
|
end
|
53
52
|
end # class
|
54
53
|
end # module
|
55
|
-
end # module
|
54
|
+
end # module
|