antelope 0.3.2 → 0.4.0
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/.gitignore +25 -25
- data/.rspec +3 -3
- data/.travis.yml +10 -10
- data/.yardopts +7 -7
- data/CONTRIBUTING.md +50 -38
- data/GENERATORS.md +180 -124
- data/Gemfile +7 -7
- data/LICENSE.txt +22 -22
- data/README.md +240 -104
- data/Rakefile +2 -2
- data/TODO.md +58 -58
- data/antelope.gemspec +29 -28
- data/bin/antelope +7 -7
- data/examples/deterministic.ace +35 -35
- data/examples/example.ace +52 -51
- data/examples/example.ace.err +192 -192
- data/examples/example.ace.inf +432 -432
- data/examples/example.ate +70 -70
- data/examples/example.ate.err +192 -192
- data/examples/example.ate.inf +432 -432
- data/examples/liquidscript.ace +233 -233
- data/examples/simple.ace +22 -22
- data/lib/antelope/ace/compiler.rb +334 -334
- data/lib/antelope/ace/errors.rb +30 -30
- data/lib/antelope/ace/scanner/argument.rb +57 -57
- data/lib/antelope/ace/scanner/first.rb +89 -89
- data/lib/antelope/ace/scanner/second.rb +178 -178
- data/lib/antelope/ace/scanner/third.rb +27 -27
- data/lib/antelope/ace/scanner.rb +144 -144
- data/lib/antelope/ace.rb +47 -47
- data/lib/antelope/cli.rb +60 -60
- data/lib/antelope/errors.rb +25 -25
- data/lib/antelope/generation/constructor/first.rb +86 -86
- data/lib/antelope/generation/constructor/follow.rb +105 -105
- data/lib/antelope/generation/constructor/nullable.rb +64 -64
- data/lib/antelope/generation/constructor.rb +127 -127
- data/lib/antelope/generation/errors.rb +17 -17
- data/lib/antelope/generation/null.rb +13 -13
- data/lib/antelope/generation/recognizer/rule.rb +216 -216
- data/lib/antelope/generation/recognizer/state.rb +129 -129
- data/lib/antelope/generation/recognizer.rb +177 -177
- data/lib/antelope/generation/tableizer.rb +176 -176
- data/lib/antelope/generation.rb +15 -15
- data/lib/antelope/generator/base/coerce.rb +115 -0
- data/lib/antelope/generator/base/extra.rb +50 -0
- data/lib/antelope/generator/base.rb +134 -264
- data/lib/antelope/generator/c.rb +11 -11
- data/lib/antelope/generator/c_header.rb +105 -105
- data/lib/antelope/generator/c_source.rb +39 -39
- data/lib/antelope/generator/error.rb +34 -34
- data/lib/antelope/generator/group.rb +60 -57
- data/lib/antelope/generator/html.rb +51 -51
- data/lib/antelope/generator/info.rb +47 -47
- data/lib/antelope/generator/null.rb +18 -18
- data/lib/antelope/generator/output.rb +17 -17
- data/lib/antelope/generator/ruby.rb +112 -79
- data/lib/antelope/generator/templates/c_header.ant +36 -36
- data/lib/antelope/generator/templates/c_source.ant +202 -202
- data/lib/antelope/generator/templates/error.erb +40 -0
- data/lib/antelope/generator/templates/html/antelope.css +53 -1
- data/lib/antelope/generator/templates/html/antelope.html +82 -1
- data/lib/antelope/generator/templates/html/antelope.js +9 -1
- data/lib/antelope/generator/templates/html/css.ant +53 -53
- data/lib/antelope/generator/templates/html/html.ant +82 -82
- data/lib/antelope/generator/templates/html/js.ant +9 -9
- data/lib/antelope/generator/templates/info.erb +61 -0
- data/lib/antelope/generator/templates/{ruby.ant → ruby.erb} +171 -178
- data/lib/antelope/generator.rb +62 -66
- data/lib/antelope/grammar/generation.rb +76 -76
- data/lib/antelope/grammar/loading.rb +84 -84
- data/lib/antelope/grammar/precedence.rb +59 -59
- data/lib/antelope/grammar/precedences.rb +64 -64
- data/lib/antelope/grammar/production.rb +56 -56
- data/lib/antelope/grammar/productions.rb +154 -154
- data/lib/antelope/grammar/symbols.rb +64 -64
- data/lib/antelope/grammar/token/epsilon.rb +23 -23
- data/lib/antelope/grammar/token/error.rb +24 -24
- data/lib/antelope/grammar/token/nonterminal.rb +15 -15
- data/lib/antelope/grammar/token/terminal.rb +15 -15
- data/lib/antelope/grammar/token.rb +231 -231
- data/lib/antelope/grammar.rb +68 -68
- data/lib/antelope/version.rb +6 -6
- data/lib/antelope.rb +18 -19
- data/optimizations.txt +42 -42
- data/spec/antelope/ace/compiler_spec.rb +60 -60
- data/spec/antelope/ace/scanner_spec.rb +27 -27
- data/spec/antelope/generation/constructor_spec.rb +131 -131
- data/spec/fixtures/simple.ace +22 -22
- data/spec/spec_helper.rb +39 -39
- data/spec/support/benchmark_helper.rb +5 -5
- data/spec/support/grammar_helper.rb +14 -14
- data/subl/Ace (Ruby).JSON-tmLanguage +94 -94
- data/subl/Ace (Ruby).tmLanguage +153 -153
- metadata +22 -11
- data/lib/antelope/generator/templates/error.ant +0 -34
- data/lib/antelope/generator/templates/info.ant +0 -53
- data/lib/antelope/template/compiler.rb +0 -78
- data/lib/antelope/template/errors.rb +0 -9
- data/lib/antelope/template/scanner.rb +0 -109
- data/lib/antelope/template.rb +0 -64
- data/spec/antelope/template_spec.rb +0 -50
@@ -1,86 +1,86 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module Antelope
|
4
|
-
module Generation
|
5
|
-
class Constructor
|
6
|
-
# Contains the methods to construct first sets for tokens.
|
7
|
-
module First
|
8
|
-
# Initialize.
|
9
|
-
def initialize
|
10
|
-
@firstifying = []
|
11
|
-
super
|
12
|
-
end
|
13
|
-
|
14
|
-
# Constructs the first set for a given token. This is how
|
15
|
-
# the method should behave:
|
16
|
-
#
|
17
|
-
# FIRST(ε) == [] # if ε is the epsilon token
|
18
|
-
# FIRST(x) == [x] # if x is a terminal
|
19
|
-
# FIRST(αβ) == if nullable?(α)
|
20
|
-
# FIRST(α) U FIRST(β)
|
21
|
-
# else
|
22
|
-
# FIRST(α)
|
23
|
-
# end
|
24
|
-
# FIRST(A) == FIRST(a_1) U FIRST(a_2) U ... U FIRST(a_n)
|
25
|
-
# # if A is a nonterminal and a_1, a_2, ..., a_3 are all
|
26
|
-
# # of the right-hand sides of its productions.
|
27
|
-
#
|
28
|
-
# @param token [Grammar::Token, Array<Grammar::Token>]
|
29
|
-
# @return [Set<Grammar::Token::Terminal>]
|
30
|
-
# @see #first_array
|
31
|
-
def first(token)
|
32
|
-
case token
|
33
|
-
when Grammar::Token::Nonterminal
|
34
|
-
firstifying(token) do
|
35
|
-
productions = grammar.productions[token.name]
|
36
|
-
productions.map { |prod|
|
37
|
-
first(prod[:items]) }.inject(Set.new, :+)
|
38
|
-
end
|
39
|
-
when Array
|
40
|
-
first_array(token)
|
41
|
-
when Grammar::Token::Epsilon
|
42
|
-
Set.new
|
43
|
-
when Grammar::Token::Terminal
|
44
|
-
Set.new([token])
|
45
|
-
else
|
46
|
-
incorrect_argument! token, Grammar::Token, Array
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
# Determines the FIRST set of an array of tokens. First, it
|
53
|
-
# removes any terminals we are finding the FIRST set for;
|
54
|
-
# then, it determines which tokens we have to find the FIRST
|
55
|
-
# sets for (since some tokens may be nullable). We then add
|
56
|
-
# those sets to our set.
|
57
|
-
#
|
58
|
-
# @param tokens [Array<Grammar::Token>]
|
59
|
-
# @return [Set<Grammar::Token>]
|
60
|
-
def first_array(tokens)
|
61
|
-
tokens.dup.delete_if { |_| @firstifying.include?(_) }.
|
62
|
-
each_with_index.take_while do |token, i|
|
63
|
-
if i.zero?
|
64
|
-
true
|
65
|
-
else
|
66
|
-
nullable?(tokens[i - 1])
|
67
|
-
end
|
68
|
-
end.map(&:first).map { |_| first(_) }.inject(Set.new, :+)
|
69
|
-
end
|
70
|
-
|
71
|
-
# Helps keep track of the nonterminals we're finding FIRST
|
72
|
-
# sets for. This helps prevent recursion.
|
73
|
-
#
|
74
|
-
# @param tok [Grammar::Token::Nonterminal]
|
75
|
-
# @yield once.
|
76
|
-
# @return [Set<Grammar::Token>]
|
77
|
-
def firstifying(tok)
|
78
|
-
@firstifying << tok
|
79
|
-
out = yield
|
80
|
-
@firstifying.delete tok
|
81
|
-
out
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
module Generation
|
5
|
+
class Constructor
|
6
|
+
# Contains the methods to construct first sets for tokens.
|
7
|
+
module First
|
8
|
+
# Initialize.
|
9
|
+
def initialize
|
10
|
+
@firstifying = []
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
# Constructs the first set for a given token. This is how
|
15
|
+
# the method should behave:
|
16
|
+
#
|
17
|
+
# FIRST(ε) == [] # if ε is the epsilon token
|
18
|
+
# FIRST(x) == [x] # if x is a terminal
|
19
|
+
# FIRST(αβ) == if nullable?(α)
|
20
|
+
# FIRST(α) U FIRST(β)
|
21
|
+
# else
|
22
|
+
# FIRST(α)
|
23
|
+
# end
|
24
|
+
# FIRST(A) == FIRST(a_1) U FIRST(a_2) U ... U FIRST(a_n)
|
25
|
+
# # if A is a nonterminal and a_1, a_2, ..., a_3 are all
|
26
|
+
# # of the right-hand sides of its productions.
|
27
|
+
#
|
28
|
+
# @param token [Grammar::Token, Array<Grammar::Token>]
|
29
|
+
# @return [Set<Grammar::Token::Terminal>]
|
30
|
+
# @see #first_array
|
31
|
+
def first(token)
|
32
|
+
case token
|
33
|
+
when Grammar::Token::Nonterminal
|
34
|
+
firstifying(token) do
|
35
|
+
productions = grammar.productions[token.name]
|
36
|
+
productions.map { |prod|
|
37
|
+
first(prod[:items]) }.inject(Set.new, :+)
|
38
|
+
end
|
39
|
+
when Array
|
40
|
+
first_array(token)
|
41
|
+
when Grammar::Token::Epsilon
|
42
|
+
Set.new
|
43
|
+
when Grammar::Token::Terminal
|
44
|
+
Set.new([token])
|
45
|
+
else
|
46
|
+
incorrect_argument! token, Grammar::Token, Array
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Determines the FIRST set of an array of tokens. First, it
|
53
|
+
# removes any terminals we are finding the FIRST set for;
|
54
|
+
# then, it determines which tokens we have to find the FIRST
|
55
|
+
# sets for (since some tokens may be nullable). We then add
|
56
|
+
# those sets to our set.
|
57
|
+
#
|
58
|
+
# @param tokens [Array<Grammar::Token>]
|
59
|
+
# @return [Set<Grammar::Token>]
|
60
|
+
def first_array(tokens)
|
61
|
+
tokens.dup.delete_if { |_| @firstifying.include?(_) }.
|
62
|
+
each_with_index.take_while do |token, i|
|
63
|
+
if i.zero?
|
64
|
+
true
|
65
|
+
else
|
66
|
+
nullable?(tokens[i - 1])
|
67
|
+
end
|
68
|
+
end.map(&:first).map { |_| first(_) }.inject(Set.new, :+)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Helps keep track of the nonterminals we're finding FIRST
|
72
|
+
# sets for. This helps prevent recursion.
|
73
|
+
#
|
74
|
+
# @param tok [Grammar::Token::Nonterminal]
|
75
|
+
# @yield once.
|
76
|
+
# @return [Set<Grammar::Token>]
|
77
|
+
def firstifying(tok)
|
78
|
+
@firstifying << tok
|
79
|
+
out = yield
|
80
|
+
@firstifying.delete tok
|
81
|
+
out
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -1,105 +1,105 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module Antelope
|
4
|
-
module Generation
|
5
|
-
class Constructor
|
6
|
-
|
7
|
-
# Contains the methods to find the FOLLOW sets of nonterminals.
|
8
|
-
module Follow
|
9
|
-
|
10
|
-
# Initialize.
|
11
|
-
def initialize
|
12
|
-
@follows = {}
|
13
|
-
super
|
14
|
-
end
|
15
|
-
|
16
|
-
# Returns the FOLLOW set of the given token. If the given
|
17
|
-
# token isn't a nonterminal, it raises an error. It then
|
18
|
-
# generates the FOLLOW set for the given token, and then
|
19
|
-
# caches it.
|
20
|
-
#
|
21
|
-
# @return [Set<Grammar::Token>]
|
22
|
-
# @see Constructor#incorrect_argument!
|
23
|
-
# @see #generate_follow_set
|
24
|
-
def follow(token)
|
25
|
-
unless token.is_a? Grammar::Token::Nonterminal
|
26
|
-
incorrect_argument! token, Grammar::Token::Nonterminal
|
27
|
-
end
|
28
|
-
|
29
|
-
@follows.fetch(token) { generate_follow_set(token) }
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
# Generates the FOLLOW set for the given token. It finds the
|
35
|
-
# positions at which the token appears in the grammar, and
|
36
|
-
# sees what could possibly follow it. For example, given the
|
37
|
-
# following production:
|
38
|
-
#
|
39
|
-
# A -> aBz
|
40
|
-
#
|
41
|
-
# With `a` and `z` being any combination of terminals and
|
42
|
-
# nonterminals, and we're trying to find the FOLLOW set of
|
43
|
-
# `B` we add the FIRST set of `z` to the FOLLOW set of `B`:
|
44
|
-
#
|
45
|
-
# FOLLOW(B) = FOLLOW(B) ∪ FIRST(z)
|
46
|
-
#
|
47
|
-
# In the case that `B` is at the end of a production, like so:
|
48
|
-
#
|
49
|
-
# A -> aB
|
50
|
-
#
|
51
|
-
# or
|
52
|
-
#
|
53
|
-
# A -> aBw
|
54
|
-
#
|
55
|
-
# (with `w` being nullable) We also add the FOLLOW set of `A`
|
56
|
-
# to `B`:
|
57
|
-
#
|
58
|
-
# FOLLOW(B) = FOLLOW(B) ∪ FOLLOW(A)
|
59
|
-
#
|
60
|
-
# In case this operation is potentially recursive, we make
|
61
|
-
# sure to set the FOLLOW set of `B` to an empty set (since we
|
62
|
-
# cache the result of a FOLLOW set, the empty set will be
|
63
|
-
# returned).
|
64
|
-
#
|
65
|
-
# @param token [Grammar::Token::Nonterminal]
|
66
|
-
# @return [Set<Grammar::Token>]
|
67
|
-
# @see First#first
|
68
|
-
# @see Nullable#nullable?
|
69
|
-
def generate_follow_set(token)
|
70
|
-
# Set it to the empty set so we don't end up recursing.
|
71
|
-
@follows[token] = Set.new
|
72
|
-
set = Set.new
|
73
|
-
|
74
|
-
productions.each do |rule|
|
75
|
-
items = rule.items
|
76
|
-
i = 0
|
77
|
-
|
78
|
-
# Find all of the positions within the rule that our token
|
79
|
-
# occurs, and then increment that position by one.
|
80
|
-
while i < items.size
|
81
|
-
next i += 1 unless items[i] == token
|
82
|
-
position = i.succ
|
83
|
-
|
84
|
-
# Find the FIRST set of every item after our token, and
|
85
|
-
# put that in our set.
|
86
|
-
set.merge first(items[position..-1])
|
87
|
-
|
88
|
-
# If we're at the end of the rule...
|
89
|
-
if position == items.size || nullable?(items[position..-1])
|
90
|
-
# Then add the FOLLOW set of the left-hand side to our
|
91
|
-
# set.
|
92
|
-
set.merge follow(rule.label)
|
93
|
-
end
|
94
|
-
|
95
|
-
i += 1
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# ReplGrammar the cached empty set with our filled set.
|
100
|
-
@follows[token] = set
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
module Generation
|
5
|
+
class Constructor
|
6
|
+
|
7
|
+
# Contains the methods to find the FOLLOW sets of nonterminals.
|
8
|
+
module Follow
|
9
|
+
|
10
|
+
# Initialize.
|
11
|
+
def initialize
|
12
|
+
@follows = {}
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the FOLLOW set of the given token. If the given
|
17
|
+
# token isn't a nonterminal, it raises an error. It then
|
18
|
+
# generates the FOLLOW set for the given token, and then
|
19
|
+
# caches it.
|
20
|
+
#
|
21
|
+
# @return [Set<Grammar::Token>]
|
22
|
+
# @see Constructor#incorrect_argument!
|
23
|
+
# @see #generate_follow_set
|
24
|
+
def follow(token)
|
25
|
+
unless token.is_a? Grammar::Token::Nonterminal
|
26
|
+
incorrect_argument! token, Grammar::Token::Nonterminal
|
27
|
+
end
|
28
|
+
|
29
|
+
@follows.fetch(token) { generate_follow_set(token) }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Generates the FOLLOW set for the given token. It finds the
|
35
|
+
# positions at which the token appears in the grammar, and
|
36
|
+
# sees what could possibly follow it. For example, given the
|
37
|
+
# following production:
|
38
|
+
#
|
39
|
+
# A -> aBz
|
40
|
+
#
|
41
|
+
# With `a` and `z` being any combination of terminals and
|
42
|
+
# nonterminals, and we're trying to find the FOLLOW set of
|
43
|
+
# `B` we add the FIRST set of `z` to the FOLLOW set of `B`:
|
44
|
+
#
|
45
|
+
# FOLLOW(B) = FOLLOW(B) ∪ FIRST(z)
|
46
|
+
#
|
47
|
+
# In the case that `B` is at the end of a production, like so:
|
48
|
+
#
|
49
|
+
# A -> aB
|
50
|
+
#
|
51
|
+
# or
|
52
|
+
#
|
53
|
+
# A -> aBw
|
54
|
+
#
|
55
|
+
# (with `w` being nullable) We also add the FOLLOW set of `A`
|
56
|
+
# to `B`:
|
57
|
+
#
|
58
|
+
# FOLLOW(B) = FOLLOW(B) ∪ FOLLOW(A)
|
59
|
+
#
|
60
|
+
# In case this operation is potentially recursive, we make
|
61
|
+
# sure to set the FOLLOW set of `B` to an empty set (since we
|
62
|
+
# cache the result of a FOLLOW set, the empty set will be
|
63
|
+
# returned).
|
64
|
+
#
|
65
|
+
# @param token [Grammar::Token::Nonterminal]
|
66
|
+
# @return [Set<Grammar::Token>]
|
67
|
+
# @see First#first
|
68
|
+
# @see Nullable#nullable?
|
69
|
+
def generate_follow_set(token)
|
70
|
+
# Set it to the empty set so we don't end up recursing.
|
71
|
+
@follows[token] = Set.new
|
72
|
+
set = Set.new
|
73
|
+
|
74
|
+
productions.each do |rule|
|
75
|
+
items = rule.items
|
76
|
+
i = 0
|
77
|
+
|
78
|
+
# Find all of the positions within the rule that our token
|
79
|
+
# occurs, and then increment that position by one.
|
80
|
+
while i < items.size
|
81
|
+
next i += 1 unless items[i] == token
|
82
|
+
position = i.succ
|
83
|
+
|
84
|
+
# Find the FIRST set of every item after our token, and
|
85
|
+
# put that in our set.
|
86
|
+
set.merge first(items[position..-1])
|
87
|
+
|
88
|
+
# If we're at the end of the rule...
|
89
|
+
if position == items.size || nullable?(items[position..-1])
|
90
|
+
# Then add the FOLLOW set of the left-hand side to our
|
91
|
+
# set.
|
92
|
+
set.merge follow(rule.label)
|
93
|
+
end
|
94
|
+
|
95
|
+
i += 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# ReplGrammar the cached empty set with our filled set.
|
100
|
+
@follows[token] = set
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -1,64 +1,64 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
module Antelope
|
4
|
-
module Generation
|
5
|
-
class Constructor
|
6
|
-
|
7
|
-
# Contains the methods to determine if an object is nullable.
|
8
|
-
module Nullable
|
9
|
-
|
10
|
-
# Initialize.
|
11
|
-
def initialize
|
12
|
-
@nullifying = Set.new
|
13
|
-
end
|
14
|
-
|
15
|
-
# Determine if a given token is nullable. This is how the
|
16
|
-
# method should behave:
|
17
|
-
#
|
18
|
-
# nullable?(ϵ) == true # if ϵ is the epsilon token
|
19
|
-
# nullable?(x) == false # if x is a terminal
|
20
|
-
# nullable?(αβ) == nullable?(α) && nullable?(β)
|
21
|
-
# nullable?(A) == nullable?(a_1) || nullable?(a_2) || ... nullable?(a_n)
|
22
|
-
# # if A is a nonterminal and a_1, a_2, ..., a_n are all
|
23
|
-
# # of the right-hand sides of its productions
|
24
|
-
#
|
25
|
-
# @param token [Grammar::Token, Array<Grammar::Token>] the token to
|
26
|
-
# check.
|
27
|
-
# @return [Boolean] if the token can reduce to ϵ.
|
28
|
-
def nullable?(token)
|
29
|
-
case token
|
30
|
-
when Grammar::Token::Nonterminal
|
31
|
-
nullifying(token) do
|
32
|
-
productions = grammar.productions[token.name]
|
33
|
-
!!productions.any? { |prod| nullable?(prod[:items]) }
|
34
|
-
end
|
35
|
-
when Array
|
36
|
-
token.dup.delete_if { |tok|
|
37
|
-
@nullifying.include?(tok) }.all? { |tok| nullable?(tok) }
|
38
|
-
when Grammar::Token::Epsilon
|
39
|
-
true
|
40
|
-
when Grammar::Token::Terminal
|
41
|
-
false
|
42
|
-
else
|
43
|
-
incorrect_argument! token, Grammar::Token, Array
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
# Helps keep track of the nonterminals we're checking for
|
50
|
-
# nullability. This helps prevent recursion.
|
51
|
-
#
|
52
|
-
# @param tok [Grammar::Token::Nonterminal]
|
53
|
-
# @yield once.
|
54
|
-
# @return [Boolean]
|
55
|
-
def nullifying(tok)
|
56
|
-
@nullifying << tok
|
57
|
-
out = yield
|
58
|
-
@nullifying.delete tok
|
59
|
-
out
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
module Generation
|
5
|
+
class Constructor
|
6
|
+
|
7
|
+
# Contains the methods to determine if an object is nullable.
|
8
|
+
module Nullable
|
9
|
+
|
10
|
+
# Initialize.
|
11
|
+
def initialize
|
12
|
+
@nullifying = Set.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Determine if a given token is nullable. This is how the
|
16
|
+
# method should behave:
|
17
|
+
#
|
18
|
+
# nullable?(ϵ) == true # if ϵ is the epsilon token
|
19
|
+
# nullable?(x) == false # if x is a terminal
|
20
|
+
# nullable?(αβ) == nullable?(α) && nullable?(β)
|
21
|
+
# nullable?(A) == nullable?(a_1) || nullable?(a_2) || ... nullable?(a_n)
|
22
|
+
# # if A is a nonterminal and a_1, a_2, ..., a_n are all
|
23
|
+
# # of the right-hand sides of its productions
|
24
|
+
#
|
25
|
+
# @param token [Grammar::Token, Array<Grammar::Token>] the token to
|
26
|
+
# check.
|
27
|
+
# @return [Boolean] if the token can reduce to ϵ.
|
28
|
+
def nullable?(token)
|
29
|
+
case token
|
30
|
+
when Grammar::Token::Nonterminal
|
31
|
+
nullifying(token) do
|
32
|
+
productions = grammar.productions[token.name]
|
33
|
+
!!productions.any? { |prod| nullable?(prod[:items]) }
|
34
|
+
end
|
35
|
+
when Array
|
36
|
+
token.dup.delete_if { |tok|
|
37
|
+
@nullifying.include?(tok) }.all? { |tok| nullable?(tok) }
|
38
|
+
when Grammar::Token::Epsilon
|
39
|
+
true
|
40
|
+
when Grammar::Token::Terminal
|
41
|
+
false
|
42
|
+
else
|
43
|
+
incorrect_argument! token, Grammar::Token, Array
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Helps keep track of the nonterminals we're checking for
|
50
|
+
# nullability. This helps prevent recursion.
|
51
|
+
#
|
52
|
+
# @param tok [Grammar::Token::Nonterminal]
|
53
|
+
# @yield once.
|
54
|
+
# @return [Boolean]
|
55
|
+
def nullifying(tok)
|
56
|
+
@nullifying << tok
|
57
|
+
out = yield
|
58
|
+
@nullifying.delete tok
|
59
|
+
out
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|