rley 0.4.03 → 0.4.04
Sign up to get free protection for your applications and to get access to all the features.
- 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
|