loxxy 0.0.3 → 0.0.4
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/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
|