loxxy 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +28 -2
- data/lib/loxxy.rb +2 -1
- data/lib/loxxy/datatype/lx_string.rb +14 -0
- data/lib/loxxy/front_end/{parser.rb → raw_parser.rb} +16 -1
- data/lib/loxxy/version.rb +1 -1
- data/spec/datatype/lx_string_spec.rb +35 -0
- data/spec/front_end/raw_parser_spec.rb +99 -0
- metadata +6 -4
- data/spec/front_end/parser_spec.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5383d6eb1725f5c53bb343e46d763f18d9d69f6b010241dbc4a93c18f949a6f
|
4
|
+
data.tar.gz: 286f213ab01ebec8061a9f25c86c6f28be064891880e06942a0d6d86b4210fcb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32d447c7dbe6593ad0002ad383a917561c82da22cbf70b4102d0eaa389cf92196e03ee14ab1cc160f530b1c96a1e259db8e7923e2de81322fc854898fa306d49
|
7
|
+
data.tar.gz: 245d4c9e255b7bb9d8602ab103367d7f83baf516db61f5a9192945ffa689027eb9f5f7a282e7052bca6b9e904cd6f6ed956d61ea949e02b7916f100033ebd9ec
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## [0.0.4] - 2021-01-01
|
2
|
+
- A first parser implementation able to parse Lox source code.
|
3
|
+
|
4
|
+
## Added
|
5
|
+
- Method `LXString::==` equality operator.
|
6
|
+
|
7
|
+
## Changed
|
8
|
+
- class `Parser` renamed to `RawParser`
|
9
|
+
- File `README.md` Added an example showing the use of `RawParser` class.
|
10
|
+
|
1
11
|
## [0.0.3] - 2020-12-29
|
2
12
|
- Scanner can recognize strings and nil tokens
|
3
13
|
### Added
|
data/README.md
CHANGED
@@ -11,11 +11,37 @@ a simple language used in Bob Nystrom's online book [Crafting Interpreters](http
|
|
11
11
|
|
12
12
|
## Roadmap
|
13
13
|
- [DONE] Scanner (tokenizer)
|
14
|
-
- [
|
15
|
-
- [TODO]
|
14
|
+
- [DONE] Raw parser. It parses Lox programs and generates a parse tree.
|
15
|
+
- [TODO] Tailored parser for generating AST (Abstract Syntax Tree)
|
16
16
|
- [TODO] Interpreter or transpiler
|
17
17
|
|
18
18
|
|
19
|
+
## Example
|
20
|
+
At this, stage, the raw parser is able to recognize Lox input and to generate
|
21
|
+
a concrete parse tree from it.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'loxxy'
|
25
|
+
|
26
|
+
lox_input = <<-LOX_END
|
27
|
+
// Your first Lox program!
|
28
|
+
print "Hello, world!";
|
29
|
+
LOX_END
|
30
|
+
|
31
|
+
# Show that the raw parser accepts the above program
|
32
|
+
base_parser = Loxxy::FrontEnd::RawParser.new
|
33
|
+
|
34
|
+
# Now parse the input into a concrete parse tree...
|
35
|
+
ptree = base_parser.parse(lox_input)
|
36
|
+
|
37
|
+
# Dump the parse tree contents...
|
38
|
+
p ptree # Lengthy (unwieldy) output
|
39
|
+
```
|
40
|
+
|
41
|
+
Soon, the `loxxy`will host another, tailored parser, that will generate
|
42
|
+
abstract syntax tree which much more convenient for further processing
|
43
|
+
(like implementing an interpreter).
|
44
|
+
|
19
45
|
## Installation
|
20
46
|
|
21
47
|
Add this line to your application's Gemfile:
|
data/lib/loxxy.rb
CHANGED
@@ -12,6 +12,20 @@ module Loxxy
|
|
12
12
|
super(aValue)
|
13
13
|
end
|
14
14
|
|
15
|
+
# Compare a Lox String with another Lox (or genuine Ruby) String
|
16
|
+
# @param other [Datatype::LxString, String]
|
17
|
+
# @return [Boolean]
|
18
|
+
def ==(other)
|
19
|
+
case other
|
20
|
+
when LXString
|
21
|
+
value == other.value
|
22
|
+
when String
|
23
|
+
value == other
|
24
|
+
else
|
25
|
+
raise StandardError, 'Cannot compare a #{self.class} with #{other.class}'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
15
29
|
protected
|
16
30
|
|
17
31
|
def validated_value(aValue)
|
@@ -5,7 +5,22 @@ require_relative 'grammar'
|
|
5
5
|
|
6
6
|
module Loxxy
|
7
7
|
module FrontEnd
|
8
|
-
|
8
|
+
# A Lox parser that produce concrete parse trees.
|
9
|
+
# Concrete parse trees are the default kind of parse tree
|
10
|
+
# generated by the Rley library.
|
11
|
+
# They consist of two node types only:
|
12
|
+
# - NonTerminalNode
|
13
|
+
# - TerminalNode
|
14
|
+
# A NonTerminalNode has zero or more child nodes (called subnodes)
|
15
|
+
# A TerminalNode is leaf node, that is, it has no child node.
|
16
|
+
# While concrete parse tree nodes can be generated out of the box,
|
17
|
+
# they have the following drawbacks:
|
18
|
+
# - Generic node classes that aren't always suited for the needs of
|
19
|
+
# the language being processing.
|
20
|
+
# - Concrete parse tree tend to be deeply nested, which may complicate
|
21
|
+
# further processing.
|
22
|
+
class RawParser
|
23
|
+
# @return [Rley::Engine] A facade object for the Rley parsing library
|
9
24
|
attr_reader(:engine)
|
10
25
|
|
11
26
|
def initialize
|
data/lib/loxxy/version.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
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/datatype/lx_string'
|
7
|
+
|
8
|
+
module Loxxy
|
9
|
+
module Datatype
|
10
|
+
describe LXString do
|
11
|
+
let(:sample_text) { 'some_text' }
|
12
|
+
subject { LXString.new(sample_text) }
|
13
|
+
|
14
|
+
context 'Initialization:' do
|
15
|
+
it 'should accept a String value at initialization' do
|
16
|
+
expect { LXString.new(sample_text) }.not_to raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should know its value' do
|
20
|
+
expect(subject.value).to eq(sample_text)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'Provided services:' do
|
25
|
+
it 'compares with another string' do
|
26
|
+
expect(subject).to eq(sample_text)
|
27
|
+
expect(subject).to eq(LXString.new(sample_text.dup))
|
28
|
+
|
29
|
+
expect(subject).not_to eq('')
|
30
|
+
expect(subject).not_to eq('other-text')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end # describe
|
34
|
+
end # module
|
35
|
+
end # module
|
@@ -0,0 +1,99 @@
|
|
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/raw_parser'
|
7
|
+
|
8
|
+
module Loxxy
|
9
|
+
module FrontEnd
|
10
|
+
describe RawParser do
|
11
|
+
subject { RawParser.new }
|
12
|
+
|
13
|
+
context 'Initialization:' do
|
14
|
+
it 'should be initialized without argument' do
|
15
|
+
expect { RawParser.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
|
+
(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
|
33
|
+
expect(eof).to be_kind_of(Rley::PTree::TerminalNode)
|
34
|
+
expect(eof.symbol.name).to eq('EOF')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should cope with an empty input' do
|
38
|
+
ptree = subject.parse('')
|
39
|
+
check_empty_input_result(ptree)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should cope with whitespaces only input' do
|
43
|
+
ptree = subject.parse(' ' * 80 + "\n" * 20)
|
44
|
+
check_empty_input_result(ptree)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should cope with comments only input' do
|
48
|
+
input = +''
|
49
|
+
%w[First Second Third].each do |ordinal|
|
50
|
+
input << "// #{ordinal} comment line\r\n"
|
51
|
+
end
|
52
|
+
ptree = subject.parse(input)
|
53
|
+
check_empty_input_result(ptree)
|
54
|
+
end
|
55
|
+
end # context
|
56
|
+
|
57
|
+
context 'Parsing expressions:' do
|
58
|
+
# Utility method to walk towards deeply nested node
|
59
|
+
# @param aNTNode [Rley::PTree::NonTerminalNode]
|
60
|
+
# @param subnodePath[Array<Integer>] An Array of subnode indices
|
61
|
+
def walk_subnodes(aNTNode, subnodePath)
|
62
|
+
curr_node = aNTNode
|
63
|
+
subnodePath.each do |index|
|
64
|
+
curr_node = curr_node.subnodes[index]
|
65
|
+
end
|
66
|
+
|
67
|
+
curr_node
|
68
|
+
end
|
69
|
+
it 'should parse a hello world program' do
|
70
|
+
program = <<-LOX_END
|
71
|
+
// Your first Lox program!
|
72
|
+
print "Hello, world!";
|
73
|
+
LOX_END
|
74
|
+
ptree = subject.parse(program)
|
75
|
+
root = ptree.root
|
76
|
+
expect(root.symbol.name).to eq('program')
|
77
|
+
(decls, eof) = root.subnodes
|
78
|
+
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]
|
81
|
+
expect(stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
|
82
|
+
expect(stmt.symbol.name).to eq('statement')
|
83
|
+
prnt_stmt = stmt.subnodes[0]
|
84
|
+
expect(prnt_stmt).to be_kind_of(Rley::PTree::NonTerminalNode)
|
85
|
+
expect(prnt_stmt.subnodes.size).to eq(3)
|
86
|
+
expect(prnt_stmt.subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
|
87
|
+
expect(prnt_stmt.subnodes[0].symbol.name).to eq('PRINT')
|
88
|
+
expect(prnt_stmt.subnodes[1]).to be_kind_of(Rley::PTree::NonTerminalNode)
|
89
|
+
leaf_node = walk_subnodes(prnt_stmt, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
90
|
+
expect(leaf_node).to be_kind_of(Rley::PTree::TerminalNode)
|
91
|
+
expect(leaf_node.symbol.name).to eq('STRING')
|
92
|
+
expect(leaf_node.token.value).to eq('Hello, world!')
|
93
|
+
expect(prnt_stmt.subnodes[2]).to be_kind_of(Rley::PTree::TerminalNode)
|
94
|
+
expect(prnt_stmt.subnodes[2].symbol.name).to eq('SEMICOLON')
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end # describe
|
98
|
+
end # module
|
99
|
+
end # module
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
@@ -93,11 +93,12 @@ files:
|
|
93
93
|
- lib/loxxy/datatype/true.rb
|
94
94
|
- lib/loxxy/front_end/grammar.rb
|
95
95
|
- lib/loxxy/front_end/literal.rb
|
96
|
-
- lib/loxxy/front_end/
|
96
|
+
- lib/loxxy/front_end/raw_parser.rb
|
97
97
|
- lib/loxxy/front_end/scanner.rb
|
98
98
|
- lib/loxxy/version.rb
|
99
99
|
- loxxy.gemspec
|
100
|
-
- spec/
|
100
|
+
- spec/datatype/lx_string_spec.rb
|
101
|
+
- spec/front_end/raw_parser_spec.rb
|
101
102
|
- spec/front_end/scanner_spec.rb
|
102
103
|
- spec/loxxy_spec.rb
|
103
104
|
- spec/spec_helper.rb
|
@@ -126,6 +127,7 @@ signing_key:
|
|
126
127
|
specification_version: 4
|
127
128
|
summary: An implementation of the Lox programming language. WIP
|
128
129
|
test_files:
|
129
|
-
- spec/
|
130
|
+
- spec/datatype/lx_string_spec.rb
|
131
|
+
- spec/front_end/raw_parser_spec.rb
|
130
132
|
- spec/front_end/scanner_spec.rb
|
131
133
|
- spec/loxxy_spec.rb
|
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../spec_helper' # Use the RSpec framework
|
4
|
-
require_relative '../../lib/loxxy/front_end/parser' # Load the class under test
|
5
|
-
|
6
|
-
module Loxxy
|
7
|
-
module FrontEnd
|
8
|
-
describe Parser do
|
9
|
-
subject { Parser.new }
|
10
|
-
|
11
|
-
context 'Initialization:' do
|
12
|
-
it 'should be initialized without argument' do
|
13
|
-
expect { Parser.new }.not_to raise_error
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'should have its parse engine initialized' do
|
17
|
-
expect(subject.engine).to be_kind_of(Rley::Engine)
|
18
|
-
end
|
19
|
-
end # context
|
20
|
-
|
21
|
-
context 'Parsing blank files:' do
|
22
|
-
def check_empty_input_result(aParseTree)
|
23
|
-
# Parse results MUST to comply to grammar rule:
|
24
|
-
# program => declaration_star EOF
|
25
|
-
# where the declaration_star MUST be empty
|
26
|
-
expect(aParseTree.root.symbol.name).to eq('program')
|
27
|
-
(decls, eof) = aParseTree.root.subnodes
|
28
|
-
expect(decls).to be_kind_of(Rley::PTree::NonTerminalNode)
|
29
|
-
expect(decls.symbol.name).to eq('declaration_star')
|
30
|
-
expect(decls.subnodes).to be_empty
|
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
|
-
end # describe
|
55
|
-
end # module
|
56
|
-
end # module
|