loxxy 0.0.5 → 0.0.6

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: de9d3c24db15863c6ea99c2a9c8a2e413b2a243ddf79de28da5583d1944fc4e4
4
+ data.tar.gz: a5f6dacfd67b6833e3c051273765e1d94c95d1d1700a974a09a3caf0519a1570
5
5
  SHA512:
6
- metadata.gz: 44e0049b0bc01e5d35bed7b91c32a7a3b977e6ba4e806439958bad2dc8aae7ead017a310c8523bd4cb628c14951946981bee42f4001d68b701159fcdaff53419
7
- data.tar.gz: c90236a82f0a4872b209b8462d5f13377815fd4fc41babf256ff4894ea6900e343c6f192194e1b713cb9f8a8d2bffc326e7ea4f00726193e0a792dc9e1457e41
6
+ metadata.gz: 3c4bd62b1a9cd960ee97d807a5f44a26cc360df4344975210ca967117c62585a2138fef382ffb5ea7d79f002b9a9a951ba77622cb8d799973c0ad8a24ffbae52
7
+ data.tar.gz: 5b14b5c4a6ae98f5e1d9a819160393393654cc3d817e36f1da68cd12fcfedfbce47097463dc6babcf36b5582e970ea41b4cef5ebceb768b962c8bd7b02bcb553
@@ -1,3 +1,17 @@
1
+ ## [0.0.6] - 2021-01-03
2
+ - First iteration of a parser with AST generation.
3
+
4
+ ## Added
5
+ - Class `Parser` this one generates AST's (Abstract Syntax Tree)
6
+ - Class `AST::ASTBuilder` default code to generate an AST.
7
+
8
+ ## Changed
9
+ - File `spec_helper.rb`: removed Bundler dependency
10
+ - File `README.md` Added example with AST visualization.
11
+
12
+ ## Fixed
13
+ - File `grammar.rb` ensure that the constant `Grammar` is created once only.
14
+
1
15
  ## [0.0.5] - 2021-01-02
2
16
  - Improved example in `README.md`, code re-styling to please `Rubocop` 1.7
3
17
 
data/README.md CHANGED
@@ -12,16 +12,19 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
12
12
  ## Roadmap
13
13
  - [DONE] Scanner (tokenizer)
14
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
15
+ - [DONE] Tailored parser for generating AST (Abstract Syntax Tree)
16
+ - [STARTED] Custom AST builder class
17
17
  - [TODO] Hierarchy classes for representing Lox expressions in AST
18
18
  - [TODO] Interpreter or transpiler
19
19
 
20
-
21
20
  ## Example
22
- At this, stage, the raw parser is able to recognize Lox input and to generate
21
+ The __loxxy__ hosts two distinct parsers classes (`RawParser` and `Parser`).
22
+ - A `RawParser` instance is able to recognize Lox input and to generate
23
23
  a concrete parse tree from it.
24
+ - A `Parser` instance can also parse Lox source code but will generate an AST
25
+ (Abstract Syntax Tree) that will be used by the future tree-walking interpreter.
24
26
 
27
+ ### Example using RawParser class
25
28
  ```ruby
26
29
  require 'loxxy'
27
30
 
@@ -74,9 +77,50 @@ program
74
77
  +-- EOF: ''
75
78
  ```
76
79
 
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
+ ### 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
+
80
124
 
81
125
  ## Installation
82
126
 
@@ -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,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../datatype/all_datatypes'
4
+
5
+ module Loxxy
6
+ module Ast
7
+ # The purpose of ASTBuilder is to build piece by piece an AST
8
+ # (Abstract Syntax Tree) from a sequence of input tokens and
9
+ # visit events produced by walking over a GFGParsing object.
10
+ class ASTBuilder < Rley::ParseRep::ASTBaseBuilder
11
+ # Terminal2NodeClass = {
12
+ # 'FALSE' => Datatype::False,
13
+ # 'NIL' => Datatype::Nil,
14
+ # 'NUMBER' => Datatype::Number,
15
+ # 'STRING' => Datatype::LXString,
16
+ # 'TRUE' => Datatype::True
17
+ # }.freeze
18
+
19
+ attr_reader :strict
20
+
21
+ # Create a new AST builder instance.
22
+ # @param theTokens [Array<Rley::Lexical::Token>] The sequence of input tokens.
23
+ def initialize(theTokens)
24
+ super(theTokens)
25
+ @strict = false
26
+ end
27
+
28
+ protected
29
+
30
+ def terminal2node
31
+ Terminal2NodeClass
32
+ end
33
+
34
+ # Method override
35
+ def new_leaf_node(_production, _terminal, aTokenPosition, aToken)
36
+ Rley::PTree::TerminalNode.new(aToken, aTokenPosition)
37
+ end
38
+
39
+ # Factory method for creating a parent node object.
40
+ # @param aProduction [Production] Production rule
41
+ # @param aRange [Range] Range of tokens matched by the rule
42
+ # @param theTokens [Array] The input tokens
43
+ # @param theChildren [Array] Children nodes (one per rhs symbol)
44
+ def new_parent_node(aProduction, aRange, theTokens, theChildren)
45
+ mth_name = method_name(aProduction.name)
46
+ if respond_to?(mth_name, true)
47
+ node = send(mth_name, aProduction, aRange, theTokens, theChildren)
48
+ else
49
+ # Default action...
50
+ node = case aProduction.rhs.size
51
+ when 0
52
+ return_epsilon(aRange, theTokens, theChildren)
53
+ when 1
54
+ return_first_child(aRange, theTokens, theChildren)
55
+ else
56
+ if strict
57
+ msg = "Don't know production '#{aProduction.name}'"
58
+ raise StandardError, msg
59
+ else
60
+ node = Rley::PTree::NonTerminalNode.new(aProduction.lhs, aRange)
61
+ theChildren&.reverse_each do |child|
62
+ node.add_subnode(child) if child
63
+ end
64
+
65
+ node
66
+ end
67
+ end
68
+ end
69
+
70
+ node
71
+ end
72
+ end # class
73
+ end # module
74
+ end # module
@@ -144,8 +144,10 @@ module Loxxy
144
144
  rule('arguments' => 'expression')
145
145
  end
146
146
 
147
- # And now build the grammar and make it accessible via a constant
148
- # @return [Rley::Syntax::Grammar]
149
- Grammar = builder.grammar
147
+ unless defined?(Grammar)
148
+ # And now build the grammar and make it accessible via a constant
149
+ # @return [Rley::Syntax::Grammar]
150
+ Grammar = builder.grammar
151
+ end
150
152
  end # module
151
153
  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.6'
5
5
  end
@@ -0,0 +1,96 @@
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
+ context 'Initialization:' do
14
+ it 'should be initialized without argument' do
15
+ expect { Parser.new }.not_to raise_error
16
+ end
17
+
18
+ it 'should have its parse engine initialized' do
19
+ expect(subject.engine).to be_kind_of(Rley::Engine)
20
+ end
21
+ end # context
22
+
23
+ context 'Parsing blank files:' do
24
+ def check_empty_input_result(aParseTree)
25
+ # Parse results MUST to comply to grammar rule:
26
+ # program => declaration_star EOF
27
+ # where the declaration_star MUST be empty
28
+ expect(aParseTree.root.symbol.name).to eq('program')
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')
33
+ end
34
+
35
+ it 'should cope with an empty input' do
36
+ ptree = subject.parse('')
37
+ check_empty_input_result(ptree)
38
+ end
39
+
40
+ it 'should cope with whitespaces only input' do
41
+ ptree = subject.parse(' ' * 80 + "\n" * 20)
42
+ check_empty_input_result(ptree)
43
+ end
44
+
45
+ it 'should cope with comments only input' do
46
+ input = +''
47
+ %w[First Second Third].each do |ordinal|
48
+ input << "// #{ordinal} comment line\r\n"
49
+ end
50
+ ptree = subject.parse(input)
51
+ check_empty_input_result(ptree)
52
+ end
53
+ end # context
54
+
55
+ context 'Parsing expressions:' do
56
+ # Utility method to walk towards deeply nested node
57
+ # @param aNTNode [Rley::PTree::NonTerminalNode]
58
+ # @param subnodePath[Array<Integer>] An Array of subnode indices
59
+ def walk_subnodes(aNTNode, subnodePath)
60
+ curr_node = aNTNode
61
+ subnodePath.each do |index|
62
+ curr_node = curr_node.subnodes[index]
63
+ end
64
+
65
+ curr_node
66
+ end
67
+ it 'should parse a hello world program' do
68
+ program = <<-LOX_END
69
+ // Your first Lox program!
70
+ print "Hello, world!";
71
+ LOX_END
72
+ ptree = subject.parse(program)
73
+ root = ptree.root
74
+ expect(root.symbol.name).to eq('program')
75
+ (decls, eof) = root.subnodes
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]
79
+ expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
80
+ expect(prnt_stmt.subnodes.size).to eq(3)
81
+ expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
82
+ expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
83
+ expect(prnt_stmt.subnodes[1]).to be_kind_of(Rley::PTree::NonTerminalNode)
84
+ # leaf_node = walk_subnodes(prnt_stmt, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
85
+ # expect(leaf_node).to be_kind_of(Rley::PTree::TerminalNode)
86
+ # expect(leaf_node.symbol.name).to eq('STRING')
87
+ # expect(leaf_node.token.value).to eq('Hello, world!')
88
+ expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
89
+ expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
90
+ expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
91
+ expect(eof.symbol.name).to eq('EOF')
92
+ end
93
+ end
94
+ end # describe
95
+ end # module
96
+ 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.6
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-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -84,6 +84,7 @@ files:
84
84
  - README.md
85
85
  - Rakefile
86
86
  - lib/loxxy.rb
87
+ - lib/loxxy/ast/ast_builder.rb
87
88
  - lib/loxxy/datatype/all_datatypes.rb
88
89
  - lib/loxxy/datatype/boolean.rb
89
90
  - lib/loxxy/datatype/builtin_datatype.rb
@@ -94,11 +95,13 @@ files:
94
95
  - lib/loxxy/datatype/true.rb
95
96
  - lib/loxxy/front_end/grammar.rb
96
97
  - lib/loxxy/front_end/literal.rb
98
+ - lib/loxxy/front_end/parser.rb
97
99
  - lib/loxxy/front_end/raw_parser.rb
98
100
  - lib/loxxy/front_end/scanner.rb
99
101
  - lib/loxxy/version.rb
100
102
  - loxxy.gemspec
101
103
  - spec/datatype/lx_string_spec.rb
104
+ - spec/front_end/parser_spec.rb
102
105
  - spec/front_end/raw_parser_spec.rb
103
106
  - spec/front_end/scanner_spec.rb
104
107
  - spec/loxxy_spec.rb
@@ -129,6 +132,7 @@ specification_version: 4
129
132
  summary: An implementation of the Lox programming language. WIP
130
133
  test_files:
131
134
  - spec/datatype/lx_string_spec.rb
135
+ - spec/front_end/parser_spec.rb
132
136
  - spec/front_end/raw_parser_spec.rb
133
137
  - spec/front_end/scanner_spec.rb
134
138
  - spec/loxxy_spec.rb