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