rley 0.6.06 → 0.6.07
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/.rubocop.yml +111 -111
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -1
- data/lib/rley/constants.rb +1 -1
- data/lib/rley/parse_rep/ast_base_builder.rb +9 -9
- data/lib/rley/parse_rep/parse_tree_builder.rb +3 -3
- data/lib/rley/parser/gfg_chart.rb +15 -4
- data/lib/rley/parser/gfg_earley_parser.rb +29 -25
- data/lib/rley/parser/gfg_parsing.rb +19 -18
- data/lib/rley/parser/parse_walker_factory.rb +2 -0
- data/lib/rley/ptree/parse_tree.rb +1 -1
- data/lib/rley/syntax/grammar.rb +19 -3
- data/lib/rley/syntax/production.rb +25 -16
- data/spec/rley/parser/gfg_chart_spec.rb +23 -11
- data/spec/rley/parser/gfg_parsing_spec.rb +21 -23
- data/spec/rley/syntax/grammar_spec.rb +7 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25c9a2612411bdb3767901c9b21d1e38f5279c94
|
4
|
+
data.tar.gz: 9c3ffe74bda63f31d40421b74a6f976ac3db49ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d289304c2a693fb1eb14747751e205bc23cccb9941c5f416af1966e6b3fcf3d2ff58f52ae6d56a2cae40752bcccbc2cd9a2c9015cc555a5ad3ef1890306f15e9
|
7
|
+
data.tar.gz: c59a9d80ff3b941efabed869de7e5d16b6ebc597c3a63d914de3976aa3c709a6cfde0630c8c931e14ae559e4c3fcbc5dddb935fa2d58c12c71fafad2fc817f4e
|
data/.rubocop.yml
CHANGED
@@ -1,111 +1,111 @@
|
|
1
|
-
AllCops:
|
2
|
-
Exclude:
|
3
|
-
- 'features/**/*'
|
4
|
-
- 'exp/**/*'
|
5
|
-
- 'gems/**/*'
|
6
|
-
- 'refs/**/*'
|
7
|
-
|
8
|
-
# This is disabled because some demos use UTF-8
|
9
|
-
AsciiComments:
|
10
|
-
Enabled: false
|
11
|
-
|
12
|
-
Attr:
|
13
|
-
Enabled: false
|
14
|
-
|
15
|
-
BlockComments:
|
16
|
-
Enabled: false
|
17
|
-
|
18
|
-
CaseIndentation:
|
19
|
-
EnforcedStyle: end
|
20
|
-
IndentOneStep: true
|
21
|
-
|
22
|
-
# Rubocop enforces the use of is_a? instead of kind_of?
|
23
|
-
# Which is contrary to modelling practice.
|
24
|
-
ClassCheck:
|
25
|
-
Enabled: false
|
26
|
-
|
27
|
-
ClassLength:
|
28
|
-
Max: 250
|
29
|
-
CountComments: false
|
30
|
-
|
31
|
-
ConstantName:
|
32
|
-
Enabled: false
|
33
|
-
|
34
|
-
CyclomaticComplexity:
|
35
|
-
Enabled: false
|
36
|
-
|
37
|
-
DefWithParentheses:
|
38
|
-
Enabled: false
|
39
|
-
|
40
|
-
Documentation:
|
41
|
-
Enabled: false
|
42
|
-
|
43
|
-
EmptyLines:
|
44
|
-
Enabled: false
|
45
|
-
|
46
|
-
Encoding:
|
47
|
-
Enabled: false
|
48
|
-
|
49
|
-
EndOfLine:
|
50
|
-
Enabled: false
|
51
|
-
# SupportedStyles: lf
|
52
|
-
|
53
|
-
|
54
|
-
IndentationWidth :
|
55
|
-
Enabled: false
|
56
|
-
|
57
|
-
# Disable this because it produces false negatives
|
58
|
-
Naming/HeredocDelimiterNaming:
|
59
|
-
Enabled: false
|
60
|
-
|
61
|
-
# Enabled after end of support of Rubies < 2.3
|
62
|
-
Layout/IndentHeredoc:
|
63
|
-
Enabled: false
|
64
|
-
|
65
|
-
Metrics/AbcSize:
|
66
|
-
Max: 50
|
67
|
-
|
68
|
-
# Avoid methods longer than 50 lines of code
|
69
|
-
Metrics/MethodLength:
|
70
|
-
Max: 50
|
71
|
-
CountComments: false
|
72
|
-
|
73
|
-
# Avoid modules longer than 200 lines of code
|
74
|
-
Metrics/ModuleLength:
|
75
|
-
CountComments: false
|
76
|
-
Max: 200
|
77
|
-
|
78
|
-
Metrics/PerceivedComplexity:
|
79
|
-
Enabled: true
|
80
|
-
Max: 50
|
81
|
-
|
82
|
-
Naming/MethodName:
|
83
|
-
Enabled: false
|
84
|
-
|
85
|
-
NonNilCheck:
|
86
|
-
Enabled: false
|
87
|
-
|
88
|
-
NumericLiterals:
|
89
|
-
Enabled: false
|
90
|
-
|
91
|
-
RaiseArgs:
|
92
|
-
Enabled: false
|
93
|
-
|
94
|
-
RedundantReturn:
|
95
|
-
Enabled: false
|
96
|
-
|
97
|
-
SpaceInsideBrackets:
|
98
|
-
Enabled: false
|
99
|
-
|
100
|
-
TrailingWhitespace:
|
101
|
-
Enabled: false
|
102
|
-
|
103
|
-
VariableName:
|
104
|
-
Enabled: false
|
105
|
-
|
106
|
-
VariableNumber:
|
107
|
-
Enabled: false
|
108
|
-
|
109
|
-
Style/CommentedKeyword:
|
110
|
-
Enabled: false
|
111
|
-
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- 'features/**/*'
|
4
|
+
- 'exp/**/*'
|
5
|
+
- 'gems/**/*'
|
6
|
+
- 'refs/**/*'
|
7
|
+
|
8
|
+
# This is disabled because some demos use UTF-8
|
9
|
+
AsciiComments:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Attr:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
BlockComments:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
CaseIndentation:
|
19
|
+
EnforcedStyle: end
|
20
|
+
IndentOneStep: true
|
21
|
+
|
22
|
+
# Rubocop enforces the use of is_a? instead of kind_of?
|
23
|
+
# Which is contrary to modelling practice.
|
24
|
+
ClassCheck:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
ClassLength:
|
28
|
+
Max: 250
|
29
|
+
CountComments: false
|
30
|
+
|
31
|
+
ConstantName:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
CyclomaticComplexity:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
DefWithParentheses:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
Documentation:
|
41
|
+
Enabled: false
|
42
|
+
|
43
|
+
EmptyLines:
|
44
|
+
Enabled: false
|
45
|
+
|
46
|
+
Encoding:
|
47
|
+
Enabled: false
|
48
|
+
|
49
|
+
EndOfLine:
|
50
|
+
Enabled: false
|
51
|
+
# SupportedStyles: lf
|
52
|
+
|
53
|
+
|
54
|
+
IndentationWidth :
|
55
|
+
Enabled: false
|
56
|
+
|
57
|
+
# Disable this because it produces false negatives
|
58
|
+
Naming/HeredocDelimiterNaming:
|
59
|
+
Enabled: false
|
60
|
+
|
61
|
+
# Enabled after end of support of Rubies < 2.3
|
62
|
+
Layout/IndentHeredoc:
|
63
|
+
Enabled: false
|
64
|
+
|
65
|
+
Metrics/AbcSize:
|
66
|
+
Max: 50
|
67
|
+
|
68
|
+
# Avoid methods longer than 50 lines of code
|
69
|
+
Metrics/MethodLength:
|
70
|
+
Max: 50
|
71
|
+
CountComments: false
|
72
|
+
|
73
|
+
# Avoid modules longer than 200 lines of code
|
74
|
+
Metrics/ModuleLength:
|
75
|
+
CountComments: false
|
76
|
+
Max: 200
|
77
|
+
|
78
|
+
Metrics/PerceivedComplexity:
|
79
|
+
Enabled: true
|
80
|
+
Max: 50
|
81
|
+
|
82
|
+
Naming/MethodName:
|
83
|
+
Enabled: false
|
84
|
+
|
85
|
+
NonNilCheck:
|
86
|
+
Enabled: false
|
87
|
+
|
88
|
+
NumericLiterals:
|
89
|
+
Enabled: false
|
90
|
+
|
91
|
+
RaiseArgs:
|
92
|
+
Enabled: false
|
93
|
+
|
94
|
+
RedundantReturn:
|
95
|
+
Enabled: false
|
96
|
+
|
97
|
+
SpaceInsideBrackets:
|
98
|
+
Enabled: false
|
99
|
+
|
100
|
+
TrailingWhitespace:
|
101
|
+
Enabled: false
|
102
|
+
|
103
|
+
VariableName:
|
104
|
+
Enabled: false
|
105
|
+
|
106
|
+
VariableNumber:
|
107
|
+
Enabled: false
|
108
|
+
|
109
|
+
Style/CommentedKeyword:
|
110
|
+
Enabled: false
|
111
|
+
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
### 0.6.07 / 2018-05-29
|
2
|
+
* [CHANGE] Method `GFGEarleyParser#parse` doesn't assume that the exact sequence of tokens to be known in advance.
|
3
|
+
* [FIXED] Method `GFGParsing#nullable_rule`: in very specific cases a wrong parse entry was added to the parse chart.
|
4
|
+
* [FIXED] File `examples/general/left.rb` is working back.
|
5
|
+
|
1
6
|
### 0.6.06 / 2018-04-12
|
2
7
|
* [ADDED] Method `done!`: added to classes involved parse tree and parse forest creation. This method signals the end of parsing.
|
3
8
|
* [CHANGE] Method `ParseTreeBuilder#process_item_entry`: added an explicit message in case of ambiguous parse.
|
data/Gemfile
CHANGED
data/lib/rley/constants.rb
CHANGED
@@ -21,14 +21,14 @@ module Rley # This module is used as a namespace
|
|
21
21
|
def terminal2node()
|
22
22
|
raise NotImplementedError
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
# Method to override in subclass.
|
26
26
|
# Default class for representing terminal nodes.
|
27
27
|
# @return [Class]
|
28
28
|
def terminalnode_class()
|
29
29
|
PTree::TerminalNode
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
# Default method name to invoke when production
|
33
33
|
# with given name is invoked.
|
34
34
|
# Override this method for other method naming convention.
|
@@ -51,7 +51,7 @@ module Rley # This module is used as a namespace
|
|
51
51
|
# Simply return the second child node
|
52
52
|
# @param _range [Lexical::TokenRange]
|
53
53
|
# @param _tokens [Array<Lexical::Token>]
|
54
|
-
# @param theChildren [Array<Object>]
|
54
|
+
# @param theChildren [Array<Object>]
|
55
55
|
def return_second_child(_range, _tokens, theChildren)
|
56
56
|
return theChildren[1]
|
57
57
|
end
|
@@ -60,7 +60,7 @@ module Rley # This module is used as a namespace
|
|
60
60
|
# Simply return the last child node
|
61
61
|
# @param _range [Lexical::TokenRange]
|
62
62
|
# @param _tokens [Array<Lexical::Token>]
|
63
|
-
# @param theChildren [Array<Object>]
|
63
|
+
# @param theChildren [Array<Object>]
|
64
64
|
def return_last_child(_range, _tokens, theChildren)
|
65
65
|
return theChildren[-1]
|
66
66
|
end
|
@@ -68,13 +68,13 @@ module Rley # This module is used as a namespace
|
|
68
68
|
# Simply return an epsilon symbol
|
69
69
|
# @param _range [Lexical::TokenRange]
|
70
70
|
# @param _tokens [Array<Lexical::Token>]
|
71
|
-
# @param _children [Array<Object>]
|
71
|
+
# @param _children [Array<Object>]
|
72
72
|
def return_epsilon(_range, _tokens, _children)
|
73
73
|
return nil
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
protected
|
77
|
-
|
77
|
+
|
78
78
|
# Overriding method.
|
79
79
|
# Create a parse tree object with given
|
80
80
|
# node as root node.
|
@@ -98,7 +98,7 @@ module Rley # This module is used as a namespace
|
|
98
98
|
|
99
99
|
return node
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
# Method to override.
|
103
103
|
# Factory method for creating a parent node object.
|
104
104
|
# @param aProduction [Production] Production rule
|
@@ -119,7 +119,7 @@ module Rley # This module is used as a namespace
|
|
119
119
|
else
|
120
120
|
msg = "Don't know production '#{aProduction.name}'"
|
121
121
|
raise StandardError, msg
|
122
|
-
end
|
122
|
+
end
|
123
123
|
end
|
124
124
|
return node
|
125
125
|
end
|
@@ -47,7 +47,7 @@ module Rley # This module is used as a namespace
|
|
47
47
|
@dummy_node = Object.new.freeze
|
48
48
|
end
|
49
49
|
|
50
|
-
# Notify the builder that the construction is
|
50
|
+
# Notify the builder that the parse tree construction is complete.
|
51
51
|
def done!()
|
52
52
|
result.done!
|
53
53
|
end
|
@@ -77,7 +77,7 @@ module Rley # This module is used as a namespace
|
|
77
77
|
|
78
78
|
# Return the stack
|
79
79
|
def stack()
|
80
|
-
|
80
|
+
@stack
|
81
81
|
end
|
82
82
|
|
83
83
|
# Overriding method.
|
@@ -92,7 +92,7 @@ module Rley # This module is used as a namespace
|
|
92
92
|
|
93
93
|
# Return the top of stack element.
|
94
94
|
def tos()
|
95
|
-
|
95
|
+
@stack.last
|
96
96
|
end
|
97
97
|
|
98
98
|
# Handler for visit events for ParseEntry matching N. pattern
|
@@ -12,10 +12,9 @@ module Rley # This module is used as a namespace
|
|
12
12
|
# @return [Array<ParseEntrySet>] entry sets (one per input token + 1)
|
13
13
|
attr_reader(:sets)
|
14
14
|
|
15
|
-
# @param tokenCount [Integer] The number of lexemes in the input to parse.
|
16
15
|
# @param aGFGraph [GFG::GrmFlowGraph] The GFG for the grammar in use.
|
17
|
-
def initialize(
|
18
|
-
@sets =
|
16
|
+
def initialize( aGFGraph)
|
17
|
+
@sets = [ ParseEntrySet.new ]
|
19
18
|
push_entry(aGFGraph.start_vertex, 0, 0, :start_rule)
|
20
19
|
end
|
21
20
|
|
@@ -25,7 +24,7 @@ module Rley # This module is used as a namespace
|
|
25
24
|
end
|
26
25
|
|
27
26
|
# @param index [Integer]
|
28
|
-
# @return [ParseEntrySet] Access the entry set at given position
|
27
|
+
# @return [ParseEntrySet] Access the entry set at given position.
|
29
28
|
def [](index)
|
30
29
|
return sets[index]
|
31
30
|
end
|
@@ -53,6 +52,11 @@ module Rley # This module is used as a namespace
|
|
53
52
|
# puts " anIndex: #{anIndex}"
|
54
53
|
# puts " _reason: #{_reason}"
|
55
54
|
new_entry = ParseEntry.new(aVertex, anOrigin)
|
55
|
+
if anIndex == sets.size
|
56
|
+
err_msg = "Internal error: unexpected push reason #{_reason}"
|
57
|
+
raise StandardError, err_msg if _reason != :scan_rule
|
58
|
+
add_entry_set
|
59
|
+
end
|
56
60
|
pushed = self[anIndex].push_entry(new_entry)
|
57
61
|
|
58
62
|
return pushed
|
@@ -88,6 +92,13 @@ module Rley # This module is used as a namespace
|
|
88
92
|
|
89
93
|
return success_entries.first
|
90
94
|
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def add_entry_set()
|
99
|
+
@sets << ParseEntrySet.new
|
100
|
+
end
|
101
|
+
|
91
102
|
end # class
|
92
103
|
end # module
|
93
104
|
end # module
|
@@ -22,36 +22,40 @@ module Rley # This module is used as a namespace
|
|
22
22
|
# tokenizer/scanner/lexer.
|
23
23
|
# @return [GFGParsing] an object that embeds the parse results.
|
24
24
|
def parse(aTokenSequence)
|
25
|
-
result = GFGParsing.new(gf_graph
|
26
|
-
|
27
|
-
if
|
25
|
+
result = GFGParsing.new(gf_graph)
|
26
|
+
token_count = aTokenSequence.size
|
27
|
+
if token_count.zero? && !grammar.start_symbol.nullable?
|
28
28
|
return unexpected_empty_input(result)
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
result
|
33
|
-
|
34
|
-
|
35
|
-
if next_symbol && next_symbol.kind_of?(Syntax::NonTerminal)
|
36
|
-
# ...apply the Call rule
|
37
|
-
call_rule(result, entry, i)
|
38
|
-
end
|
39
|
-
|
40
|
-
exit_rule(result, entry, i) if entry.exit_entry?
|
41
|
-
start_rule(result, entry, i) if entry.start_entry?
|
42
|
-
end_rule(result, entry, i) if entry.end_entry?
|
43
|
-
end
|
44
|
-
if i < last_token_index
|
45
|
-
scan_success = scan_rule(result, i)
|
46
|
-
break unless scan_success
|
47
|
-
end
|
31
|
+
aTokenSequence.each_with_index do |token, i|
|
32
|
+
parse_for_token(result, i)
|
33
|
+
scan_success = scan_rule(result, i, token)
|
34
|
+
break unless scan_success
|
48
35
|
end
|
36
|
+
parse_for_token(result, token_count) unless result.failure_reason
|
37
|
+
|
49
38
|
result.done # End of parsing process
|
50
39
|
return result
|
51
40
|
end
|
52
41
|
|
53
42
|
private
|
54
43
|
|
44
|
+
def parse_for_token(result, index)
|
45
|
+
result.chart[index].each do |entry|
|
46
|
+
# Is entry of the form? [A => alpha . B beta, k]...
|
47
|
+
next_symbol = entry.next_symbol
|
48
|
+
if next_symbol && next_symbol.kind_of?(Syntax::NonTerminal)
|
49
|
+
# ...apply the Call rule
|
50
|
+
call_rule(result, entry, index)
|
51
|
+
end
|
52
|
+
|
53
|
+
exit_rule(result, entry, index) if entry.exit_entry?
|
54
|
+
start_rule(result, entry, index) if entry.start_entry?
|
55
|
+
end_rule(result, entry, index) if entry.end_entry?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
55
59
|
# Let the current sigma set be the ith parse entry set.
|
56
60
|
# This method is invoked when an entry is added to the parse entry set
|
57
61
|
# and is of the form [A => alpha . B beta, k].
|
@@ -95,17 +99,17 @@ module Rley # This module is used as a namespace
|
|
95
99
|
# [A => α . t γ, i]
|
96
100
|
# and allow them to cross the edge, adding the node on the back side
|
97
101
|
# of the edge as an entry to the next sigma set:
|
98
|
-
# add an entry to the next sigma set [A => α t . γ, i]
|
99
|
-
def scan_rule(aParsing, aPosition)
|
100
|
-
aParsing.scan_rule(aPosition)
|
102
|
+
# add an entry to the next sigma set [A => α t . γ, i + 1]
|
103
|
+
def scan_rule(aParsing, aPosition, aToken)
|
104
|
+
aParsing.scan_rule(aPosition, aToken)
|
101
105
|
end
|
102
|
-
|
106
|
+
|
103
107
|
# Parse error detected: no input tokens provided while the grammar
|
104
108
|
# forbids this this.
|
105
109
|
def unexpected_empty_input(aParsing)
|
106
110
|
aParsing.faulty(NoInput.new)
|
107
111
|
return aParsing
|
108
|
-
end
|
112
|
+
end
|
109
113
|
end # class
|
110
114
|
end # module
|
111
115
|
end # module
|
@@ -33,11 +33,10 @@ module Rley # This module is used as a namespace
|
|
33
33
|
|
34
34
|
# Constructor
|
35
35
|
# @param theGFG [GFG::GrmFlowGraph] the Grammar Flow Graph
|
36
|
-
|
37
|
-
def initialize(theGFG, theTokens)
|
36
|
+
def initialize(theGFG)
|
38
37
|
@gf_graph = theGFG
|
39
|
-
@tokens =
|
40
|
-
@chart = GFGChart.new(
|
38
|
+
@tokens = []
|
39
|
+
@chart = GFGChart.new(gf_graph)
|
41
40
|
@antecedence = Hash.new { |hash, key| hash[key] = [] }
|
42
41
|
antecedence[chart[0].first]
|
43
42
|
end
|
@@ -57,7 +56,7 @@ module Rley # This module is used as a namespace
|
|
57
56
|
|
58
57
|
if next_symbol.nullable? && anEntry.dotted_entry?
|
59
58
|
size_after = chart[pos].size
|
60
|
-
# ...apply the Nullable rule
|
59
|
+
# ...apply the Nullable rule, if not already invoked for this entry
|
61
60
|
nullable_rule(anEntry, aPosition) if size_after == size_before
|
62
61
|
end
|
63
62
|
end
|
@@ -81,14 +80,15 @@ module Rley # This module is used as a namespace
|
|
81
80
|
|
82
81
|
start.edges.each do |edge|
|
83
82
|
succ = edge.successor # succ always an ItemVertex
|
84
|
-
|
85
|
-
|
86
|
-
|
83
|
+
if succ.dotted_item.production.nullable?
|
84
|
+
succ_entry = apply_rule(start_entry, succ, pos, pos, :nullable_rule)
|
85
|
+
apply_rule(succ_entry, end_vertex, pos, pos, :nullable_rule)
|
86
|
+
end
|
87
87
|
end
|
88
88
|
|
89
89
|
curr_vertex = anEntry.vertex
|
90
90
|
next_vertex = curr_vertex.shortcut.successor
|
91
|
-
end_entry = push_entry(end_vertex, pos, pos, :nullable_rule)
|
91
|
+
end_entry = push_entry(end_vertex, pos, pos, :nullable_rule)
|
92
92
|
apply_rule(end_entry, next_vertex, anEntry.origin, pos, :nullable_rule)
|
93
93
|
end
|
94
94
|
|
@@ -144,10 +144,11 @@ module Rley # This module is used as a namespace
|
|
144
144
|
# [A => α . t γ, i]
|
145
145
|
# and allow them to cross the edge, adding the node on the back side
|
146
146
|
# of the edge as an entry to the next sigma set:
|
147
|
-
# add an entry to the next sigma set [A => α t . γ, i]
|
147
|
+
# add an entry to the next sigma set [A => α t . γ, i + 1]
|
148
148
|
# returns true if next token matches the expectations, false otherwise.
|
149
|
-
def scan_rule(aPosition)
|
150
|
-
|
149
|
+
def scan_rule(aPosition, aToken)
|
150
|
+
tokens << aToken
|
151
|
+
terminal = aToken.terminal
|
151
152
|
|
152
153
|
# Retrieve all the entries that expect the given terminal
|
153
154
|
expecting_term = chart[aPosition].entries4term(terminal)
|
@@ -271,11 +272,11 @@ END_MSG
|
|
271
272
|
# Rule: has 1..* antecedents, all of them are exit items
|
272
273
|
antecedents.each do |antec|
|
273
274
|
next if antec.exit_entry?
|
274
|
-
msg_prefix = "
|
275
|
-
msg_suffix = " has for antecedent #{antec}"
|
275
|
+
msg_prefix = "End vertex parse entry #{consequent}"
|
276
|
+
msg_suffix = " has for antecedent #{antec}"
|
276
277
|
raise StandardError, msg_prefix + msg_suffix
|
277
278
|
end
|
278
|
-
|
279
|
+
|
279
280
|
when Rley::GFG::ItemVertex
|
280
281
|
# Rule: has exactly one antecedent
|
281
282
|
# if antecedents.size > 1
|
@@ -284,15 +285,15 @@ END_MSG
|
|
284
285
|
# msg_suffix = antecedents.map(&:to_s).join("\n")
|
285
286
|
# raise(StandardError, msg_prefix + msg + msg_suffix)
|
286
287
|
# end
|
287
|
-
|
288
|
+
|
288
289
|
when Rley::GFG::StartVertex
|
289
290
|
# Rule: has 0..* antecedents, all of them are item vertices but not exit items
|
290
291
|
antecedents.each do |antec|
|
291
292
|
next if antec.dotted_entry? && !antec.end_entry?
|
292
293
|
msg_prefix = "Parse entry #{consequent}"
|
293
|
-
msg_suffix = " has for antecedent #{antec}"
|
294
|
+
msg_suffix = " has for antecedent #{antec}"
|
294
295
|
raise StandardError, msg_prefix + msg_suffix
|
295
|
-
end
|
296
|
+
end
|
296
297
|
end
|
297
298
|
|
298
299
|
consequent.add_antecedent(antecedentEntry)
|
@@ -52,6 +52,8 @@ module Rley # This module is used as a namespace
|
|
52
52
|
# @return [Enumerator] yields visit events when walking over the
|
53
53
|
# parse result
|
54
54
|
def build_walker(acceptingEntry, maxIndex, lazyWalk = false)
|
55
|
+
msg = 'Internal error: nil entry argument'
|
56
|
+
raise StandardError, msg if acceptingEntry.nil?
|
55
57
|
# Local context for the enumerator
|
56
58
|
ctx = init_context(acceptingEntry, maxIndex, lazyWalk)
|
57
59
|
|
@@ -13,7 +13,7 @@ module Rley # This module is used as a namespace
|
|
13
13
|
# The root node corresponds to the main/start symbol of the grammar.
|
14
14
|
class ParseTree
|
15
15
|
# @return [ParseTreeNode] The root node of the tree.
|
16
|
-
|
16
|
+
attr_accessor(:root)
|
17
17
|
|
18
18
|
# @param theRootNode [ParseTreeNode] The root node of the parse tree.
|
19
19
|
def initialize(theRootNode)
|
data/lib/rley/syntax/grammar.rb
CHANGED
@@ -214,20 +214,36 @@ module Rley # This module is used as a namespace
|
|
214
214
|
filtered_rules.reject! { |prod| prod.lhs.nullable? }
|
215
215
|
nullable_sets[i] = nullable_sets[i - 1].merge(new_nullables)
|
216
216
|
end
|
217
|
+
|
218
|
+
mark_nullable
|
217
219
|
end
|
218
220
|
|
219
221
|
# Return the set of nonterminals which have one of their
|
220
222
|
# production rules empty
|
221
223
|
def direct_nullable()
|
222
|
-
|
224
|
+
nullables = Set.new
|
223
225
|
# Direct nullable nonterminals correspond to empty productions
|
224
226
|
rules.each do |prod|
|
225
227
|
next unless prod.empty?
|
226
228
|
prod.lhs.nullable = true
|
227
|
-
|
229
|
+
nullables << prod.lhs
|
228
230
|
end
|
229
231
|
|
230
|
-
return
|
232
|
+
return nullables
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
# For each prodction determine whether it is nullable or not.
|
237
|
+
# A nullable production is a production that can match an empty string.
|
238
|
+
def mark_nullable
|
239
|
+
rules.each do |prod|
|
240
|
+
if prod.empty?
|
241
|
+
prod.nullable = true
|
242
|
+
else
|
243
|
+
# If all rhs members are all nullable, then rule is nullable
|
244
|
+
prod.nullable = prod.rhs.members.all?(&:nullable?)
|
245
|
+
end
|
246
|
+
end
|
231
247
|
end
|
232
248
|
|
233
249
|
def add_symbol(aSymbol)
|
@@ -15,15 +15,19 @@ module Rley # This module is used as a namespace
|
|
15
15
|
|
16
16
|
# @return [NonTerminal] The left-hand side of the rule.
|
17
17
|
attr_reader(:lhs)
|
18
|
-
|
19
|
-
# @return [String]
|
18
|
+
|
19
|
+
# @return [String]
|
20
20
|
# The name of the production rule. It must be unique in a grammar.
|
21
21
|
attr_accessor(:name)
|
22
|
-
|
23
|
-
# @return [Boolean] A production is generative when all of its
|
22
|
+
|
23
|
+
# @return [Boolean] A production is generative when all of its
|
24
24
|
# rhs members are generative (that is, they can each generate/derive
|
25
25
|
# a non-empty string of terminals).
|
26
|
-
attr_writer(:generative)
|
26
|
+
attr_writer(:generative)
|
27
|
+
|
28
|
+
# @return [Boolean] A production is nullable when all of its
|
29
|
+
# rhs members are nullable.
|
30
|
+
attr_writer(:nullable)
|
27
31
|
|
28
32
|
# Provide common alternate names to lhs and rhs accessors
|
29
33
|
|
@@ -32,10 +36,10 @@ module Rley # This module is used as a namespace
|
|
32
36
|
|
33
37
|
# Create a Production instance.
|
34
38
|
# @param aNonTerminal [NonTerminal] The left-hand side of the rule.
|
35
|
-
# @param theSymbols [list<Terminal | NonTerminal>] symbols of rhs.
|
39
|
+
# @param theSymbols [list<Terminal | NonTerminal>] symbols of rhs.
|
36
40
|
def initialize(aNonTerminal, theSymbols)
|
37
41
|
@lhs = valid_lhs(aNonTerminal)
|
38
|
-
@rhs = valid_rhs(theSymbols)
|
42
|
+
@rhs = valid_rhs(theSymbols)
|
39
43
|
end
|
40
44
|
|
41
45
|
# Is the rhs empty?
|
@@ -43,16 +47,21 @@ module Rley # This module is used as a namespace
|
|
43
47
|
def empty?()
|
44
48
|
return rhs.empty?
|
45
49
|
end
|
46
|
-
|
47
|
-
# Return true iff the production is generative
|
50
|
+
|
51
|
+
# Return true iff the production is generative
|
48
52
|
def generative?()
|
49
53
|
if @generative.nil?
|
50
54
|
end
|
51
|
-
|
55
|
+
|
52
56
|
return @generative
|
53
57
|
end
|
54
58
|
|
55
|
-
#
|
59
|
+
# @return [Boolen] true iff the production is nullable
|
60
|
+
def nullable?()
|
61
|
+
return @nullable
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a string containing a human-readable representation of the
|
56
65
|
# production.
|
57
66
|
# @return [String]
|
58
67
|
def inspect()
|
@@ -63,7 +72,7 @@ module Rley # This module is used as a namespace
|
|
63
72
|
result << " @generative=#{@generative}>"
|
64
73
|
return result
|
65
74
|
end
|
66
|
-
|
75
|
+
|
67
76
|
# A setter for the production name
|
68
77
|
# @param aName [String] the name of the production
|
69
78
|
def as(aName)
|
@@ -83,14 +92,14 @@ module Rley # This module is used as a namespace
|
|
83
92
|
|
84
93
|
return aNonTerminal
|
85
94
|
end
|
86
|
-
|
95
|
+
|
87
96
|
def valid_rhs(theSymbols)
|
88
97
|
if theSymbols.nil?
|
89
98
|
msg_prefix = 'Right side of a production of the kind '
|
90
99
|
msg_suffix = "'#{lhs.name}' => ... is nil."
|
91
|
-
raise StandardError, msg_prefix + msg_suffix
|
92
|
-
end
|
93
|
-
|
100
|
+
raise StandardError, msg_prefix + msg_suffix
|
101
|
+
end
|
102
|
+
|
94
103
|
return SymbolSeq.new(theSymbols)
|
95
104
|
end
|
96
105
|
end # class
|
@@ -47,38 +47,50 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
47
47
|
let(:items_from_grammar) { build_items_for_grammar(grammar_abc) }
|
48
48
|
let(:sample_gfg) { GFG::GrmFlowGraph.new(items_from_grammar) }
|
49
49
|
let(:sample_start_symbol) { sample_gfg.start_vertex.non_terminal }
|
50
|
+
let(:second_vertex) { sample_gfg.start_vertex.edges[0].successor }
|
50
51
|
|
51
52
|
|
52
53
|
# Default instantiation rule
|
53
|
-
subject { GFGChart.new(
|
54
|
+
subject { GFGChart.new(sample_gfg) }
|
54
55
|
|
55
56
|
|
56
57
|
context 'Initialization:' do
|
57
58
|
it 'should be created with start vertex, token count' do
|
58
|
-
expect { GFGChart.new(
|
59
|
-
.not_to raise_error
|
59
|
+
expect { GFGChart.new(sample_gfg) }.not_to raise_error
|
60
60
|
end
|
61
61
|
|
62
|
-
it 'should have
|
63
|
-
expect(subject.sets.size).to eq(
|
62
|
+
it 'should have one entry set' do
|
63
|
+
expect(subject.sets.size).to eq(1)
|
64
64
|
end
|
65
65
|
|
66
66
|
it 'should know the start symbol' do
|
67
67
|
expect(subject.start_symbol).to eq(sample_start_symbol)
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
it 'should know the initial parse entry' do
|
71
71
|
expect(subject.initial_entry.vertex).to eq(sample_gfg.start_vertex)
|
72
72
|
expect(subject.initial_entry.origin).to eq(0)
|
73
73
|
end
|
74
|
-
|
74
|
+
end # context
|
75
75
|
|
76
|
-
|
77
|
-
|
76
|
+
context 'Provided services:' do
|
77
|
+
it 'should accept the pushing of a parse entry in existing set' do
|
78
|
+
expect(subject.sets[0].entries.size).to eq(1)
|
79
|
+
subject.push_entry(second_vertex, 0, 0, :scan_rule)
|
80
|
+
expect(subject.sets[0].entries.size).to eq(2)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should accept the pushing of a parse entry in new set' do
|
84
|
+
expect(subject.sets[0].entries.size).to eq(1)
|
85
|
+
subject.push_entry(second_vertex, 0, 1, :scan_rule)
|
86
|
+
expect(subject.sets[0].entries.size).to eq(1)
|
87
|
+
expect(subject.sets.size).to eq(2)
|
88
|
+
expect(subject.sets[1].entries.size).to eq(1)
|
78
89
|
end
|
79
90
|
|
80
|
-
|
81
|
-
|
91
|
+
it 'should retrieve an existing set at given position' do
|
92
|
+
expect(subject[0]).to eq(subject.sets[0])
|
93
|
+
end
|
82
94
|
end # context
|
83
95
|
end # describe
|
84
96
|
end # module
|
@@ -41,10 +41,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
41
41
|
builder.grammar
|
42
42
|
end
|
43
43
|
|
44
|
-
let(:grm1_tokens)
|
45
|
-
build_token_sequence(%w[a a b c c], grm1)
|
46
|
-
end
|
47
|
-
|
44
|
+
let(:grm1_tokens) { build_token_sequence(%w[a a b c c], grm1) }
|
48
45
|
let(:grm1_token_b) { build_token_sequence(['b'], grm1) }
|
49
46
|
|
50
47
|
# Helper method. Create an array of dotted items
|
@@ -55,18 +52,15 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
55
52
|
let(:output) { StringIO.new('', 'w') }
|
56
53
|
|
57
54
|
# Default instantiation rule
|
58
|
-
subject
|
59
|
-
GFGParsing.new(sample_gfg, grm1_tokens)
|
60
|
-
end
|
55
|
+
subject { GFGParsing.new(sample_gfg) }
|
61
56
|
|
62
57
|
context 'Initialization:' do
|
63
|
-
it 'should be created with a GFG
|
64
|
-
expect { GFGParsing.new(sample_gfg
|
65
|
-
.not_to raise_error
|
58
|
+
it 'should be created with a GFG' do
|
59
|
+
expect { GFGParsing.new(sample_gfg) }.not_to raise_error
|
66
60
|
end
|
67
61
|
|
68
|
-
it 'should
|
69
|
-
expect(subject.tokens).to
|
62
|
+
it 'should have an empty tokens array' do
|
63
|
+
expect(subject.tokens).to be_empty
|
70
64
|
end
|
71
65
|
|
72
66
|
it 'should know its chart object' do
|
@@ -107,11 +101,9 @@ SNIPPET
|
|
107
101
|
|
108
102
|
# Utility method to initialize the second entry set...
|
109
103
|
def seed_second_set()
|
110
|
-
# Cheating: we change
|
111
|
-
subject.instance_variable_set(:@tokens, grm1_token_b)
|
112
|
-
|
104
|
+
# Cheating: we change the tokens to scan...
|
113
105
|
# Seeding second entry set...
|
114
|
-
subject.scan_rule(0)
|
106
|
+
subject.scan_rule(0, grm1_token_b[0])
|
115
107
|
end
|
116
108
|
|
117
109
|
# Utility method used to invoke the private method 'push_entry'
|
@@ -120,19 +112,19 @@ SNIPPET
|
|
120
112
|
end
|
121
113
|
|
122
114
|
it 'should push a parse entry to a given chart entry set' do
|
123
|
-
expect(subject.chart[1]).to
|
115
|
+
expect(subject.chart.sets[1]).to be_nil
|
124
116
|
a_vertex = sample_gfg.find_vertex('A => a . A c')
|
125
117
|
|
126
|
-
push_entry(subject, a_vertex, 1, 1, :
|
118
|
+
push_entry(subject, a_vertex, 1, 1, :scan_rule)
|
127
119
|
expect(subject.chart[1].size).to eq(1)
|
128
120
|
expect(subject.chart[1].first.vertex).to eq(a_vertex)
|
129
121
|
|
130
122
|
# Pushing twice the same state must be no-op
|
131
|
-
push_entry(subject, a_vertex, 1, 1, :
|
123
|
+
push_entry(subject, a_vertex, 1, 1, :scan_rule)
|
132
124
|
expect(subject.chart[1].size).to eq(1)
|
133
125
|
|
134
126
|
# Pushing to another entry set
|
135
|
-
push_entry(subject, a_vertex, 1, 2, :
|
127
|
+
push_entry(subject, a_vertex, 1, 2, :scan_rule)
|
136
128
|
expect(subject.chart[2].size).to eq(1)
|
137
129
|
end
|
138
130
|
|
@@ -191,8 +183,8 @@ SNIPPET
|
|
191
183
|
# ['A => . a A c', 'A => . b']
|
192
184
|
fourth_entry = subject.chart[0].entries[3] # 'A => . a A c'
|
193
185
|
|
194
|
-
expect(subject.chart[1]).to
|
195
|
-
subject.scan_rule(0)
|
186
|
+
expect(subject.chart.sets[1]).to be_nil
|
187
|
+
subject.scan_rule(0, grm1_tokens[0])
|
196
188
|
# Given that the scanned token is 'a'...
|
197
189
|
# Then a new entry is added in next entry set
|
198
190
|
expect(subject.chart[1].size).to eq(1)
|
@@ -316,9 +308,15 @@ SNIPPET
|
|
316
308
|
tokens = expr_tokenizer('2 + 3 * 4', b_expr_grammar)
|
317
309
|
parser.parse(tokens)
|
318
310
|
end
|
311
|
+
|
312
|
+
it 'should indicate whether a parse succeeded' do
|
313
|
+
expect(subject.success?).to be_truthy
|
314
|
+
end
|
319
315
|
|
320
316
|
it 'should build a parse forest' do
|
321
|
-
|
317
|
+
if subject.success?
|
318
|
+
expect { subject.parse_forest }.not_to raise_error
|
319
|
+
end
|
322
320
|
end
|
323
321
|
=begin
|
324
322
|
it 'should create the root of a parse forest' do
|
@@ -246,6 +246,13 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
246
246
|
expect(nterm).to be_nullable
|
247
247
|
end
|
248
248
|
end
|
249
|
+
|
250
|
+
it 'should mark its nullable productions' do
|
251
|
+
# Given the above productions, here are our expectations:
|
252
|
+
expectations = [true, false, false, true]
|
253
|
+
actuals = subject.rules.map(&:nullable?)
|
254
|
+
expect(actuals).to eq(expectations)
|
255
|
+
end
|
249
256
|
end # context
|
250
257
|
end # describe
|
251
258
|
end # module
|
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.6.
|
4
|
+
version: 0.6.07
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: coveralls
|