antelope 0.2.4 → 0.3.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/.rubocop.yml +11 -0
- data/bin/antelope +3 -3
- data/examples/{example.err → example.ace.err} +9 -9
- data/examples/{example.inf → example.ace.inf} +57 -9
- data/examples/example.ate +70 -0
- data/examples/example.ate.err +192 -0
- data/examples/example.ate.inf +432 -0
- data/lib/antelope/ace/compiler.rb +4 -4
- data/lib/antelope/ace/errors.rb +0 -18
- data/lib/antelope/ace.rb +6 -12
- data/lib/antelope/cli.rb +1 -1
- data/lib/antelope/dsl/compiler.rb +117 -0
- data/lib/antelope/dsl/contexts/base.rb +29 -0
- data/lib/antelope/dsl/contexts/main.rb +63 -0
- data/lib/antelope/dsl/contexts/match.rb +24 -0
- data/lib/antelope/dsl/contexts/precedence.rb +20 -0
- data/lib/antelope/dsl/contexts/production.rb +24 -0
- data/lib/antelope/dsl/contexts/terminal.rb +28 -0
- data/lib/antelope/dsl/contexts.rb +16 -0
- data/lib/antelope/dsl.rb +9 -0
- data/lib/antelope/errors.rb +18 -1
- data/lib/antelope/generation/constructor/first.rb +10 -12
- data/lib/antelope/generation/constructor/follow.rb +6 -6
- data/lib/antelope/generation/constructor/nullable.rb +6 -6
- data/lib/antelope/generation/constructor.rb +4 -4
- data/lib/antelope/generation/recognizer/rule.rb +17 -17
- data/lib/antelope/generation/recognizer/state.rb +9 -10
- data/lib/antelope/generation/recognizer.rb +8 -11
- data/lib/antelope/generation/tableizer.rb +2 -2
- data/lib/antelope/generator/base.rb +7 -7
- data/lib/antelope/generator/ruby.rb +1 -1
- data/lib/antelope/grammar/generation.rb +77 -0
- data/lib/antelope/grammar/loading.rb +84 -0
- data/lib/antelope/{ace → grammar}/precedence.rb +2 -4
- data/lib/antelope/grammar/precedences.rb +64 -0
- data/lib/antelope/{ace → grammar}/production.rb +11 -12
- data/lib/antelope/grammar/productions.rb +154 -0
- data/lib/antelope/grammar/symbols.rb +64 -0
- data/lib/antelope/{ace → grammar}/token/epsilon.rb +1 -2
- data/lib/antelope/{ace → grammar}/token/error.rb +1 -3
- data/lib/antelope/{ace → grammar}/token/nonterminal.rb +1 -3
- data/lib/antelope/{ace → grammar}/token/terminal.rb +1 -3
- data/lib/antelope/{ace → grammar}/token.rb +12 -15
- data/lib/antelope/grammar.rb +68 -0
- data/lib/antelope/version.rb +1 -1
- data/lib/antelope.rb +12 -6
- data/spec/antelope/ace/compiler_spec.rb +6 -6
- data/spec/antelope/ace/scanner_spec.rb +7 -7
- data/spec/antelope/generation/constructor_spec.rb +131 -0
- data/spec/support/grammar_helper.rb +2 -3
- metadata +32 -19
- data/lib/antelope/ace/grammar/generation.rb +0 -80
- data/lib/antelope/ace/grammar/loading.rb +0 -53
- data/lib/antelope/ace/grammar/precedences.rb +0 -68
- data/lib/antelope/ace/grammar/productions.rb +0 -156
- data/lib/antelope/ace/grammar/symbols.rb +0 -66
- data/lib/antelope/ace/grammar.rb +0 -69
- data/spec/antelope/constructor_spec.rb +0 -133
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
class Grammar
|
5
|
+
# The default modifiers for generation. It's not really
|
6
|
+
# recommended to (heh) modify this; however, adding your own
|
7
|
+
# modifier is always acceptable.
|
8
|
+
DEFAULT_MODIFIERS = [
|
9
|
+
[:recognizer, Generation::Recognizer ],
|
10
|
+
[:constructor, Generation::Constructor],
|
11
|
+
[:tableizer, Generation::Tableizer ]
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
# Handles the generation of output for the grammar.
|
15
|
+
module Generation
|
16
|
+
# Generates the output. First, it runs through every given
|
17
|
+
# modifier, and instintates it. It then calls every modifier,
|
18
|
+
# turns it into a hash, and passes that hash to each of the
|
19
|
+
# given generators.
|
20
|
+
#
|
21
|
+
# @param options [Hash] options.
|
22
|
+
# @param generators [Array<Generator>] a list of generators
|
23
|
+
# to use in generation.
|
24
|
+
# @param modifiers [Array<Array<(Symbol, #call)>>] a list of
|
25
|
+
# modifiers to apply to the grammar.
|
26
|
+
# @return [void]
|
27
|
+
def generate(options = {},
|
28
|
+
generators = :guess,
|
29
|
+
modifiers = DEFAULT_MODIFIERS)
|
30
|
+
pp self
|
31
|
+
mods = modifiers.map(&:last)
|
32
|
+
.map { |x| x.new(self) }
|
33
|
+
mods.each do |mod|
|
34
|
+
puts "Running mod #{mod.class}..." if options[:verbose]
|
35
|
+
mod.call
|
36
|
+
end
|
37
|
+
hash = Hash[modifiers.map(&:first).zip(mods)]
|
38
|
+
# This is when we'd generate
|
39
|
+
|
40
|
+
find_generators(generators, options).each do |gen|
|
41
|
+
puts "Running generator #{gen}..." if options[:verbose]
|
42
|
+
gen.new(self, hash).generate
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Find the corresponding generators. If the first argument
|
49
|
+
# isn't `:guess`, it returns the first argument. Otherwise,
|
50
|
+
# it tries to "intelligently guess" by checking the type from
|
51
|
+
# the options _or_ the compiler. If it is unable to find the
|
52
|
+
# type, it will raise a {NoTypeError}.
|
53
|
+
#
|
54
|
+
# @raise [NoTypeError] if it could not determine the type of
|
55
|
+
# the generator.
|
56
|
+
# @param generators [Symbol, Array<Generator>]
|
57
|
+
# @param options [Hash]
|
58
|
+
# @return [Array<Generator>]
|
59
|
+
def find_generators(generators, options)
|
60
|
+
return generators unless generators == :guess
|
61
|
+
|
62
|
+
generators = [Generator::Output]
|
63
|
+
|
64
|
+
# command line precedence...
|
65
|
+
type = options[:type] || options['type'] ||
|
66
|
+
compiler.options.fetch(:type)
|
67
|
+
|
68
|
+
generators << Generator.generators.fetch(type.to_s)
|
69
|
+
|
70
|
+
generators
|
71
|
+
|
72
|
+
rescue KeyError
|
73
|
+
raise NoTypeError, "Undefined type #{type}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
class Grammar
|
5
|
+
# Handles loading to and from files and strings.
|
6
|
+
module Loading
|
7
|
+
# Defines class methods on the grammar.
|
8
|
+
module ClassMethods
|
9
|
+
# Loads a grammar from a file. Assumes the output
|
10
|
+
# directory and name from the file name.
|
11
|
+
#
|
12
|
+
# @param file_name [String] the file name.
|
13
|
+
# @return [Grammar]
|
14
|
+
# @see #from_string
|
15
|
+
def from_file(file_name)
|
16
|
+
ext = File.extname(file_name)
|
17
|
+
case ext
|
18
|
+
when ".rb", ".ate"
|
19
|
+
from_dsl_file(file_name)
|
20
|
+
when ".ace"
|
21
|
+
from_ace_file(file_name)
|
22
|
+
else
|
23
|
+
raise ArgumentError, "Unexpected file extension #{ext},"\
|
24
|
+
" expected one of .rb, .ate, or .ace"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def from_dsl_file(file_name)
|
29
|
+
body = File.read(file_name)
|
30
|
+
output = File.dirname(file_name)
|
31
|
+
from_dsl_string(file_name, output, body)
|
32
|
+
end
|
33
|
+
|
34
|
+
def from_ace_file(file_name)
|
35
|
+
body = File.read(file_name)
|
36
|
+
output = File.dirname(file_name)
|
37
|
+
name = File.basename(file_name)
|
38
|
+
from_ace_string(name, output, body)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Loads a grammar from a string. First runs the scanner and
|
42
|
+
# compiler over the string, and then instantiates a new
|
43
|
+
# Grammar from the resultant.
|
44
|
+
#
|
45
|
+
# @param file [String] the path of the grammar. This is
|
46
|
+
# used for eval.
|
47
|
+
# @param output [String] the output directory.
|
48
|
+
# @param string [String] the grammar body.
|
49
|
+
# @return [Grammar]
|
50
|
+
# @see DSL::Compiler
|
51
|
+
def from_dsl_string(file, output, string)
|
52
|
+
eval(string, TOPLEVEL_BINDING, file, 0)
|
53
|
+
grammar = Antelope.grammar
|
54
|
+
compiler = DSL::Compiler.compile(grammar[1], &grammar[2])
|
55
|
+
new(File.basename(file), output, compiler)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Loads a grammar from a string. First runs the scanner and
|
59
|
+
# compiler over the string, and then instantiates a new
|
60
|
+
# Grammar from the resultant.
|
61
|
+
#
|
62
|
+
# @param name [String] the name of the grammar.
|
63
|
+
# @param output [String] the output directory.
|
64
|
+
# @param string [String] the grammar body.
|
65
|
+
# @return [Grammar]
|
66
|
+
# @see Ace::Scanner
|
67
|
+
# @see Ace::Compiler
|
68
|
+
def from_ace_string(name, output, string)
|
69
|
+
scanner = Ace::Scanner.scan(string, name)
|
70
|
+
compiler = Ace::Compiler.compile(scanner)
|
71
|
+
new(name, output, compiler)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Extends the grammar with the class methods.
|
76
|
+
#
|
77
|
+
# @param receiver [Grammar]
|
78
|
+
# @see ClassMethods
|
79
|
+
def self.included(receiver)
|
80
|
+
receiver.extend ClassMethods
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Antelope
|
4
|
-
|
5
|
-
|
4
|
+
class Grammar
|
6
5
|
# Defines a precedence. A precedence has a type, tokens, and a
|
7
6
|
# level.
|
8
|
-
|
9
|
-
|
7
|
+
Precedence = Struct.new(:type, :tokens, :level) do
|
10
8
|
# @!attribute [rw] type
|
11
9
|
# The type of precedence level. This should be one of
|
12
10
|
# `:left`, `:right`, or `:nonassoc`.
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
module Antelope
|
6
|
+
class Grammar
|
7
|
+
# Manages precedence for tokens.
|
8
|
+
module Precedences
|
9
|
+
# Accesses the generated precedence list. Lazily generates
|
10
|
+
# the precedence rules on the go, and then caches it.
|
11
|
+
#
|
12
|
+
# @return [Array<Ace::Precedence>]
|
13
|
+
def precedence
|
14
|
+
@_precedence ||= generate_precedence
|
15
|
+
end
|
16
|
+
|
17
|
+
# Finds a precedence rule for a given token. If no direct
|
18
|
+
# rule is defined for that token, it will check for a rule
|
19
|
+
# defined for the special symbol, `:_`. By default, there
|
20
|
+
# is always a rule defined for `:_`.
|
21
|
+
#
|
22
|
+
# @param token [Ace::Token, Symbol]
|
23
|
+
# @return [Ace::Precedence]
|
24
|
+
def precedence_for(token)
|
25
|
+
token = token.name if token.is_a?(Token)
|
26
|
+
|
27
|
+
prec = precedence.
|
28
|
+
detect { |pr| pr.tokens.include?(token) } ||
|
29
|
+
precedence.
|
30
|
+
detect { |pr| pr.tokens.include?(:_) }
|
31
|
+
|
32
|
+
prec
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Generates the precedence rules. Loops through the compiler
|
38
|
+
# given precedence settings, and then adds two default
|
39
|
+
# precedence rules; one for `:$` (level 0, nonassoc), and one
|
40
|
+
# for `:_` (level 1, nonassoc).
|
41
|
+
#
|
42
|
+
# @return [Array<Ace::Precedence>]
|
43
|
+
def generate_precedence
|
44
|
+
size = @compiler.options[:prec].size + 1
|
45
|
+
index = 0
|
46
|
+
precedence = []
|
47
|
+
|
48
|
+
while index < size - 1
|
49
|
+
prec = @compiler.options[:prec][index]
|
50
|
+
precedence <<
|
51
|
+
Precedence.new(prec[0], prec[1..-1].to_set,
|
52
|
+
size - index)
|
53
|
+
index += 1
|
54
|
+
end
|
55
|
+
|
56
|
+
precedence <<
|
57
|
+
Precedence.new(:nonassoc, [:$end].to_set, 0) <<
|
58
|
+
Precedence.new(:nonassoc, [:_].to_set, 1)
|
59
|
+
precedence.sort_by(&:level).reverse
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Antelope
|
4
|
-
|
5
|
-
|
4
|
+
class Grammar
|
6
5
|
# Defines a production.
|
7
|
-
|
6
|
+
Production = Struct.new(:label, :items, :block, :prec, :id) do
|
8
7
|
# @!attribute [rw] label
|
9
8
|
# The label (or left-hand side) of the production. This
|
10
9
|
# should be a nonterminal.
|
@@ -35,11 +34,11 @@ module Antelope
|
|
35
34
|
#
|
36
35
|
# @param hash [Hash<(Symbol, Object)>]
|
37
36
|
def self.from_hash(hash)
|
38
|
-
new(hash[:label] || hash[
|
39
|
-
hash[:items] || hash[
|
40
|
-
hash[:block] || hash[
|
41
|
-
hash[:prec] || hash[
|
42
|
-
hash[:id] || hash[
|
37
|
+
new(hash[:label] || hash['label'],
|
38
|
+
hash[:items] || hash['items'],
|
39
|
+
hash[:block] || hash['block'],
|
40
|
+
hash[:prec] || hash['prec'],
|
41
|
+
hash[:id] || hash['id'])
|
43
42
|
end
|
44
43
|
|
45
44
|
# Create a new version of the production with duplicated values.
|
@@ -47,10 +46,10 @@ module Antelope
|
|
47
46
|
# @return [Production]
|
48
47
|
def clone
|
49
48
|
Production.new(label.dup,
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
items.map(&:dup),
|
50
|
+
block.dup,
|
51
|
+
prec.dup,
|
52
|
+
id)
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
class Grammar
|
5
|
+
|
6
|
+
# Manages the productions of the grammar.
|
7
|
+
module Productions
|
8
|
+
|
9
|
+
# Returns a hash of all of the productions. The result is
|
10
|
+
# cached.
|
11
|
+
#
|
12
|
+
# @return [Hash<(Symbol, Array<Production>)>]
|
13
|
+
def productions
|
14
|
+
@_productions || generate_productions
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns all productions for all nonterminals, sorted by id.
|
18
|
+
#
|
19
|
+
# @return [Array<Production>]
|
20
|
+
def all_productions
|
21
|
+
productions.values.flatten.sort_by(&:id)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Finds a token based on its corresponding symbol. First
|
25
|
+
# checks the productions, to see if it's a nonterminal; then,
|
26
|
+
# tries to find it in the terminals; otherwise, if the symbol
|
27
|
+
# is `error`, it returns a {Token::Error}; if the symbol is
|
28
|
+
# `nothing` or `ε`, it returns a {Token::Epsilon}; if it's
|
29
|
+
# none of those, it raises an {UndefinedTokenError}.
|
30
|
+
#
|
31
|
+
# @raise [UndefinedTokenError] if the token doesn't exist.
|
32
|
+
# @param value [String, Symbol, #intern] the token's symbol to
|
33
|
+
# check.
|
34
|
+
# @return [Token]
|
35
|
+
def find_token(value)
|
36
|
+
value = value.intern
|
37
|
+
|
38
|
+
if productions.key?(value)
|
39
|
+
typed_nonterminals.find { |term| term.name == value } ||
|
40
|
+
Token::Nonterminal.new(value)
|
41
|
+
elsif terminal = terminals.
|
42
|
+
find { |term| term.name == value }
|
43
|
+
terminal
|
44
|
+
elsif value == :$error || value == :error
|
45
|
+
Token::Error.new
|
46
|
+
elsif [:nothing, :ε, :"%empty"].include?(value)
|
47
|
+
Token::Epsilon.new
|
48
|
+
else
|
49
|
+
raise UndefinedTokenError, "Could not find a token " \
|
50
|
+
"named #{value.inspect}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Actually generates the productions. Uses the rules from the
|
57
|
+
# compiler to construct the productions. Makes two loops over
|
58
|
+
# the compiler's rules; the first to tell the grammar that the
|
59
|
+
# nonterminal does exist, and the second to actually construct
|
60
|
+
# the productions. The first loop is for {#find_token},
|
61
|
+
# because otherwise it wouldn't be able to return a
|
62
|
+
# nonterminal properly.
|
63
|
+
#
|
64
|
+
# @return [Hash<(Symbol, Array<Production>)>]
|
65
|
+
def generate_productions
|
66
|
+
@_productions = {}
|
67
|
+
index = 0
|
68
|
+
|
69
|
+
rules = @compiler.rules.each do |rule|
|
70
|
+
productions[rule[:label]] = []
|
71
|
+
end
|
72
|
+
|
73
|
+
while index < rules.size
|
74
|
+
rule = rules[index]
|
75
|
+
productions[rule[:label]] <<
|
76
|
+
generate_production_for(rule, index)
|
77
|
+
index += 1
|
78
|
+
end
|
79
|
+
|
80
|
+
productions[:$start] = [default_production]
|
81
|
+
|
82
|
+
productions
|
83
|
+
end
|
84
|
+
|
85
|
+
# Generates a production for a given compiler rule. Converts
|
86
|
+
# the tokens in the set to their {Token} counterparts,
|
87
|
+
# and then sets the precedence for the production. If the
|
88
|
+
# precedence declaration from the compiler rule is empty,
|
89
|
+
# then it'll use the last terminal from the set to check for
|
90
|
+
# precedence; otherwise, it'll use the precedence declaration.
|
91
|
+
# This is to make sure that every production has a precedence
|
92
|
+
# declaration.
|
93
|
+
#
|
94
|
+
# @param rule [Hash] the compiler's rule.
|
95
|
+
# @param id [Numeric] the id for the production.
|
96
|
+
# @return [Production]
|
97
|
+
def generate_production_for(rule, id)
|
98
|
+
left = Token::Nonterminal.new(rule[:label])
|
99
|
+
items = rule[:set].map { |_| find_token(_[0]) }
|
100
|
+
prec = if rule[:prec].empty?
|
101
|
+
items.select(&:terminal?).first
|
102
|
+
else
|
103
|
+
rule[:prec].intern
|
104
|
+
end
|
105
|
+
|
106
|
+
prec = precedence_for(prec)
|
107
|
+
left.type = type_for(rule[:label])
|
108
|
+
left.id = rule[:label_id]
|
109
|
+
|
110
|
+
rule[:set].each_with_index do |tok, i|
|
111
|
+
items[i] = items[i].dup
|
112
|
+
items[i].id = tok[1]
|
113
|
+
end
|
114
|
+
items.delete_if(&:epsilon?)
|
115
|
+
|
116
|
+
Production.new(left, items, rule[:block], prec, id + 1)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns the defined type for the given token name.
|
120
|
+
# Uses the `%type` directive to infer the corresponding types.
|
121
|
+
#
|
122
|
+
# @param token [Symbol] the token to check for
|
123
|
+
# types.
|
124
|
+
def type_for(token)
|
125
|
+
token = find_token(token) unless token.is_a?(Token)
|
126
|
+
|
127
|
+
case token
|
128
|
+
when Token::Nonterminal
|
129
|
+
token.type
|
130
|
+
when Token::Terminal
|
131
|
+
token.type
|
132
|
+
when Token::Epsilon
|
133
|
+
""
|
134
|
+
when Token::Error
|
135
|
+
""
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Creates the default production for the grammar. The left
|
140
|
+
# hand side of the production is the `:$start` symbol, with
|
141
|
+
# the right hand side being the first rule's left-hand side
|
142
|
+
# and the terminal `$`. This production is automagically
|
143
|
+
# given the last precedence, and an id of 0.
|
144
|
+
#
|
145
|
+
# @return [Production]
|
146
|
+
def default_production
|
147
|
+
Production.new(Token::Nonterminal.new(:$start), [
|
148
|
+
Token::Nonterminal.new(@compiler.rules.first[:label]),
|
149
|
+
Token::Terminal.new(:$end)
|
150
|
+
], "", precedence.last, 0)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Antelope
|
4
|
+
class Grammar
|
5
|
+
|
6
|
+
# Manages a list of the symbols in the grammar.
|
7
|
+
module Symbols
|
8
|
+
|
9
|
+
# A list of all terminals in the grammar. Checks the compiler
|
10
|
+
# options for terminals, and then returns an array of
|
11
|
+
# terminals. Caches the result.
|
12
|
+
#
|
13
|
+
# @return [Array<Token::Terminal>]
|
14
|
+
def terminals
|
15
|
+
@_terminals ||= begin
|
16
|
+
@compiler.options.fetch(:terminals) { [] }.map do |v|
|
17
|
+
Token::Terminal.new(*v)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# A list of all nonterminals in the grammar.
|
23
|
+
#
|
24
|
+
# @return [Array<Symbol>]
|
25
|
+
# @see #productions
|
26
|
+
def nonterminals
|
27
|
+
@_nonterminals ||= productions.keys
|
28
|
+
end
|
29
|
+
|
30
|
+
# A list of all nonterminals, with types.
|
31
|
+
#
|
32
|
+
# @return [Array<Token::Nonterminal>>]
|
33
|
+
def typed_nonterminals
|
34
|
+
@_typed_nonterminals ||= begin
|
35
|
+
typed = []
|
36
|
+
compiler.options[:nonterminals].each do |data|
|
37
|
+
data[1].each do |nonterm|
|
38
|
+
typed << Token::Nonterminal.new(nonterm, data[0])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
typed
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# A list of all symbols in the grammar; includes both
|
46
|
+
# terminals and nonterminals.
|
47
|
+
#
|
48
|
+
# @return [Array<Token::Terminal, Symbol>]
|
49
|
+
# @see #terminals
|
50
|
+
# @see #nonterminals
|
51
|
+
def symbols
|
52
|
+
@_symbols ||= terminals + nonterminals
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks to see if the grammar uses the `error` terminal
|
56
|
+
# anywhere.
|
57
|
+
#
|
58
|
+
# @return [Boolean]
|
59
|
+
def contains_error_token?
|
60
|
+
all_productions.any? { |_| _.items.any?(&:error?) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,15 +1,13 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Antelope
|
4
|
-
|
4
|
+
class Grammar
|
5
5
|
class Token
|
6
|
-
|
7
6
|
# Defines an error token. This may be used internally by the
|
8
7
|
# parser when it enters panic mode; any tokens following this
|
9
8
|
# are the synchronisation tokens. This is considered a terminal
|
10
9
|
# for the purposes of rule definitions.
|
11
10
|
class Error < Terminal
|
12
|
-
|
13
11
|
# Initialize the error token. Technically takes no arguments.
|
14
12
|
# Sets the name to be `:$error`.
|
15
13
|
def initialize(*)
|
@@ -1,21 +1,18 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
|
3
|
+
require 'antelope/grammar/token/nonterminal'
|
4
|
+
require 'antelope/grammar/token/terminal'
|
5
|
+
require 'antelope/grammar/token/epsilon'
|
6
|
+
require 'antelope/grammar/token/error'
|
8
7
|
|
9
8
|
module Antelope
|
10
|
-
|
11
|
-
|
9
|
+
class Grammar
|
12
10
|
# Defines a token type for productions/rules.
|
13
11
|
#
|
14
12
|
# @abstract This class should be inherited to define a real token.
|
15
13
|
# A base class does not match any token; however, any token can
|
16
14
|
# match the base class.
|
17
15
|
class Token
|
18
|
-
|
19
16
|
# The name of the token.
|
20
17
|
#
|
21
18
|
# @return [Symbol]
|
@@ -138,16 +135,16 @@ module Antelope
|
|
138
135
|
# @see #name
|
139
136
|
def to_s
|
140
137
|
buf = if @value
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
138
|
+
@value.inspect
|
139
|
+
else
|
140
|
+
@name.to_s
|
141
|
+
end
|
145
142
|
|
146
|
-
if from
|
147
|
-
buf <<
|
143
|
+
if from || to
|
144
|
+
buf << '('
|
148
145
|
buf << "#{from.id}" if from
|
149
146
|
buf << ":#{to.id}" if to
|
150
|
-
buf <<
|
147
|
+
buf << ')'
|
151
148
|
end
|
152
149
|
|
153
150
|
buf
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'hashie'
|
4
|
+
require 'antelope/grammar/symbols'
|
5
|
+
require 'antelope/grammar/productions'
|
6
|
+
require 'antelope/grammar/production'
|
7
|
+
require 'antelope/grammar/precedences'
|
8
|
+
require 'antelope/grammar/precedence'
|
9
|
+
require 'antelope/grammar/loading'
|
10
|
+
require 'antelope/grammar/generation'
|
11
|
+
require 'antelope/grammar/token'
|
12
|
+
|
13
|
+
module Antelope
|
14
|
+
# Defines a grammar from an Ace file. This handles setting up
|
15
|
+
# productions, loading from files, symbols, precedence, and
|
16
|
+
# generation.
|
17
|
+
class Grammar
|
18
|
+
include Symbols
|
19
|
+
include Productions
|
20
|
+
include Precedences
|
21
|
+
include Loading
|
22
|
+
include Grammar::Generation
|
23
|
+
|
24
|
+
# Used by a generation class; this is all the generated states
|
25
|
+
# of the grammar.
|
26
|
+
#
|
27
|
+
# @return [Set<Generation::Recognizer::State>]
|
28
|
+
# @see Generation::Recognizer
|
29
|
+
attr_accessor :states
|
30
|
+
|
31
|
+
# The name of the grammar. This is normally assumed from a file
|
32
|
+
# name.
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
attr_accessor :name
|
36
|
+
|
37
|
+
# The output directory for the grammar. This is normally the
|
38
|
+
# same directory as the Ace file.
|
39
|
+
#
|
40
|
+
# @return [Pathname]
|
41
|
+
attr_accessor :output
|
42
|
+
|
43
|
+
# The compiler for the Ace file.
|
44
|
+
#
|
45
|
+
# @return [Compiler]
|
46
|
+
attr_reader :compiler
|
47
|
+
|
48
|
+
# Initialize.
|
49
|
+
#
|
50
|
+
# @param name [String]
|
51
|
+
# @param output [String] the output directory. Automagically
|
52
|
+
# turned into a Pathname.
|
53
|
+
# @param compiler [Compiler]
|
54
|
+
def initialize(name, output, compiler)
|
55
|
+
@name = name
|
56
|
+
@output = Pathname.new(output)
|
57
|
+
@compiler = compiler
|
58
|
+
end
|
59
|
+
|
60
|
+
# Extra options from the compiler. This can be used by
|
61
|
+
# generators for output information.
|
62
|
+
#
|
63
|
+
# @return [Hash]
|
64
|
+
def options
|
65
|
+
compiler.options[:extra]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/antelope/version.rb
CHANGED