rley 0.4.03 → 0.4.04
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 +7 -0
- data/examples/data_formats/JSON/JSON_demo.rb +20 -26
- data/examples/data_formats/JSON/JSON_grammar.rb +5 -5
- data/examples/data_formats/JSON/JSON_parser.rb +1 -1
- data/examples/data_formats/JSON/cli_options.rb +65 -0
- data/lib/rley.rb +1 -1
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/formatter/asciitree.rb +128 -0
- data/spec/rley/formatter/asciitree_spec.rb +95 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5100723b47c2572c2ac0b5996413eb4cd8e462ba
|
4
|
+
data.tar.gz: 4d37be9ca7128a36f77505560c7214454e413eb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c197b1b962408e3a3ce4be5d310c227bdc638028d9a40d26b89c544232f59a8ce994228e4ae3673cbe6147dbcd520ee53c36a0620e3c7871edd3aba46762933f
|
7
|
+
data.tar.gz: 21b59e0b9b5c942be83954b35ee15650e1bc2c2aa2f9a6f1ebba645b94e3f822df510026825354512cf7eea879431feed852f34cef9fd176c5d0b89377504a46
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,15 @@
|
|
1
|
+
### 0.4.04 / 2017-05-01
|
2
|
+
* [NEW] `Asciitree` formatter class. Allows parse tree output in simple printable text.
|
3
|
+
* [CHANGE] Major enhancements in directory `examples\data_formats\JSON`. The demo command-line tool parses JSON and outputs the parse tree in one of the supported formats.
|
4
|
+
|
1
5
|
### 0.4.03 / 2017-04-17
|
2
6
|
* [FIX] File `rley.rb` reference to obsolete `EarleyParser` class removed.
|
3
7
|
* [NEW] `BracketNotation` formatter class. Allows parse tree output in Labelled Bracket Notation.
|
4
8
|
* [CHANGE] Code refactoring in directory `examples\data_formats\JSON`. The demo command-line tool parses JSON and converts it into LBN (Labelled Bracket Notation). There are two diagrams (in SVG format) generated from the LBN output.
|
5
9
|
|
10
|
+
### 0.4.02 / 2017-04-09
|
11
|
+
* [NEW] Module re-organization for clearer dependencies: Classes `Token` and `TokenRange` are moved to a separate module `Tokens`.
|
12
|
+
* [CHANGE] Code, specs, examples and `README.md` adapted to reflect the module re-organization.
|
6
13
|
|
7
14
|
### 0.4.01 / 2016-12-21
|
8
15
|
* [NEW] File `appveyor.yml`. Add AppVeyor CI to Github commits. AppVeyor complements Travis by running builds under Windows OS.
|
@@ -1,32 +1,23 @@
|
|
1
|
+
require_relative 'cli_options'
|
1
2
|
require_relative 'JSON_parser'
|
2
3
|
|
3
|
-
|
4
|
-
|
4
|
+
prog_name = 'JSON_demo'
|
5
|
+
prog_version = '0.1.0'
|
5
6
|
|
6
|
-
|
7
|
+
cli_options = CLIOptions.new(prog_name, prog_version, ARGV)
|
7
8
|
if ARGV.empty?
|
8
|
-
|
9
|
-
|
10
|
-
Use online tools (e.g. http://yohasebe.com/rsyntaxtree/) to visualize
|
11
|
-
parse trees from LBN output.
|
12
|
-
|
13
|
-
Command-line syntax:
|
14
|
-
|
15
|
-
ruby #{__FILE__} filename
|
16
|
-
where:
|
17
|
-
filename is the name of a JSON file
|
18
|
-
|
19
|
-
Example:
|
20
|
-
ruby #{__FILE__} sample01.json
|
21
|
-
END_MSG
|
22
|
-
puts msg
|
9
|
+
puts 'Missing input file name.'
|
10
|
+
puts 'Use -h option for command-line help.'
|
23
11
|
exit(1)
|
24
12
|
end
|
13
|
+
|
25
14
|
file_name = ARGV[0]
|
15
|
+
# Create a JSON parser object
|
16
|
+
parser = JSONParser.new
|
26
17
|
result = parser.parse_file(file_name) # result object contains parse details
|
27
18
|
|
28
19
|
unless result.success?
|
29
|
-
# Stop if
|
20
|
+
# Stop if parse failed...
|
30
21
|
puts "Parsing of '#{file_name}' failed"
|
31
22
|
puts result.failure_reason.message
|
32
23
|
exit(1)
|
@@ -34,15 +25,18 @@ end
|
|
34
25
|
|
35
26
|
# Generate a parse tree from the parse result
|
36
27
|
ptree = result.parse_tree
|
37
|
-
|
38
|
-
|
28
|
+
|
29
|
+
# Select the output format
|
30
|
+
case cli_options[:format]
|
31
|
+
when :ascii_tree
|
32
|
+
renderer = Rley::Formatter::Asciitree.new($stdout)
|
33
|
+
when :labelled
|
34
|
+
renderer = Rley::Formatter::BracketNotation.new($stdout)
|
35
|
+
end
|
39
36
|
|
40
37
|
# Let's create a parse tree visitor
|
41
38
|
visitor = Rley::ParseTreeVisitor.new(ptree)
|
42
39
|
|
43
|
-
#
|
44
|
-
|
45
|
-
use_notation.render(visitor)
|
46
|
-
|
47
|
-
|
40
|
+
# Now output formatted parse tree
|
41
|
+
renderer.render(visitor)
|
48
42
|
# End of file
|
@@ -4,7 +4,7 @@ require 'rley' # Load the gem
|
|
4
4
|
|
5
5
|
########################################
|
6
6
|
# Define a grammar for JSON
|
7
|
-
# Original JSON grammar is available http://www.json.org/fatfree.html
|
7
|
+
# Original JSON grammar is available at: http://www.json.org/fatfree.html
|
8
8
|
# Official JSON grammar: http://rfc7159.net/rfc7159#rfc.section.2
|
9
9
|
# Names of grammar elements are based on the RFC 7159 documentation
|
10
10
|
builder = Rley::Syntax::GrammarBuilder.new do
|
@@ -27,11 +27,11 @@ builder = Rley::Syntax::GrammarBuilder.new do
|
|
27
27
|
rule 'member-list' => %w(member-list value-separator member)
|
28
28
|
rule 'member-list' => 'member'
|
29
29
|
rule 'member' => %w(string name-separator value)
|
30
|
-
rule 'array' => %w(begin-array
|
30
|
+
rule 'array' => %w(begin-array array-items end-array)
|
31
31
|
rule 'array' => %w(begin-array end-array)
|
32
|
-
rule '
|
33
|
-
rule '
|
32
|
+
rule 'array-items' => %w(array-items value-separator value)
|
33
|
+
rule 'array-items' => %w(value)
|
34
34
|
end
|
35
35
|
|
36
|
-
# And now build the grammar...
|
36
|
+
# And now build the JSON grammar...
|
37
37
|
GrammarJSON = builder.grammar
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
# A Hash specialization that collects the command-line options
|
4
|
+
class CLIOptions < Hash
|
5
|
+
#labelled square notation (LBN).
|
6
|
+
#Use online tools (e.g. http://yohasebe.com/rsyntaxtree/) to visualize
|
7
|
+
#parse trees from LBN output.
|
8
|
+
|
9
|
+
def initialize(progName, progVersion, args)
|
10
|
+
super()
|
11
|
+
|
12
|
+
# Default values
|
13
|
+
self[:prog_name] = progName
|
14
|
+
self[:prog_version] = progVersion
|
15
|
+
self[:format] = :ascii_tree
|
16
|
+
|
17
|
+
options = build_option_parser
|
18
|
+
options.parse!(args)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def build_option_parser
|
24
|
+
OptionParser.new do |opts|
|
25
|
+
opts.banner = <<-END_BANNER
|
26
|
+
#{self[:prog_name]}: a demo utility that parses a JSON file
|
27
|
+
and renders its parse tree to the standard output
|
28
|
+
in the format specified in the command-line.
|
29
|
+
|
30
|
+
Usage: JSON_demo.rb [options] FILE
|
31
|
+
|
32
|
+
Examples:
|
33
|
+
JSON_demo --format ascii_tree sample01.jon
|
34
|
+
END_BANNER
|
35
|
+
|
36
|
+
opts.separator ''
|
37
|
+
|
38
|
+
format_help = <<-END_TEXT
|
39
|
+
Select the output format (default: ascii_tree). Available formats:
|
40
|
+
ascii_tree Simple text representation of parse trees
|
41
|
+
labelled Labelled square notation (LBN)
|
42
|
+
Use online tools (e.g. http://yohasebe.com/rsyntaxtree/)
|
43
|
+
to visualize parse trees from LBN output.
|
44
|
+
END_TEXT
|
45
|
+
formats = %i(ascii_tree labelled)
|
46
|
+
opts.on('-f', '--format FORMAT', formats, format_help) do |frm|
|
47
|
+
self[:format] = frm
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.separator ''
|
51
|
+
opts.separator ' **** Utility ****'
|
52
|
+
|
53
|
+
opts.on('-v', '--version', 'Display the program version.') do
|
54
|
+
puts self[:prog_version]
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
|
58
|
+
# No argument, shows at tail. This will print an options summary.
|
59
|
+
opts.on_tail('-h', '--help', 'Display this help message.') do
|
60
|
+
puts opts
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end # class
|
data/lib/rley.rb
CHANGED
@@ -9,7 +9,7 @@ require_relative './rley/parser/gfg_earley_parser'
|
|
9
9
|
require_relative './rley/parse_tree_visitor'
|
10
10
|
require_relative './rley/formatter/debug'
|
11
11
|
require_relative './rley/formatter/json'
|
12
|
-
require_relative './rley/formatter/
|
12
|
+
require_relative './rley/formatter/asciitree'
|
13
13
|
require_relative './rley/formatter/bracket_notation'
|
14
14
|
|
15
15
|
# End of file
|
data/lib/rley/constants.rb
CHANGED
@@ -0,0 +1,128 @@
|
|
1
|
+
require_relative 'base_formatter'
|
2
|
+
|
3
|
+
|
4
|
+
module Rley # This module is used as a namespace
|
5
|
+
# Namespace dedicated to parse tree formatters.
|
6
|
+
module Formatter
|
7
|
+
# A formatter class that draws parse trees by using characters
|
8
|
+
class Asciitree < BaseFormatter
|
9
|
+
# TODO
|
10
|
+
attr_reader(:curr_path)
|
11
|
+
|
12
|
+
# For each node in curr_path, there is a corresponding string value.
|
13
|
+
# Allowed string values are: 'first', 'last', 'first_and_last', 'other'
|
14
|
+
attr_reader(:ranks)
|
15
|
+
|
16
|
+
attr_reader(:nesting_prefix)
|
17
|
+
|
18
|
+
attr_reader(:blank_indent)
|
19
|
+
|
20
|
+
attr_reader(:continuation_indent)
|
21
|
+
|
22
|
+
# Constructor.
|
23
|
+
# @param anIO [IO] The output stream to which the rendered grammar
|
24
|
+
# is written.
|
25
|
+
def initialize(anIO)
|
26
|
+
super(anIO)
|
27
|
+
@curr_path = []
|
28
|
+
@ranks = []
|
29
|
+
|
30
|
+
@nesting_prefix = '+-- '
|
31
|
+
@blank_indent = ' '
|
32
|
+
@continuation_indent = '| '
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Method called by a ParseTreeVisitor to which the formatter subscribed.
|
37
|
+
# Notification of a visit event: the visitor is about to visit
|
38
|
+
# the children of a non-terminal node
|
39
|
+
# @param _parent [NonTerminalNode]
|
40
|
+
# @param _children [Array] array of children nodes
|
41
|
+
def before_subnodes(parent, children)
|
42
|
+
rank_of(parent)
|
43
|
+
curr_path << parent
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Method called by a ParseTreeVisitor to which the formatter subscribed.
|
48
|
+
# Notification of a visit event: the visitor is about to visit
|
49
|
+
# a non-terminal node
|
50
|
+
# @param nonterm [NonTerminalNode]
|
51
|
+
def before_non_terminal(aNonTerm)
|
52
|
+
emit(aNonTerm)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Method called by a ParseTreeVisitor to which the formatter subscribed.
|
57
|
+
# Notification of a visit event: the visitor is about to visit
|
58
|
+
# a terminal node
|
59
|
+
# @param _term [TerminalNode]
|
60
|
+
def before_terminal(aTerm)
|
61
|
+
emit(aTerm, ": '#{aTerm.token.lexeme}'")
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Method called by a ParseTreeVisitor to which the formatter subscribed.
|
66
|
+
# Notification of a visit event: the visitor completed the visit of
|
67
|
+
# the children of a non-terminal node.
|
68
|
+
# @param _parent [NonTerminalNode]
|
69
|
+
# @param _children [Array] array of children nodes
|
70
|
+
def after_subnodes(_parent, _children)
|
71
|
+
curr_path.pop
|
72
|
+
ranks.pop
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Parent node is last node in current path
|
78
|
+
# or current path is empty (then aChild is root node)
|
79
|
+
def rank_of(aChild)
|
80
|
+
if curr_path.empty?
|
81
|
+
rank = 'root'
|
82
|
+
elsif curr_path[-1].subnodes.size == 1
|
83
|
+
rank = 'first_and_last'
|
84
|
+
else
|
85
|
+
parent = curr_path[-1]
|
86
|
+
siblings = parent.subnodes
|
87
|
+
siblings_last_index = siblings.size - 1
|
88
|
+
rank = case siblings.find_index(aChild)
|
89
|
+
when 0 then 'first'
|
90
|
+
when siblings_last_index then 'last'
|
91
|
+
else
|
92
|
+
'other'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
self.ranks << rank
|
96
|
+
end
|
97
|
+
|
98
|
+
# 'root', 'first', 'first_and_last', 'last', 'other'
|
99
|
+
def path_prefix()
|
100
|
+
return '' if ranks.empty?
|
101
|
+
|
102
|
+
prefix = ''
|
103
|
+
@ranks.each_with_index do |rank, i|
|
104
|
+
next if i == 0
|
105
|
+
|
106
|
+
case rank
|
107
|
+
when 'first', 'other'
|
108
|
+
prefix << continuation_indent
|
109
|
+
|
110
|
+
when 'last', 'first_and_last', 'root'
|
111
|
+
prefix << blank_indent
|
112
|
+
else
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
prefix << nesting_prefix
|
117
|
+
return prefix
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
def emit(aNode, aSuffix = '')
|
122
|
+
output.puts("#{path_prefix}#{aNode.symbol.name}#{aSuffix}")
|
123
|
+
end
|
124
|
+
end # class
|
125
|
+
end # module
|
126
|
+
end # module
|
127
|
+
|
128
|
+
# End of file
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require_relative '../support/grammar_abc_helper'
|
5
|
+
require_relative '../../../lib/rley/tokens/token'
|
6
|
+
require_relative '../../../lib/rley/parser/gfg_earley_parser'
|
7
|
+
require_relative '../../../lib/rley/ptree/parse_tree'
|
8
|
+
require_relative '../../../lib/rley/parse_tree_visitor'
|
9
|
+
# Load the class under test
|
10
|
+
require_relative '../../../lib/rley/formatter/asciitree'
|
11
|
+
|
12
|
+
module Rley # Re-open the module to get rid of qualified names
|
13
|
+
module Formatter
|
14
|
+
describe Asciitree do
|
15
|
+
include GrammarABCHelper # Mix-in module for grammar abc
|
16
|
+
|
17
|
+
# Factory method. Build a production with the given sequence
|
18
|
+
# of symbols as its rhs.
|
19
|
+
let(:grammar_abc) do
|
20
|
+
builder = grammar_abc_builder
|
21
|
+
builder.grammar
|
22
|
+
end
|
23
|
+
|
24
|
+
# Variables for the terminal symbols
|
25
|
+
let(:a_) { grammar_abc.name2symbol['a'] }
|
26
|
+
let(:b_) { grammar_abc.name2symbol['b'] }
|
27
|
+
let(:c_) { grammar_abc.name2symbol['c'] }
|
28
|
+
|
29
|
+
# Helper method that mimicks the output of a tokenizer
|
30
|
+
# for the language specified by gramma_abc
|
31
|
+
let(:grm_abc_tokens1) do
|
32
|
+
[
|
33
|
+
Tokens::Token.new('a', a_),
|
34
|
+
Tokens::Token.new('a', a_),
|
35
|
+
Tokens::Token.new('b', b_),
|
36
|
+
Tokens::Token.new('c', c_),
|
37
|
+
Tokens::Token.new('c', c_)
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Factory method that builds a sample parse tree.
|
42
|
+
# Generated tree has the following structure:
|
43
|
+
# S[0,5]
|
44
|
+
# +- A[0,5]
|
45
|
+
# +- a[0,0]
|
46
|
+
# +- A[1,4]
|
47
|
+
# | +- a[1,1]
|
48
|
+
# | +- A[2,3]
|
49
|
+
# | | +- b[2,3]
|
50
|
+
# | +- c[3,4]
|
51
|
+
# +- c[4,5]
|
52
|
+
# Capital letters represent non-terminal nodes
|
53
|
+
let(:grm_abc_ptree1) do
|
54
|
+
parser = Parser::GFGEarleyParser.new(grammar_abc)
|
55
|
+
parse_result = parser.parse(grm_abc_tokens1)
|
56
|
+
parse_result.parse_tree
|
57
|
+
end
|
58
|
+
|
59
|
+
let(:destination) { StringIO.new('', 'w') }
|
60
|
+
subject { Asciitree.new(destination) }
|
61
|
+
|
62
|
+
context 'Standard creation & initialization:' do
|
63
|
+
it 'should be initialized with an IO argument' do
|
64
|
+
expect { Asciitree.new(StringIO.new('', 'w')) }.not_to raise_error
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should know its output destination' do
|
68
|
+
expect(subject.output).to eq(destination)
|
69
|
+
end
|
70
|
+
end # context
|
71
|
+
|
72
|
+
|
73
|
+
context 'Rendering:' do
|
74
|
+
it 'should render a parse tree' do
|
75
|
+
visitor = Rley::ParseTreeVisitor.new(grm_abc_ptree1)
|
76
|
+
subject.render(visitor)
|
77
|
+
expectations = <<-SNIPPET
|
78
|
+
S
|
79
|
+
+-- A
|
80
|
+
+-- a: 'a'
|
81
|
+
+-- A
|
82
|
+
| +-- a: 'a'
|
83
|
+
| +-- A
|
84
|
+
| | +-- b: 'b'
|
85
|
+
| +-- c: 'c'
|
86
|
+
+-- c: 'c'
|
87
|
+
SNIPPET
|
88
|
+
expect(destination.string).to eq(expectations)
|
89
|
+
end
|
90
|
+
end # context
|
91
|
+
end # describe
|
92
|
+
end # module
|
93
|
+
end # module
|
94
|
+
|
95
|
+
# End of file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rley
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.04
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -135,12 +135,14 @@ files:
|
|
135
135
|
- examples/data_formats/JSON/JSON_grammar.rb
|
136
136
|
- examples/data_formats/JSON/JSON_lexer.rb
|
137
137
|
- examples/data_formats/JSON/JSON_parser.rb
|
138
|
+
- examples/data_formats/JSON/cli_options.rb
|
138
139
|
- examples/general/calc/calc_demo.rb
|
139
140
|
- examples/general/calc/calc_grammar.rb
|
140
141
|
- examples/general/calc/calc_lexer.rb
|
141
142
|
- examples/general/calc/calc_parser.rb
|
142
143
|
- lib/rley.rb
|
143
144
|
- lib/rley/constants.rb
|
145
|
+
- lib/rley/formatter/asciitree.rb
|
144
146
|
- lib/rley/formatter/base_formatter.rb
|
145
147
|
- lib/rley/formatter/bracket_notation.rb
|
146
148
|
- lib/rley/formatter/debug.rb
|
@@ -202,6 +204,7 @@ files:
|
|
202
204
|
- lib/rley/syntax/verbatim_symbol.rb
|
203
205
|
- lib/rley/tokens/token.rb
|
204
206
|
- lib/rley/tokens/token_range.rb
|
207
|
+
- spec/rley/formatter/asciitree_spec.rb
|
205
208
|
- spec/rley/formatter/bracket_notation_spec.rb
|
206
209
|
- spec/rley/formatter/debug_spec.rb
|
207
210
|
- spec/rley/formatter/json_spec.rb
|
@@ -293,6 +296,7 @@ signing_key:
|
|
293
296
|
specification_version: 4
|
294
297
|
summary: Ruby implementation of the Earley's parsing algorithm
|
295
298
|
test_files:
|
299
|
+
- spec/rley/formatter/asciitree_spec.rb
|
296
300
|
- spec/rley/formatter/bracket_notation_spec.rb
|
297
301
|
- spec/rley/formatter/debug_spec.rb
|
298
302
|
- spec/rley/formatter/json_spec.rb
|