antelope 0.2.0 → 0.2.2
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 -23
- data/.rspec +3 -3
- data/.travis.yml +10 -9
- data/.yardopts +7 -7
- data/CONTRIBUTING.md +38 -38
- data/GENERATORS.md +124 -124
- data/Gemfile +7 -7
- data/LICENSE.txt +22 -22
- data/README.md +104 -104
- data/Rakefile +2 -2
- data/TODO.md +58 -58
- data/antelope.gemspec +28 -28
- data/bin/antelope +7 -7
- data/examples/deterministic.ace +35 -35
- data/examples/example.ace +51 -50
- data/examples/example.err +192 -0
- data/examples/{example.output → example.inf} +384 -385
- data/examples/liquidscript.ace +233 -162
- data/examples/simple.ace +22 -22
- data/lib/antelope/ace/compiler.rb +334 -334
- data/lib/antelope/ace/errors.rb +48 -48
- data/lib/antelope/ace/grammar/generation.rb +80 -80
- data/lib/antelope/ace/grammar/loading.rb +53 -53
- data/lib/antelope/ace/grammar/precedences.rb +68 -65
- data/lib/antelope/ace/grammar/productions.rb +156 -150
- data/lib/antelope/ace/grammar/symbols.rb +66 -66
- data/lib/antelope/ace/grammar.rb +69 -69
- data/lib/antelope/ace/precedence.rb +61 -61
- data/lib/antelope/ace/production.rb +57 -57
- 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 +177 -177
- data/lib/antelope/ace/scanner/third.rb +27 -27
- data/lib/antelope/ace/scanner.rb +134 -134
- data/lib/antelope/ace/token/epsilon.rb +24 -24
- data/lib/antelope/ace/token/error.rb +26 -26
- data/lib/antelope/ace/token/nonterminal.rb +17 -17
- data/lib/antelope/ace/token/terminal.rb +17 -17
- data/lib/antelope/ace/token.rb +238 -238
- data/lib/antelope/ace.rb +53 -53
- data/lib/antelope/cli.rb +55 -55
- data/lib/antelope/errors.rb +8 -8
- data/lib/antelope/generation/constructor/first.rb +88 -88
- data/lib/antelope/generation/constructor/follow.rb +103 -103
- data/lib/antelope/generation/constructor/nullable.rb +64 -64
- data/lib/antelope/generation/constructor.rb +126 -126
- 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 +130 -130
- data/lib/antelope/generation/recognizer.rb +180 -180
- data/lib/antelope/generation/tableizer.rb +175 -154
- data/lib/antelope/generation.rb +15 -15
- data/lib/antelope/generator/base.rb +264 -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 -0
- data/lib/antelope/generator/group.rb +57 -57
- data/lib/antelope/generator/html.rb +51 -0
- data/lib/antelope/generator/info.rb +47 -0
- data/lib/antelope/generator/null.rb +18 -18
- data/lib/antelope/generator/output.rb +17 -49
- data/lib/antelope/generator/ruby.rb +79 -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.ant +33 -0
- data/lib/antelope/generator/templates/html/antelope.css +1 -0
- data/lib/antelope/generator/templates/html/antelope.html +1 -0
- data/lib/antelope/generator/templates/html/antelope.js +1 -0
- data/lib/antelope/generator/templates/html/css.ant +53 -0
- data/lib/antelope/generator/templates/html/html.ant +82 -0
- data/lib/antelope/generator/templates/html/js.ant +9 -0
- data/lib/antelope/generator/templates/info.ant +53 -0
- data/lib/antelope/generator/templates/ruby.ant +178 -146
- data/lib/antelope/generator.rb +66 -63
- data/lib/antelope/template/compiler.rb +78 -78
- data/lib/antelope/template/errors.rb +9 -9
- data/lib/antelope/template/scanner.rb +109 -109
- data/lib/antelope/template.rb +65 -60
- data/lib/antelope/version.rb +6 -6
- data/lib/antelope.rb +13 -13
- data/optimizations.txt +42 -0
- data/spec/antelope/ace/compiler_spec.rb +60 -60
- data/spec/antelope/ace/scanner_spec.rb +27 -27
- data/spec/antelope/constructor_spec.rb +133 -136
- data/spec/antelope/template_spec.rb +50 -49
- 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 +15 -15
- data/subl/Ace (Ruby).JSON-tmLanguage +94 -94
- data/subl/Ace (Ruby).tmLanguage +153 -153
- metadata +17 -6
- data/lib/antelope/generator/templates/output.ant +0 -68
@@ -1,130 +1,130 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require "forwardable"
|
4
|
-
require "securerandom"
|
5
|
-
|
6
|
-
module Antelope
|
7
|
-
module Generation
|
8
|
-
class Recognizer
|
9
|
-
|
10
|
-
# A state within the parser. A state has a set of rules, as
|
11
|
-
# well as transitions on those rules.
|
12
|
-
class State
|
13
|
-
|
14
|
-
# All of the rules in this state.
|
15
|
-
#
|
16
|
-
# @return [Set<Rule>]
|
17
|
-
attr_reader :rules
|
18
|
-
|
19
|
-
# All of the transitions that can be made on this state.
|
20
|
-
#
|
21
|
-
# @return [Hash<(Symbol, State)>]
|
22
|
-
attr_reader :transitions
|
23
|
-
|
24
|
-
# The id of this state. This starts off as a string of
|
25
|
-
# hexadecmial characters, but after all of the states are
|
26
|
-
# finalized, this becomes a numeric.
|
27
|
-
#
|
28
|
-
# @return [String, Numeric]
|
29
|
-
attr_accessor :id
|
30
|
-
|
31
|
-
include Enumerable
|
32
|
-
extend Forwardable
|
33
|
-
|
34
|
-
def_delegator :@rules, :each
|
35
|
-
|
36
|
-
# Initialize the state.
|
37
|
-
def initialize
|
38
|
-
@rules = Set.new
|
39
|
-
@transitions = {}
|
40
|
-
@id =
|
41
|
-
end
|
42
|
-
|
43
|
-
# Gives a nice string representation of the state.
|
44
|
-
#
|
45
|
-
# @return [String]
|
46
|
-
def inspect
|
47
|
-
"#<#{self.class} id=#{id} " \
|
48
|
-
"transitions=[#{transitions.keys.join(", ")}] " \
|
49
|
-
"rules=[{#{rules.to_a.join("} {")}}]>"
|
50
|
-
end
|
51
|
-
|
52
|
-
# Merges another state with this state. It copies all of the
|
53
|
-
# rules into this state, and then merges the transitions on
|
54
|
-
# the given state to this state. It then returns self.
|
55
|
-
#
|
56
|
-
# @raise [ArgumentError] if the given argument is not a state.
|
57
|
-
# @param other [State] the state to merge.
|
58
|
-
# @return [self]
|
59
|
-
def merge!(other)
|
60
|
-
raise ArgumentError, "Expected #{self.class}, " \
|
61
|
-
"got #{other.class}" unless other.is_a? State
|
62
|
-
|
63
|
-
self << other
|
64
|
-
self.transitions.merge! other.transitions
|
65
|
-
|
66
|
-
self
|
67
|
-
end
|
68
|
-
|
69
|
-
# Finds the rule that match the given production. It
|
70
|
-
# uses fuzzy equality checking. It returns the first rule
|
71
|
-
# that matches.
|
72
|
-
#
|
73
|
-
# @param production [Rule] the rule to compare.
|
74
|
-
# @return [Rule?]
|
75
|
-
def rule_for(production)
|
76
|
-
rules.find { |rule| production === rule }
|
77
|
-
end
|
78
|
-
|
79
|
-
# Appends the given object to this state. The given object
|
80
|
-
# must be a state or a rule. If it's a state, it appends all
|
81
|
-
# of the rules in the state to this state. If it's a rule, it
|
82
|
-
# adds the rule to our rules.
|
83
|
-
#
|
84
|
-
# @raise [ArgumentError] if the argument isn't a {State} or a
|
85
|
-
# {Rule}.
|
86
|
-
# @param rule [State, Rule] the object to append.
|
87
|
-
# @return [self]
|
88
|
-
def <<(rule)
|
89
|
-
case rule
|
90
|
-
when State
|
91
|
-
rule.rules.map(&:clone).each { |r| self << r }
|
92
|
-
when Rule
|
93
|
-
rules << rule #unless rules.include? rule
|
94
|
-
when Array, Set
|
95
|
-
rule.each do |part|
|
96
|
-
self << part
|
97
|
-
end
|
98
|
-
else
|
99
|
-
raise ArgumentError, "Expected State or Rule, " \
|
100
|
-
"got #{rule.class}"
|
101
|
-
end
|
102
|
-
|
103
|
-
self
|
104
|
-
end
|
105
|
-
|
106
|
-
alias_method :push, :<<
|
107
|
-
|
108
|
-
# Check to see if this state is fuzzily equivalent to another
|
109
|
-
# state. It does this by checking if the transitions are
|
110
|
-
# equivalent, and then that the rules are fuzzily equivalent.
|
111
|
-
# Ideally, the method is commutative; that is,
|
112
|
-
# `(a === b) == (b === a)`.
|
113
|
-
#
|
114
|
-
# @param other [State] the state to check.
|
115
|
-
# @return [Boolean]
|
116
|
-
# @see Rule#===
|
117
|
-
def ===(other)
|
118
|
-
return super unless other.is_a? State
|
119
|
-
|
120
|
-
other_rules = other.rules.to_a
|
121
|
-
other.transitions == transitions &&
|
122
|
-
rules.size == other_rules.size &&
|
123
|
-
rules.each_with_index.
|
124
|
-
all? { |rule, i| rule === other_rules[i] }
|
125
|
-
end
|
126
|
-
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
require "securerandom"
|
5
|
+
|
6
|
+
module Antelope
|
7
|
+
module Generation
|
8
|
+
class Recognizer
|
9
|
+
|
10
|
+
# A state within the parser. A state has a set of rules, as
|
11
|
+
# well as transitions on those rules.
|
12
|
+
class State
|
13
|
+
|
14
|
+
# All of the rules in this state.
|
15
|
+
#
|
16
|
+
# @return [Set<Rule>]
|
17
|
+
attr_reader :rules
|
18
|
+
|
19
|
+
# All of the transitions that can be made on this state.
|
20
|
+
#
|
21
|
+
# @return [Hash<(Symbol, State)>]
|
22
|
+
attr_reader :transitions
|
23
|
+
|
24
|
+
# The id of this state. This starts off as a string of
|
25
|
+
# hexadecmial characters, but after all of the states are
|
26
|
+
# finalized, this becomes a numeric.
|
27
|
+
#
|
28
|
+
# @return [String, Numeric]
|
29
|
+
attr_accessor :id
|
30
|
+
|
31
|
+
include Enumerable
|
32
|
+
extend Forwardable
|
33
|
+
|
34
|
+
def_delegator :@rules, :each
|
35
|
+
|
36
|
+
# Initialize the state.
|
37
|
+
def initialize
|
38
|
+
@rules = Set.new
|
39
|
+
@transitions = {}
|
40
|
+
@id = "%10x" % object_id
|
41
|
+
end
|
42
|
+
|
43
|
+
# Gives a nice string representation of the state.
|
44
|
+
#
|
45
|
+
# @return [String]
|
46
|
+
def inspect
|
47
|
+
"#<#{self.class} id=#{id} " \
|
48
|
+
"transitions=[#{transitions.keys.join(", ")}] " \
|
49
|
+
"rules=[{#{rules.to_a.join("} {")}}]>"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Merges another state with this state. It copies all of the
|
53
|
+
# rules into this state, and then merges the transitions on
|
54
|
+
# the given state to this state. It then returns self.
|
55
|
+
#
|
56
|
+
# @raise [ArgumentError] if the given argument is not a state.
|
57
|
+
# @param other [State] the state to merge.
|
58
|
+
# @return [self]
|
59
|
+
def merge!(other)
|
60
|
+
raise ArgumentError, "Expected #{self.class}, " \
|
61
|
+
"got #{other.class}" unless other.is_a? State
|
62
|
+
|
63
|
+
self << other
|
64
|
+
self.transitions.merge! other.transitions
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Finds the rule that match the given production. It
|
70
|
+
# uses fuzzy equality checking. It returns the first rule
|
71
|
+
# that matches.
|
72
|
+
#
|
73
|
+
# @param production [Rule] the rule to compare.
|
74
|
+
# @return [Rule?]
|
75
|
+
def rule_for(production)
|
76
|
+
rules.find { |rule| production === rule }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Appends the given object to this state. The given object
|
80
|
+
# must be a state or a rule. If it's a state, it appends all
|
81
|
+
# of the rules in the state to this state. If it's a rule, it
|
82
|
+
# adds the rule to our rules.
|
83
|
+
#
|
84
|
+
# @raise [ArgumentError] if the argument isn't a {State} or a
|
85
|
+
# {Rule}.
|
86
|
+
# @param rule [State, Rule] the object to append.
|
87
|
+
# @return [self]
|
88
|
+
def <<(rule)
|
89
|
+
case rule
|
90
|
+
when State
|
91
|
+
rule.rules.map(&:clone).each { |r| self << r }
|
92
|
+
when Rule
|
93
|
+
rules << rule #unless rules.include? rule
|
94
|
+
when Array, Set
|
95
|
+
rule.each do |part|
|
96
|
+
self << part
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise ArgumentError, "Expected State or Rule, " \
|
100
|
+
"got #{rule.class}"
|
101
|
+
end
|
102
|
+
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
alias_method :push, :<<
|
107
|
+
|
108
|
+
# Check to see if this state is fuzzily equivalent to another
|
109
|
+
# state. It does this by checking if the transitions are
|
110
|
+
# equivalent, and then that the rules are fuzzily equivalent.
|
111
|
+
# Ideally, the method is commutative; that is,
|
112
|
+
# `(a === b) == (b === a)`.
|
113
|
+
#
|
114
|
+
# @param other [State] the state to check.
|
115
|
+
# @return [Boolean]
|
116
|
+
# @see Rule#===
|
117
|
+
def ===(other)
|
118
|
+
return super unless other.is_a? State
|
119
|
+
|
120
|
+
other_rules = other.rules.to_a
|
121
|
+
other.transitions == transitions &&
|
122
|
+
rules.size == other_rules.size &&
|
123
|
+
rules.each_with_index.
|
124
|
+
all? { |rule, i| rule === other_rules[i] }
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -1,180 +1,180 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require "antelope/generation/recognizer/rule"
|
4
|
-
require "antelope/generation/recognizer/state"
|
5
|
-
|
6
|
-
module Antelope
|
7
|
-
module Generation
|
8
|
-
|
9
|
-
# Recognizes all of the states in the grammar.
|
10
|
-
#
|
11
|
-
# @see http://redjazz96.tumblr.com/post/88446352960
|
12
|
-
class Recognizer
|
13
|
-
|
14
|
-
# A list of all of the states in the grammar.
|
15
|
-
#
|
16
|
-
# @return [Set<State>]
|
17
|
-
attr_reader :states
|
18
|
-
|
19
|
-
# The initial state. This is the state that is constructed from
|
20
|
-
# the rule with the left-hand side being `$start`.
|
21
|
-
#
|
22
|
-
# @return [State]
|
23
|
-
attr_reader :start
|
24
|
-
|
25
|
-
# The grammar that the recognizer is running off of.
|
26
|
-
#
|
27
|
-
# @return [Ace::Grammar]
|
28
|
-
attr_reader :grammar
|
29
|
-
|
30
|
-
# Initialize the recognizer.
|
31
|
-
#
|
32
|
-
# @param grammar [Ace::Grammar]
|
33
|
-
def initialize(grammar)
|
34
|
-
@grammar = grammar
|
35
|
-
@states = Set.new
|
36
|
-
@map = {}
|
37
|
-
end
|
38
|
-
|
39
|
-
# Runs the recognizer. After all states have been created, it
|
40
|
-
# resets the state ids into a more friendly form (they were
|
41
|
-
# originally hexadecimal, see {State#initialize}), and then
|
42
|
-
# resets the rule ids in each state into a more friendly form
|
43
|
-
# (they were also originally hexadecmial, see {Rule#initialize}
|
44
|
-
# ).
|
45
|
-
#
|
46
|
-
# @see #compute_initial_state
|
47
|
-
# @return [void]
|
48
|
-
def call
|
49
|
-
@states = Set.new
|
50
|
-
@start = compute_initial_state
|
51
|
-
redefine_state_ids
|
52
|
-
redefine_rule_ids
|
53
|
-
grammar.states = states
|
54
|
-
end
|
55
|
-
|
56
|
-
# Computes the initial state. Starting with the default
|
57
|
-
# production of `$start`, it then generates the whole state
|
58
|
-
# and then the spawned states from it.
|
59
|
-
#
|
60
|
-
# @return [State]
|
61
|
-
def compute_initial_state
|
62
|
-
production = grammar.productions[:$start][0]
|
63
|
-
rule = Rule.new(production, 0)
|
64
|
-
compute_whole_state(rule)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Computes the entire initial state from the initial rule.
|
68
|
-
# It starts with a blank state, adds the initial rule to it, and
|
69
|
-
# then generates the closure for that state; it then computes
|
70
|
-
# the rest of the states in the grammar.
|
71
|
-
#
|
72
|
-
# @param rule [Rule] the initial rule.
|
73
|
-
# @return [State]
|
74
|
-
def compute_whole_state(rule)
|
75
|
-
state = State.new
|
76
|
-
state << rule
|
77
|
-
compute_closure(state)
|
78
|
-
states << state
|
79
|
-
compute_states
|
80
|
-
state
|
81
|
-
end
|
82
|
-
|
83
|
-
# Computes all states. Uses a fix point iteration to determine
|
84
|
-
# when no states have been added. Loops through every state and
|
85
|
-
# every rule, looking for rules that have an active nonterminal
|
86
|
-
# and computing the closure for said rule.
|
87
|
-
#
|
88
|
-
# @return [void]
|
89
|
-
# @see #compute_closure
|
90
|
-
def compute_states
|
91
|
-
fixed_point(states) do
|
92
|
-
states.dup.each do |state|
|
93
|
-
compute_gotos(state)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# Given a state, it does a fixed point iteration on the rules of
|
99
|
-
# the state that have an active nonterminal, and add the
|
100
|
-
# corresponding production rules to the state.
|
101
|
-
#
|
102
|
-
# @return [void]
|
103
|
-
def compute_closure(state)
|
104
|
-
fixed_point(state.rules) do
|
105
|
-
state.rules.select { |_| _.active.nonterminal? }.each do |rule|
|
106
|
-
grammar.productions[rule.active.name].each do |prod|
|
107
|
-
state << Rule.new(prod, 0)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def compute_gotos(state)
|
114
|
-
actives = state.rules.map(&:active).select(&:name)
|
115
|
-
|
116
|
-
actives.each do |active|
|
117
|
-
next if state.transitions[active.name]
|
118
|
-
rules = state.rules.
|
119
|
-
select { |r| r.active == active && r.succ? }.
|
120
|
-
map(&:succ).to_set
|
121
|
-
s = states.find { |st| rules.subset? st.rules } || begin
|
122
|
-
s = State.new << rules
|
123
|
-
compute_closure(s)
|
124
|
-
states << s
|
125
|
-
s
|
126
|
-
end
|
127
|
-
|
128
|
-
state.transitions[active.name] = s
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
private
|
133
|
-
|
134
|
-
# Changes the IDs of the states into a more friendly format.
|
135
|
-
#
|
136
|
-
# @return [void]
|
137
|
-
def redefine_state_ids
|
138
|
-
states.each_with_index do |state, i|
|
139
|
-
state.id = i
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
# Redefines all of the rule ids to make them more friendly.
|
144
|
-
# Every rule in every state is given a unique ID, reguardless if
|
145
|
-
# the rules are equivalent.
|
146
|
-
#
|
147
|
-
# @return [void]
|
148
|
-
def redefine_rule_ids
|
149
|
-
start = 0
|
150
|
-
|
151
|
-
states.each do |state|
|
152
|
-
state.rules.each do |rule|
|
153
|
-
rule.id = start
|
154
|
-
start += 1
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# Begins a fixed point iteration on the given enumerable. It
|
160
|
-
# initializes the added elements to one; then, while the number
|
161
|
-
# of added elements is not zero, it yields and checks for added
|
162
|
-
# elements.
|
163
|
-
#
|
164
|
-
# @param enum [Enumerable]
|
165
|
-
# @yield for every iteration. Guarenteed to do so at least
|
166
|
-
# once.
|
167
|
-
# @return [void]
|
168
|
-
def fixed_point(enum)
|
169
|
-
added = 1
|
170
|
-
|
171
|
-
until added.zero?
|
172
|
-
added = enum.size
|
173
|
-
yield
|
174
|
-
added = enum.size - added
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "antelope/generation/recognizer/rule"
|
4
|
+
require "antelope/generation/recognizer/state"
|
5
|
+
|
6
|
+
module Antelope
|
7
|
+
module Generation
|
8
|
+
|
9
|
+
# Recognizes all of the states in the grammar.
|
10
|
+
#
|
11
|
+
# @see http://redjazz96.tumblr.com/post/88446352960
|
12
|
+
class Recognizer
|
13
|
+
|
14
|
+
# A list of all of the states in the grammar.
|
15
|
+
#
|
16
|
+
# @return [Set<State>]
|
17
|
+
attr_reader :states
|
18
|
+
|
19
|
+
# The initial state. This is the state that is constructed from
|
20
|
+
# the rule with the left-hand side being `$start`.
|
21
|
+
#
|
22
|
+
# @return [State]
|
23
|
+
attr_reader :start
|
24
|
+
|
25
|
+
# The grammar that the recognizer is running off of.
|
26
|
+
#
|
27
|
+
# @return [Ace::Grammar]
|
28
|
+
attr_reader :grammar
|
29
|
+
|
30
|
+
# Initialize the recognizer.
|
31
|
+
#
|
32
|
+
# @param grammar [Ace::Grammar]
|
33
|
+
def initialize(grammar)
|
34
|
+
@grammar = grammar
|
35
|
+
@states = Set.new
|
36
|
+
@map = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Runs the recognizer. After all states have been created, it
|
40
|
+
# resets the state ids into a more friendly form (they were
|
41
|
+
# originally hexadecimal, see {State#initialize}), and then
|
42
|
+
# resets the rule ids in each state into a more friendly form
|
43
|
+
# (they were also originally hexadecmial, see {Rule#initialize}
|
44
|
+
# ).
|
45
|
+
#
|
46
|
+
# @see #compute_initial_state
|
47
|
+
# @return [void]
|
48
|
+
def call
|
49
|
+
@states = Set.new
|
50
|
+
@start = compute_initial_state
|
51
|
+
redefine_state_ids
|
52
|
+
redefine_rule_ids
|
53
|
+
grammar.states = states
|
54
|
+
end
|
55
|
+
|
56
|
+
# Computes the initial state. Starting with the default
|
57
|
+
# production of `$start`, it then generates the whole state
|
58
|
+
# and then the spawned states from it.
|
59
|
+
#
|
60
|
+
# @return [State]
|
61
|
+
def compute_initial_state
|
62
|
+
production = grammar.productions[:$start][0]
|
63
|
+
rule = Rule.new(production, 0)
|
64
|
+
compute_whole_state(rule)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Computes the entire initial state from the initial rule.
|
68
|
+
# It starts with a blank state, adds the initial rule to it, and
|
69
|
+
# then generates the closure for that state; it then computes
|
70
|
+
# the rest of the states in the grammar.
|
71
|
+
#
|
72
|
+
# @param rule [Rule] the initial rule.
|
73
|
+
# @return [State]
|
74
|
+
def compute_whole_state(rule)
|
75
|
+
state = State.new
|
76
|
+
state << rule
|
77
|
+
compute_closure(state)
|
78
|
+
states << state
|
79
|
+
compute_states
|
80
|
+
state
|
81
|
+
end
|
82
|
+
|
83
|
+
# Computes all states. Uses a fix point iteration to determine
|
84
|
+
# when no states have been added. Loops through every state and
|
85
|
+
# every rule, looking for rules that have an active nonterminal
|
86
|
+
# and computing the closure for said rule.
|
87
|
+
#
|
88
|
+
# @return [void]
|
89
|
+
# @see #compute_closure
|
90
|
+
def compute_states
|
91
|
+
fixed_point(states) do
|
92
|
+
states.dup.each do |state|
|
93
|
+
compute_gotos(state)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Given a state, it does a fixed point iteration on the rules of
|
99
|
+
# the state that have an active nonterminal, and add the
|
100
|
+
# corresponding production rules to the state.
|
101
|
+
#
|
102
|
+
# @return [void]
|
103
|
+
def compute_closure(state)
|
104
|
+
fixed_point(state.rules) do
|
105
|
+
state.rules.select { |_| _.active.nonterminal? }.each do |rule|
|
106
|
+
grammar.productions[rule.active.name].each do |prod|
|
107
|
+
state << Rule.new(prod, 0)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def compute_gotos(state)
|
114
|
+
actives = state.rules.map(&:active).select(&:name)
|
115
|
+
|
116
|
+
actives.each do |active|
|
117
|
+
next if state.transitions[active.name]
|
118
|
+
rules = state.rules.
|
119
|
+
select { |r| r.active == active && r.succ? }.
|
120
|
+
map(&:succ).to_set
|
121
|
+
s = states.find { |st| rules.subset? st.rules } || begin
|
122
|
+
s = State.new << rules
|
123
|
+
compute_closure(s)
|
124
|
+
states << s
|
125
|
+
s
|
126
|
+
end
|
127
|
+
|
128
|
+
state.transitions[active.name] = s
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
# Changes the IDs of the states into a more friendly format.
|
135
|
+
#
|
136
|
+
# @return [void]
|
137
|
+
def redefine_state_ids
|
138
|
+
states.each_with_index do |state, i|
|
139
|
+
state.id = i
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Redefines all of the rule ids to make them more friendly.
|
144
|
+
# Every rule in every state is given a unique ID, reguardless if
|
145
|
+
# the rules are equivalent.
|
146
|
+
#
|
147
|
+
# @return [void]
|
148
|
+
def redefine_rule_ids
|
149
|
+
start = 0
|
150
|
+
|
151
|
+
states.each do |state|
|
152
|
+
state.rules.each do |rule|
|
153
|
+
rule.id = start
|
154
|
+
start += 1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Begins a fixed point iteration on the given enumerable. It
|
160
|
+
# initializes the added elements to one; then, while the number
|
161
|
+
# of added elements is not zero, it yields and checks for added
|
162
|
+
# elements.
|
163
|
+
#
|
164
|
+
# @param enum [Enumerable]
|
165
|
+
# @yield for every iteration. Guarenteed to do so at least
|
166
|
+
# once.
|
167
|
+
# @return [void]
|
168
|
+
def fixed_point(enum)
|
169
|
+
added = 1
|
170
|
+
|
171
|
+
until added.zero?
|
172
|
+
added = enum.size
|
173
|
+
yield
|
174
|
+
added = enum.size - added
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|