antelope 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.yardopts +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/antelope.gemspec +30 -0
- data/bin/antelope +24 -0
- data/examples/deterministic.ace +27 -0
- data/examples/deterministic.output +229 -0
- data/examples/example.ace +45 -0
- data/examples/example.output +610 -0
- data/examples/simple.ace +26 -0
- data/examples/simple.output +194 -0
- data/lib/antelope/ace/compiler.rb +290 -0
- data/lib/antelope/ace/errors.rb +27 -0
- data/lib/antelope/ace/grammar/generation.rb +47 -0
- data/lib/antelope/ace/grammar/loading.rb +51 -0
- data/lib/antelope/ace/grammar/presidence.rb +59 -0
- data/lib/antelope/ace/grammar/production.rb +47 -0
- data/lib/antelope/ace/grammar/productions.rb +119 -0
- data/lib/antelope/ace/grammar/terminals.rb +41 -0
- data/lib/antelope/ace/grammar.rb +59 -0
- data/lib/antelope/ace/presidence.rb +51 -0
- data/lib/antelope/ace/scanner/first.rb +61 -0
- data/lib/antelope/ace/scanner/second.rb +160 -0
- data/lib/antelope/ace/scanner/third.rb +25 -0
- data/lib/antelope/ace/scanner.rb +110 -0
- data/lib/antelope/ace/token/epsilon.rb +22 -0
- data/lib/antelope/ace/token/error.rb +24 -0
- data/lib/antelope/ace/token/nonterminal.rb +15 -0
- data/lib/antelope/ace/token/terminal.rb +15 -0
- data/lib/antelope/ace/token.rb +171 -0
- data/lib/antelope/ace.rb +50 -0
- data/lib/antelope/automaton.rb +36 -0
- data/lib/antelope/generation/conflictor/conflict.rb +7 -0
- data/lib/antelope/generation/conflictor.rb +45 -0
- data/lib/antelope/generation/constructor/first.rb +52 -0
- data/lib/antelope/generation/constructor/follow.rb +46 -0
- data/lib/antelope/generation/constructor/lookahead.rb +42 -0
- data/lib/antelope/generation/constructor/nullable.rb +40 -0
- data/lib/antelope/generation/constructor.rb +81 -0
- data/lib/antelope/generation/recognizer/rule.rb +93 -0
- data/lib/antelope/generation/recognizer/state.rb +56 -0
- data/lib/antelope/generation/recognizer.rb +152 -0
- data/lib/antelope/generation/tableizer.rb +80 -0
- data/lib/antelope/generation.rb +12 -0
- data/lib/antelope/generator/output.rb +30 -0
- data/lib/antelope/generator/ruby.rb +57 -0
- data/lib/antelope/generator/templates/output.erb +49 -0
- data/lib/antelope/generator/templates/ruby.erb +62 -0
- data/lib/antelope/generator.rb +84 -0
- data/lib/antelope/version.rb +4 -0
- data/lib/antelope.rb +9 -0
- data/spec/antelope/ace/compiler_spec.rb +50 -0
- data/spec/antelope/ace/scanner_spec.rb +27 -0
- data/spec/antelope/automaton_spec.rb +29 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/benchmark_helper.rb +5 -0
- metadata +223 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
module Generation
|
5
|
+
class Recognizer
|
6
|
+
class Rule
|
7
|
+
|
8
|
+
attr_reader :left
|
9
|
+
attr_reader :right
|
10
|
+
attr_reader :position
|
11
|
+
attr_reader :block
|
12
|
+
attr_accessor :lookahead
|
13
|
+
attr_accessor :id
|
14
|
+
attr_accessor :presidence
|
15
|
+
attr_reader :production
|
16
|
+
|
17
|
+
include Comparable
|
18
|
+
|
19
|
+
def initialize(production, position, inherited = false)
|
20
|
+
@left = production.label
|
21
|
+
@position = position
|
22
|
+
@lookahead = Set.new
|
23
|
+
@presidence = production.prec
|
24
|
+
@production = production
|
25
|
+
@block = production.block
|
26
|
+
@id = SecureRandom.hex
|
27
|
+
|
28
|
+
if inherited
|
29
|
+
@right = inherited
|
30
|
+
else
|
31
|
+
@right = production.items.map(&:dup).freeze
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"#<#{self.class} id=#{id} left=#{left} right=[#{right.join(" ")}] position=#{position}>"
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s(dot = true)
|
40
|
+
"#{id}/#{presidence.type.to_s[0]}#{presidence.level}: #{left} → #{right[0, position].join(" ")}#{" • " if dot}#{right[position..-1].join(" ")}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def active
|
44
|
+
right[position] or Ace::Token.new(nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
def succ
|
48
|
+
Rule.new(production, position + 1)
|
49
|
+
end
|
50
|
+
|
51
|
+
def succ?
|
52
|
+
right.size > (position)
|
53
|
+
end
|
54
|
+
|
55
|
+
def final?
|
56
|
+
!succ?
|
57
|
+
end
|
58
|
+
|
59
|
+
def <=>(other)
|
60
|
+
if other.is_a? Rule
|
61
|
+
to_a <=> other.to_a
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def without_transitions
|
68
|
+
@_without_transitions ||=
|
69
|
+
Rule.new(production, position)
|
70
|
+
end
|
71
|
+
|
72
|
+
def ===(other)
|
73
|
+
if other.is_a? Rule
|
74
|
+
left === other.left and right.each_with_index.
|
75
|
+
all? { |e, i| e === other.right[i] }
|
76
|
+
else
|
77
|
+
super
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def hash
|
82
|
+
to_a.hash
|
83
|
+
end
|
84
|
+
|
85
|
+
alias_method :eql?, :==
|
86
|
+
|
87
|
+
def to_a
|
88
|
+
[left, right, position]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
module Antelope
|
5
|
+
module Generation
|
6
|
+
class Recognizer
|
7
|
+
class State
|
8
|
+
|
9
|
+
attr_reader :rules
|
10
|
+
attr_reader :transitions
|
11
|
+
attr_accessor :id
|
12
|
+
|
13
|
+
include Enumerable
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def_delegator :@rules, :each
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@rules = Set.new
|
20
|
+
@transitions = {} #Hash.new { |hash, key| hash[key] = State.new }
|
21
|
+
@id = SecureRandom.hex
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"#<#{self.class} id=#{id} transitions=[#{transitions.keys.join(", ")}] rules=[{#{rules.to_a.join("} {")}}]>"
|
26
|
+
end
|
27
|
+
|
28
|
+
def merge!(other)
|
29
|
+
return if other == :_ignore
|
30
|
+
raise ArgumentError, "Expected #{self.class}, " +
|
31
|
+
"got #{other.class}" unless other.is_a? State
|
32
|
+
|
33
|
+
self << other
|
34
|
+
self.transitions.merge! other.transitions
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def rule_for(production)
|
40
|
+
rules.find { |rule| production === rule }
|
41
|
+
end
|
42
|
+
|
43
|
+
def <<(rule)
|
44
|
+
if rule.is_a? State
|
45
|
+
rule.rules.each { |r| self << r }
|
46
|
+
else
|
47
|
+
rules << rule unless rules.include? rule
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :push, :<<
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require "antelope/generation/recognizer/rule"
|
2
|
+
require "antelope/generation/recognizer/state"
|
3
|
+
|
4
|
+
module Antelope
|
5
|
+
module Generation
|
6
|
+
|
7
|
+
# Recognizes all of the states in the grammar.
|
8
|
+
#
|
9
|
+
# @see http://redjazz96.tumblr.com/post/88446352960
|
10
|
+
class Recognizer
|
11
|
+
|
12
|
+
# A list of all of the states in the grammar.
|
13
|
+
#
|
14
|
+
# @return [Set<State>]
|
15
|
+
attr_reader :states
|
16
|
+
|
17
|
+
# The initial state. This is the state that is constructed from
|
18
|
+
# the rule with the left-hand side being `$start`.
|
19
|
+
#
|
20
|
+
# @return [State]
|
21
|
+
attr_reader :start
|
22
|
+
|
23
|
+
# The grammar that the recognizer is running off of.
|
24
|
+
#
|
25
|
+
# @return [Ace::Grammar]
|
26
|
+
attr_reader :grammar
|
27
|
+
|
28
|
+
# Initialize the recognizer.
|
29
|
+
#
|
30
|
+
# @param grammar [Ace::Grammar]
|
31
|
+
def initialize(grammar)
|
32
|
+
@grammar = grammar
|
33
|
+
@states = Set.new
|
34
|
+
end
|
35
|
+
|
36
|
+
# Runs the recognizer. After all states have been created, it
|
37
|
+
# resets the state ids into a more friendly form (they were
|
38
|
+
# originally hexadecimal, see {State#initialize}), and then
|
39
|
+
# resets the rule ids in each state into a more friendly form
|
40
|
+
# (they were also originally hexadecmial, see {Rule#initialize}
|
41
|
+
# ).
|
42
|
+
#
|
43
|
+
# @see #compute_initial_state
|
44
|
+
# @return [void]
|
45
|
+
def call
|
46
|
+
@states = Set.new
|
47
|
+
@start = compute_initial_state
|
48
|
+
redefine_state_ids
|
49
|
+
redefine_rule_ids
|
50
|
+
grammar.states = states
|
51
|
+
end
|
52
|
+
|
53
|
+
# Computes the initial state. Starting with the default
|
54
|
+
# production of `$start`, it then generates the whole state
|
55
|
+
# and then the spawned states from it.
|
56
|
+
#
|
57
|
+
# @return [State]
|
58
|
+
def compute_initial_state
|
59
|
+
production = grammar.productions[:$start][0]
|
60
|
+
rule = Rule.new(production, 0)
|
61
|
+
compute_whole_state(rule)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Computes the entire initial state from the initial rule.
|
65
|
+
# It starts with a blank state, adds the initial rule to it, and
|
66
|
+
# then generates the closure for that state; it then computes
|
67
|
+
# the rest of the states in the grammar.
|
68
|
+
#
|
69
|
+
# @param rule [Rule] the initial rule.
|
70
|
+
# @return [State]
|
71
|
+
def compute_whole_state(rule)
|
72
|
+
state = State.new
|
73
|
+
state << rule
|
74
|
+
compute_closure(state)
|
75
|
+
states << state
|
76
|
+
compute_states
|
77
|
+
state
|
78
|
+
end
|
79
|
+
|
80
|
+
# Computes all states. Uses a fix point iteration to determine
|
81
|
+
# when no states have been added. Loops through every state and
|
82
|
+
# every rule, looking for rules that have an active nonterminal
|
83
|
+
# and computing
|
84
|
+
def compute_states
|
85
|
+
fixed_point(states) do
|
86
|
+
states.dup.each do |state|
|
87
|
+
state.rules.each do |rule|
|
88
|
+
next unless rule.succ?
|
89
|
+
transitional = find_state_for(rule.succ) do |succ|
|
90
|
+
ns = State.new
|
91
|
+
ns << succ
|
92
|
+
compute_closure(ns)
|
93
|
+
states << ns
|
94
|
+
ns
|
95
|
+
end
|
96
|
+
|
97
|
+
if state.transitions[rule.active.name]
|
98
|
+
state.transitions[rule.active.name].merge! transitional
|
99
|
+
else
|
100
|
+
state.transitions[rule.active.name] = transitional
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def compute_closure(state)
|
108
|
+
fixed_point(state.rules) do
|
109
|
+
state.rules.select { |_| _.active.nonterminal? }.each do |rule|
|
110
|
+
grammar.productions[rule.active.name].each do |prod|
|
111
|
+
state << Rule.new(prod, 0)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def find_state_for(rule)
|
120
|
+
states.find { |state| state.include?(rule) } or yield(rule)
|
121
|
+
end
|
122
|
+
|
123
|
+
def redefine_state_ids
|
124
|
+
states.each_with_index do |state, i|
|
125
|
+
state.id = i
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def redefine_rule_ids
|
130
|
+
start = 0
|
131
|
+
|
132
|
+
states.each do |state|
|
133
|
+
state.rules.each do |rule|
|
134
|
+
rule.id = start
|
135
|
+
start += 1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def fixed_point(enum)
|
141
|
+
added = 1
|
142
|
+
|
143
|
+
until added.zero?
|
144
|
+
added = enum.size
|
145
|
+
yield
|
146
|
+
added = enum.size - added
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Antelope
|
2
|
+
module Generation
|
3
|
+
|
4
|
+
class UnresolvableConflictError < StandardError; end
|
5
|
+
|
6
|
+
class Tableizer
|
7
|
+
|
8
|
+
attr_accessor :parser
|
9
|
+
attr_accessor :table
|
10
|
+
attr_accessor :rules
|
11
|
+
|
12
|
+
def initialize(parser)
|
13
|
+
@parser = parser
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
tablize
|
18
|
+
conflictize
|
19
|
+
end
|
20
|
+
|
21
|
+
def tablize
|
22
|
+
@table = Array.new(parser.states.size) do
|
23
|
+
Hash.new { |h, k| h[k] = [] }
|
24
|
+
end
|
25
|
+
@rules = []
|
26
|
+
|
27
|
+
parser.states.each do |state|
|
28
|
+
state.transitions.each do |on, to|
|
29
|
+
table[state.id][on] << [:state, to.id]
|
30
|
+
end
|
31
|
+
|
32
|
+
state.rules.each do |rule|
|
33
|
+
@rules[rule.id] = rule
|
34
|
+
if rule.final?
|
35
|
+
rule.lookahead.each do |look|
|
36
|
+
table[state.id][look.name] <<
|
37
|
+
[:reduce, rule.production.id]
|
38
|
+
end
|
39
|
+
|
40
|
+
if rule.production.id.zero?
|
41
|
+
table[state.id][:"$"] = [[:accept, rule.production.id]]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
table
|
48
|
+
end
|
49
|
+
|
50
|
+
def conflictize
|
51
|
+
@table.each_with_index do |v, state|
|
52
|
+
v.each do |on, data|
|
53
|
+
if data.size == 1
|
54
|
+
@table[state][on] = data[0]
|
55
|
+
next
|
56
|
+
end
|
57
|
+
|
58
|
+
terminal = parser.presidence_for(on)
|
59
|
+
|
60
|
+
state_part = data.select { |(t, d)| t == :state }.first
|
61
|
+
rule_part = data.select { |(t, d)| t == :reduce}.first
|
62
|
+
|
63
|
+
result = @rules[rule_part[1]].presidence <=> terminal
|
64
|
+
|
65
|
+
case result
|
66
|
+
when 0
|
67
|
+
p v, terminal, @rules[rule_part[1]].presidence
|
68
|
+
raise UnresolvableConflictError,
|
69
|
+
"Could not determine move for #{on} in state #{state}"
|
70
|
+
when 1
|
71
|
+
@table[state][on] = rule_part
|
72
|
+
when -1
|
73
|
+
@table[state][on] = state_part
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "antelope/generation/conflictor"
|
2
|
+
require "antelope/generation/constructor"
|
3
|
+
require "antelope/generation/recognizer"
|
4
|
+
require "antelope/generation/tableizer"
|
5
|
+
|
6
|
+
module Antelope
|
7
|
+
|
8
|
+
# Contains the generation mods.
|
9
|
+
module Generation
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "pp"
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
class Generator
|
5
|
+
|
6
|
+
# Generates an output file, mainly for debugging. Included always
|
7
|
+
# as a generator for a grammar.
|
8
|
+
class Output < Generator
|
9
|
+
|
10
|
+
# Defines singleton method for every mod that the grammar passed
|
11
|
+
# to the generator.
|
12
|
+
#
|
13
|
+
# @see Generator#initialize
|
14
|
+
def initialize(*)
|
15
|
+
super
|
16
|
+
mods.each do |k, v|
|
17
|
+
define_singleton_method (k) { v }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Actually performs the generation. Uses the template in
|
22
|
+
# output.erb, and generates the file `<file>.output`.
|
23
|
+
#
|
24
|
+
# @return [void]
|
25
|
+
def generate
|
26
|
+
template "output.erb", "#{file}.output"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "pp"
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
class Generator
|
5
|
+
|
6
|
+
# Generates a ruby parser.
|
7
|
+
class Ruby < Generator
|
8
|
+
|
9
|
+
# Creates an action table for the parser.
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
def generate_action_table
|
13
|
+
out = ""
|
14
|
+
PP.pp(mods[:tableizer].table, out)
|
15
|
+
out
|
16
|
+
end
|
17
|
+
|
18
|
+
# Outputs an array of all of the productions.
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
def generate_productions_list
|
22
|
+
out = "["
|
23
|
+
|
24
|
+
grammar.all_productions.each do |production|
|
25
|
+
out <<
|
26
|
+
"[" <<
|
27
|
+
production.label.name.inspect <<
|
28
|
+
", " <<
|
29
|
+
production.items.size.inspect <<
|
30
|
+
", "
|
31
|
+
|
32
|
+
block = if production.block.empty?
|
33
|
+
"proc {}"
|
34
|
+
else
|
35
|
+
"proc #{production.block}"
|
36
|
+
end
|
37
|
+
|
38
|
+
out << block << "],\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
out.chomp!(",\n")
|
42
|
+
|
43
|
+
out << "]"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Actually performs the generation. Takes the template from
|
47
|
+
# ruby.erb and outputs it to `<file>_parser.rb`.
|
48
|
+
#
|
49
|
+
# @return [void]
|
50
|
+
def generate
|
51
|
+
template "ruby.erb", "#{file}_parser.rb" do |body|
|
52
|
+
sprintf(grammar.compiler.body, :write => body)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
Productions:
|
2
|
+
% constructor.productions.each do |production|
|
3
|
+
<%= production.to_s(false) %>
|
4
|
+
% end
|
5
|
+
|
6
|
+
Original Productions:
|
7
|
+
% grammar.productions.each do |k, v|
|
8
|
+
% v.each do |prod|
|
9
|
+
<%= k %> → <%= prod[:items].join(" ") %>
|
10
|
+
<%= prod[:block] %>
|
11
|
+
% end
|
12
|
+
% end
|
13
|
+
|
14
|
+
Conflicts:
|
15
|
+
% conflictor.conflicts.each do |conflict|
|
16
|
+
State <%= conflict.state.id %>:
|
17
|
+
rules : <%= conflict.rules.map(&:id).join(", ") %>
|
18
|
+
type : <%= conflict.type %>
|
19
|
+
tokens: {<%= conflict.token.to_a.join(", ") %>}
|
20
|
+
% end
|
21
|
+
|
22
|
+
Presidence:
|
23
|
+
--- highest
|
24
|
+
% grammar.presidence.each do |pr|
|
25
|
+
<%= "%-8s" % pr.type %> <%= pr.level %>:
|
26
|
+
{<%= pr.tokens.to_a.join(", ") %>}
|
27
|
+
% end
|
28
|
+
--- lowest
|
29
|
+
|
30
|
+
Table:
|
31
|
+
% PP.pp(Hash[tableizer.table.each_with_index.to_a.map(&:reverse)], _erbout)
|
32
|
+
|
33
|
+
% PP.pp(tableizer.rules, _erbout)
|
34
|
+
|
35
|
+
% grammar.states.each do |state|
|
36
|
+
State <%= state.id %>:
|
37
|
+
rules:
|
38
|
+
% state.rules.each do |rule|
|
39
|
+
<%= rule %>
|
40
|
+
{<%= rule.lookahead.to_a.join(", ") %>}
|
41
|
+
% end
|
42
|
+
|
43
|
+
transitions:
|
44
|
+
% max = state.transitions.keys.map(&:length).max || 0
|
45
|
+
% state.transitions.each do |on, to|
|
46
|
+
<%= "%-#{max}s" % on %>: State <%= to.id %>
|
47
|
+
% end
|
48
|
+
|
49
|
+
% end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# This file assumes that the output of the generator will be placed
|
2
|
+
# within a module or a class. However, the module/class requires a
|
3
|
+
# `type` method, which takes a terminal and gives its type, as a
|
4
|
+
# symbol. These types should line up with the terminals that were
|
5
|
+
# defined in the original grammar.
|
6
|
+
|
7
|
+
# The actions to take during parsing. In every state, there are a
|
8
|
+
# set of acceptable peek tokens; this table tells the parser what
|
9
|
+
# to do on each acceptable peek token. The possible actions include
|
10
|
+
# `:accept`, `:reduce`, and `:state`; `:accept` means to accept the
|
11
|
+
# input and return the value of the pasing. `:reduce` means to
|
12
|
+
# reduce the top of the stack into a given nonterminal. `:state`
|
13
|
+
# means to transition to another state.
|
14
|
+
#
|
15
|
+
# @return [Array<Hash<(Symbol, Array<(Symbol, Numeric)>)>>]
|
16
|
+
ACTION_TABLE = <%= generate_action_table %>.freeze # >
|
17
|
+
|
18
|
+
# A list of all of the productions. Only includes the left-hand side,
|
19
|
+
# the number of tokens on the right-hand side, and the block to call
|
20
|
+
# on reduction.
|
21
|
+
#
|
22
|
+
# @return [Array<Array<(Symbol, Numeric, Proc)>>]
|
23
|
+
PRODUCTIONS = <%= generate_productions_list %>.freeze # >
|
24
|
+
|
25
|
+
# Runs the parser.
|
26
|
+
#
|
27
|
+
# @param input [Array<Object>] the input to run the parser over.
|
28
|
+
# @return [Object] the result of the accept.
|
29
|
+
def parse(input)
|
30
|
+
stack = []
|
31
|
+
stack.push([nil, 0])
|
32
|
+
input = input.dup
|
33
|
+
last = nil
|
34
|
+
|
35
|
+
until stack.empty? do
|
36
|
+
peek_token = if input.empty?
|
37
|
+
:"$"
|
38
|
+
else
|
39
|
+
type(input.first)
|
40
|
+
end
|
41
|
+
|
42
|
+
action = ACTION_TABLE[stack.last.last].fetch(peek_token)
|
43
|
+
case action.first
|
44
|
+
when :accept
|
45
|
+
production = PRODUCTIONS[action.last]
|
46
|
+
last = stack.pop(production[1]).first.first
|
47
|
+
stack.pop
|
48
|
+
when :reduce
|
49
|
+
production = PRODUCTIONS[action.last]
|
50
|
+
removing = stack.pop(production[1])
|
51
|
+
value = production[2].call(*removing.map(&:first))
|
52
|
+
goto = ACTION_TABLE[stack.last.last][production[0]]
|
53
|
+
stack.push([value, goto.last])
|
54
|
+
when :state
|
55
|
+
stack.push([input.shift, action.last])
|
56
|
+
else
|
57
|
+
raise
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
last
|
62
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "antelope/generator/output"
|
2
|
+
require "antelope/generator/ruby"
|
3
|
+
require "erb"
|
4
|
+
require "pathname"
|
5
|
+
|
6
|
+
module Antelope
|
7
|
+
|
8
|
+
# Generates a parser. This is normally the parent class, and the
|
9
|
+
# specific implementations inherit from this. The generated
|
10
|
+
# parser should, ideally, be completely independent (not requiring
|
11
|
+
# any external source code), as well as be under a permissive
|
12
|
+
# license.
|
13
|
+
class Generator
|
14
|
+
|
15
|
+
# The modifiers that were applied to the grammar.
|
16
|
+
#
|
17
|
+
# @return [Hash<(Symbol, Object)>]
|
18
|
+
attr_reader :mods
|
19
|
+
|
20
|
+
# The file name (not including the extension) that the grammar
|
21
|
+
# should output to.
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
attr_reader :file
|
25
|
+
|
26
|
+
# The grammar that the generator is for.
|
27
|
+
#
|
28
|
+
# @return [Ace::Grammar]
|
29
|
+
attr_reader :grammar
|
30
|
+
|
31
|
+
# The source root directory for templates. Overwrite to change.
|
32
|
+
#
|
33
|
+
# @return [Pathname]
|
34
|
+
def self.source_root
|
35
|
+
Pathname.new("../generator/templates").expand_path(__FILE__)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Initialize the generator.
|
39
|
+
#
|
40
|
+
# @param grammar [Grammar]
|
41
|
+
# @param mods [Hash<(Symbol, Object)>]
|
42
|
+
def initialize(grammar, mods)
|
43
|
+
@file = grammar.name
|
44
|
+
@grammar = grammar
|
45
|
+
@mods = mods
|
46
|
+
end
|
47
|
+
|
48
|
+
# Actually does the generation. A subclass should implement this.
|
49
|
+
#
|
50
|
+
# @raise [NotImplementedError]
|
51
|
+
# @return [void]
|
52
|
+
def generate
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
# Copies a template from the source, runs it through erb (in the
|
59
|
+
# context of this class), and then outputs it at the destination.
|
60
|
+
# If given a block, it will call the block after the template is
|
61
|
+
# run through erb with the content from erb; the result of the
|
62
|
+
# block is then used as the content instead.
|
63
|
+
#
|
64
|
+
# @param source [String] the source file. This should be in
|
65
|
+
# {.source_root}.
|
66
|
+
# @param destination [String] the destination file. This will be
|
67
|
+
# in {Ace::Grammar#output}.
|
68
|
+
# @yieldparam [String] content The content that ERB created.
|
69
|
+
# @yieldreturn [String] The new content to write to the output.
|
70
|
+
# @return [void]
|
71
|
+
def template(source, destination)
|
72
|
+
src_file = self.class.source_root + source
|
73
|
+
src = src_file.open("r")
|
74
|
+
context = instance_eval('binding')
|
75
|
+
content = ERB.new(src.read, nil, "%").result(context)
|
76
|
+
content = yield content if block_given?
|
77
|
+
dest_file = grammar.output + destination
|
78
|
+
dest_file.open("w") do |f|
|
79
|
+
f.write(content)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|