loxxy 0.0.3 → 0.0.8

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: 3f8733558204a56db1177c9c25e5fae5af1676c455b1e3a2f3145a70fae741a7
4
- data.tar.gz: ac99f27c25e52703adea30eca1db61671a275355a4a3a00657f2f0359e7fe57f
3
+ metadata.gz: ed8bad2906a9ffd76c81a1fe14d89088f913f84996f0a6d98e7f104e0eccfdc9
4
+ data.tar.gz: 40ee48dc0f4d7e0ce1028820b9c5cf56d0c78d9853916f3bf9e5f32a7106887a
5
5
  SHA512:
6
- metadata.gz: beb8fa74fab5d2799ac982a2585a4ba8ebce992ae2b9a98d9356fe9e0afdd8d2b08d371bd9a6940e7b8761710f876c5494697a47ba1b4a047d7a38babb0d1695
7
- data.tar.gz: 64df1b2c919c47f8852bc2885e7dc2eac5bd55b5892f2f6477e56401a9e2d1cf01080c512d2592d07472ef8ff053ba26ac0b5c29278e9aefa57cea9f639c33ba
6
+ metadata.gz: 69c3c6e6b2a60c3f27284be820ac8f2687de0073c28719bd9414578ab37ac59de267dc1e69897966fef33d4ba725597587fe991b8e20ce6d40ec1587f3aa53c4
7
+ data.tar.gz: c296dcedcf97fe7426a59cfb2df2144685f2809c64becb18483e62a5aea536c2ca3ae2ab697b865c0945456e191a0a011f01123d34160acfa554761b8a2f917e
@@ -1,6 +1,7 @@
1
1
  AllCops:
2
2
  Exclude:
3
3
  - 'exp/**/*'
4
+ - 'demo/**/*'
4
5
 
5
6
  Layout/ArgumentAlignment:
6
7
  Enabled: false
@@ -55,6 +56,9 @@ Layout/MultilineMethodCallBraceLayout:
55
56
 
56
57
  Layout/SpaceAroundOperators:
57
58
  Enabled: true
59
+
60
+ Layout/SpaceBeforeBrackets:
61
+ Enabled: true
58
62
 
59
63
  Layout/SpaceInsideParens:
60
64
  Enabled: true
@@ -70,6 +74,9 @@ Layout/TrailingEmptyLines:
70
74
 
71
75
  Layout/TrailingWhitespace:
72
76
  Enabled: true
77
+
78
+ Lint/AmbiguousAssignment:
79
+ Enabled: true
73
80
 
74
81
  Lint/DuplicateBranch:
75
82
  Enabled: true
@@ -163,6 +170,9 @@ Naming/BlockParameterName:
163
170
 
164
171
  Naming/MethodParameterName:
165
172
  Enabled: false
173
+
174
+ Naming/MethodName:
175
+ Enabled: false
166
176
 
167
177
  Naming/VariableName:
168
178
  Enabled: false
@@ -226,6 +236,9 @@ Style/GuardClause:
226
236
 
227
237
  Style/HashEachMethods:
228
238
  Enabled: true
239
+
240
+ Style/HashExcept:
241
+ Enabled: true
229
242
 
230
243
  Style/HashTransformKeys:
231
244
  Enabled: true
@@ -0,0 +1,6 @@
1
+ --exclude examples --exclude features --exclude spec
2
+ --no-private
3
+ --markup markdown
4
+ -
5
+ CHANGELOG.md
6
+ LICENSE.txt
@@ -1,3 +1,59 @@
1
+ ## [0.0.8] - 2021-01-07
2
+ - AST node generation for arithmetic operations of number literals.
3
+
4
+ ## Changed
5
+ - Class `AST::ASTBuilder` added `reduce_` methods for arithmetic operations.
6
+
7
+ ## Fixed
8
+ - File `grammar.rb`: second rule for `factor` had a missing member in rhs.
9
+
10
+ ## [0.0.7] - 2021-01-06
11
+ - Lox grammar reworked, initial AST classes created.
12
+
13
+ ## Added
14
+ - Class `Parser` this one generates AST's (Abstract Syntax Tree)
15
+ - Class `AST::ASTVisitor` draft initial implementation.
16
+ - Class `AST::BinaryExpr` draft initial implementation.
17
+ - Class `AST::LoxCompoundExpr` draft initial implementation.
18
+ - Class `AST::LiteralExpr` draft initial implementation.
19
+ - Class `AST::LoxNode` draft initial implementation.
20
+
21
+ ## Changed
22
+ - File `spec_helper.rb`: removed Bundler dependency
23
+ - Class `AST::ASTBuilder` added initial `reduce_` methods.
24
+ - File `README.md` Removed example with AST generation since this is in flux.
25
+
26
+ ## [0.0.6] - 2021-01-03
27
+ - First iteration of a parser with AST generation.
28
+
29
+ ## Added
30
+ - Class `Parser` this one generates AST's (Abstract Syntax Tree)
31
+ - Class `AST::ASTBuilder` default code to generate an AST.
32
+
33
+ ## Changed
34
+ - File `spec_helper.rb`: removed Bundler dependency
35
+ - File `README.md` Added example with AST visualization.
36
+
37
+ ## Fixed
38
+ - File `grammar.rb` ensure that the constant `Grammar` is created once only.
39
+
40
+ ## [0.0.5] - 2021-01-02
41
+ - Improved example in `README.md`, code re-styling to please `Rubocop` 1.7
42
+
43
+ ## Changed
44
+ - Code re-styling to please `Rubocop` 1.7
45
+ - File `README.md` Improved example with better parse tree visualization.
46
+
47
+ ## [0.0.4] - 2021-01-01
48
+ - A first parser implementation able to parse Lox source code.
49
+
50
+ ## Added
51
+ - Method `LXString::==` equality operator.
52
+
53
+ ## Changed
54
+ - class `Parser` renamed to `RawParser`
55
+ - File `README.md` Added an example showing the use of `RawParser` class.
56
+
1
57
  ## [0.0.3] - 2020-12-29
2
58
  - Scanner can recognize strings and nil tokens
3
59
  ### Added
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
@@ -9,12 +9,66 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
9
9
  - The implementation should be mature enough to run (LoxLox)[https://github.com/benhoyt/loxlox],
10
10
  a Lox interpreter written in Lox.
11
11
 
12
+ ## Current status
13
+ The __loxxy__ gem hosts two distinct parsers classes (`RawParser` and `Parser`).
14
+ - A `RawParser` instance is able to recognize valid Lox input and to generate
15
+ a concrete parse tree from it.
16
+ - A `Parser` instance can also parse Lox source code but will generate an AST
17
+ (Abstract Syntax Tree) that will be used by the future tree-walking interpreter.
18
+ Currently it generates AST for arithmetic expressions with literal numbers only.
19
+
12
20
  ## Roadmap
13
21
  - [DONE] Scanner (tokenizer)
14
- - [STARTED] Recognizer
15
- - [TODO] Parser
22
+ - [DONE] Raw parser. It parses Lox programs and generates a parse tree.
23
+ - [DONE] Tailored parser for generating AST (Abstract Syntax Tree)
24
+ - [STARTED] Custom AST builder class
25
+ - [STARTED] Hierarchy classes for representing Lox expressions in AST
16
26
  - [TODO] Interpreter or transpiler
17
27
 
28
+ ## Example using RawParser class
29
+ ```ruby
30
+ require 'loxxy'
31
+
32
+ lox_input = <<-LOX_END
33
+ // Your first Lox program!
34
+ print "Hello, world!";
35
+ LOX_END
36
+
37
+ # Show that the raw parser accepts the above program
38
+ base_parser = Loxxy::FrontEnd::RawParser.new
39
+
40
+ # Now parse the input into a concrete parse tree...
41
+ ptree = base_parser.parse(lox_input)
42
+
43
+ # Display the parse tree thanks to Rley formatters...
44
+ visitor = Rley::ParseTreeVisitor.new(ptree)
45
+ tree_formatter = Rley::Formatter::Asciitree.new($stdout)
46
+ tree_formatter.render(visitor)
47
+ ```
48
+
49
+ This is the output produced by the above example:
50
+ ```
51
+ program
52
+ +-- declaration_plus
53
+ | +-- declaration
54
+ | +-- statement
55
+ | +-- printStmt
56
+ | +-- PRINT: 'print'
57
+ | +-- expression
58
+ | | +-- assignment
59
+ | | +-- logic_or
60
+ | | +-- logic_and
61
+ | | +-- equality
62
+ | | +-- comparison
63
+ | | +-- term
64
+ | | +-- factor
65
+ | | +-- unary
66
+ | | +-- call
67
+ | | +-- primary
68
+ | | +-- STRING: '"Hello, world!"'
69
+ | +-- SEMICOLON: ';'
70
+ +-- EOF: ''
71
+ ```
18
72
 
19
73
  ## Installation
20
74
 
@@ -37,9 +91,9 @@ Or install it yourself as:
37
91
  TODO: Write usage instructions here
38
92
 
39
93
  ## Other Lox implementations in Ruby
40
- An impressive list of Lox implementations can be found [here](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations
94
+ An impressive list of Lox implementations can be found [here](https://github.com/munificent/craftinginterpreters/wiki/Lox-implementations)
41
95
 
42
- For Ruby, ther is the [lox](https://github.com/rdodson41/ruby-lox) gem.
96
+ For Ruby, there is the [lox](https://github.com/rdodson41/ruby-lox) gem.
43
97
  There are other Ruby-based projects as well:
44
98
  - [SlowLox](https://github.com/ArminKleinert/SlowLox), described as a "1-to-1 conversion of JLox to Ruby"
45
99
  - [rulox](https://github.com/LevitatingBusinessMan/rulox)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './loxxy/version'
3
+ require_relative 'loxxy/version'
4
+ require_relative 'loxxy/front_end/raw_parser'
5
+ require_relative 'loxxy/front_end/parser'
4
6
 
5
7
  module Loxxy
6
8
  class Error < StandardError; end
@@ -0,0 +1,138 @@
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
+ # Terminal2NodeClass = {
14
+ # 'FALSE' => Datatype::False,
15
+ # 'NIL' => Datatype::Nil,
16
+ # 'NUMBER' => Datatype::Number,
17
+ # 'STRING' => Datatype::LXString,
18
+ # 'TRUE' => Datatype::True
19
+ # }.freeze
20
+
21
+ attr_reader :strict
22
+
23
+ # Create a new AST builder instance.
24
+ # @param theTokens [Array<Rley::Lexical::Token>] The sequence of input tokens.
25
+ def initialize(theTokens)
26
+ super(theTokens)
27
+ @strict = false
28
+ end
29
+
30
+ protected
31
+
32
+ def terminal2node
33
+ Terminal2NodeClass
34
+ end
35
+
36
+ # Method override
37
+ def new_leaf_node(_production, _terminal, aTokenPosition, aToken)
38
+ Rley::PTree::TerminalNode.new(aToken, aTokenPosition)
39
+ end
40
+
41
+ # Factory method for creating a parent node object.
42
+ # @param aProduction [Production] Production rule
43
+ # @param aRange [Range] Range of tokens matched by the rule
44
+ # @param theTokens [Array] The input tokens
45
+ # @param theChildren [Array] Children nodes (one per rhs symbol)
46
+ def new_parent_node(aProduction, aRange, theTokens, theChildren)
47
+ mth_name = method_name(aProduction.name)
48
+ if respond_to?(mth_name, true)
49
+ node = send(mth_name, aProduction, aRange, theTokens, theChildren)
50
+ else
51
+ # Default action...
52
+ node = case aProduction.rhs.size
53
+ when 0
54
+ return_epsilon(aRange, theTokens, theChildren)
55
+ when 1
56
+ return_first_child(aRange, theTokens, theChildren)
57
+ else
58
+ if strict
59
+ msg = "Don't know production '#{aProduction.name}'"
60
+ raise StandardError, msg
61
+ else
62
+ node = Rley::PTree::NonTerminalNode.new(aProduction.lhs, aRange)
63
+ theChildren&.reverse_each do |child|
64
+ node.add_subnode(child) if child
65
+ end
66
+
67
+ node
68
+ end
69
+ end
70
+ end
71
+
72
+ node
73
+ end
74
+
75
+ # rule('term' => 'factor additive_plus')
76
+ def reduce_term_additive(_production, _range, tokens, theChildren)
77
+ operand1 = theChildren[0]
78
+
79
+ # Second child is anray with couples [operator, operand2]
80
+ theChildren[1].each do |(operator, operand2)|
81
+ operand1 = LoxBinaryExpr.new(tokens[0].position, operator, operand1, operand2)
82
+ end
83
+
84
+ operand1
85
+ end
86
+
87
+ # rule('additive_star' => 'additive_star additionOp factor').as 'additionOp_expr'
88
+ def reduce_additive_plus_more(_production, _range, _tokens, theChildren)
89
+ result = theChildren[0]
90
+ operator = theChildren[1].symbol.name == 'MINUS' ? :- : :+
91
+ operand2 = theChildren[2]
92
+ result << [operator, operand2]
93
+ end
94
+
95
+ # rule('additive_plus' => 'additionOp factor')
96
+ def reduce_additive_plus_end(_production, _range, _tokens, theChildren)
97
+ operator = theChildren[0].symbol.name == 'MINUS' ? :- : :+
98
+ operand2 = theChildren[1]
99
+ [[operator, operand2]]
100
+ end
101
+
102
+ # rule('factor' => 'multiplicative_plus')
103
+ def reduce_factor_multiplicative(_production, _range, tokens, theChildren)
104
+ operand1 = theChildren[0]
105
+
106
+ # Second child is anray with couples [operator, operand2]
107
+ theChildren[1].each do |(operator, operand2)|
108
+ operand1 = LoxBinaryExpr.new(tokens[0].position, operator, operand1, operand2)
109
+ end
110
+
111
+ operand1
112
+ end
113
+
114
+ # rule('multiplicative_plus' => 'multiplicative_plus multOp unary')
115
+ def reduce_multiplicative_plus_more(_production, _range, _tokens, theChildren)
116
+ result = theChildren[0]
117
+ operator = theChildren[1].symbol.name == 'SLASH' ? :/ : :*
118
+ operand2 = theChildren[2]
119
+ result << [operator, operand2]
120
+ end
121
+
122
+ # rule('multiplicative_plus' => 'multOp unary')
123
+ def reduce_multiplicative_plus_end(_production, _range, _tokens, theChildren)
124
+ operator = theChildren[0].symbol.name == 'SLASH' ? :/ : :*
125
+ operand2 = theChildren[1]
126
+ [[operator, operand2]]
127
+ end
128
+
129
+ # rule('primary' => 'FALSE' | TRUE').as 'literal_expr'
130
+ def reduce_literal_expr(_production, _range, _tokens, theChildren)
131
+ first_child = theChildren.first
132
+ pos = first_child.token.position
133
+ literal = first_child.token.value
134
+ LoxLiteralExpr.new(pos, literal)
135
+ end
136
+ end # class
137
+ end # module
138
+ 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