antelope 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +38 -0
- data/GENERATORS.md +82 -0
- data/examples/deterministic.output +6 -6
- data/examples/example.output +8 -8
- data/examples/simple.output +6 -6
- data/lib/antelope/ace/grammar/generation.rb +2 -5
- data/lib/antelope/ace/grammar/loading.rb +1 -1
- data/lib/antelope/ace/grammar/productions.rb +27 -27
- data/lib/antelope/ace/precedence.rb +4 -0
- data/lib/antelope/ace/scanner.rb +21 -5
- data/lib/antelope/generation/tableizer.rb +6 -0
- data/lib/antelope/generator/base.rb +127 -0
- data/lib/antelope/generator/group.rb +57 -0
- data/lib/antelope/generator/null.rb +13 -0
- data/lib/antelope/generator/output.rb +19 -2
- data/lib/antelope/generator/ruby.rb +12 -10
- data/lib/antelope/generator/templates/output.erb +10 -4
- data/lib/antelope/generator/templates/ruby.erb +1 -0
- data/lib/antelope/generator.rb +34 -72
- data/lib/antelope/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a92fce8aab3e6c5107252ea4a33c51f7da399473
|
4
|
+
data.tar.gz: 6833313957e6ae84009644b24ce78093bf049285
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0904f4b66bbb6468462420b81bdcd3d711291137f9ca54232bf3856dc8f1fafe6d208186c4db6d2aeb74af7b83e3fd74b2877a159c80767eac55325fdee5333b
|
7
|
+
data.tar.gz: c75fa1bc293beb80a2e45fe971c2db0330064919954393121a72fae89872c5a8e1d93f99eebfc247e7f879b25c2dcf86d3ad01d4ba92982f9eed9ffdfbb5a411
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Contributing to _Antelope_
|
2
|
+
|
3
|
+
First and foremost, **thank you**! Contributing to _Antelope_ is great! There are a few guidelines, however...
|
4
|
+
|
5
|
+
## Contribution Process
|
6
|
+
|
7
|
+
1. Fork the repository ([medcat/antelope])
|
8
|
+
2. Create a new branch for your feature (something like `my-feature`)
|
9
|
+
3. Make yo' changes
|
10
|
+
4. Commit yo' changes
|
11
|
+
5. if(notDone) goto 3;
|
12
|
+
6. Push yo' changes
|
13
|
+
7. Make yo' pull request
|
14
|
+
|
15
|
+
Creating a new branch is a _really, really_ good idea; it keeps things neat in the world of git. When you make the pull request, any commits you make to the merging branch are added to the pull request. Also, _please_ make sure you describe the pull request, and what it does, and why it's needed.
|
16
|
+
|
17
|
+
## Commit Message Style
|
18
|
+
|
19
|
+
I have to admit, I'm absolutely terrible at commits. But, in case of a commit, commit messages should be imperative - it's what the commit _does_, not what it _will do_ or what it _has done_; for example, "Create Generator for C Output". Messages should have a subject, and optionally a body; the subject should have no more than 50 characters, and should be concise, as well as in Title Case. If you can't fit information in the subject, put it in the body.
|
20
|
+
|
21
|
+
## Issues
|
22
|
+
|
23
|
+
When opening issues, there are a few requirements:
|
24
|
+
|
25
|
+
- Describe the problem
|
26
|
+
- Show how to reproduce it, if applicable
|
27
|
+
- Explain what you think is causing it, if applicable
|
28
|
+
- Give a plausible solution, if applicable
|
29
|
+
|
30
|
+
Give (us|me) as much information as needed so (we|I) can decide how to handle the issue.
|
31
|
+
|
32
|
+
## Closing Words
|
33
|
+
|
34
|
+
And, most of all, the last requirement: have fun!
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
[medcat/antelope]: https://github.com/medcat/antelope
|
data/GENERATORS.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Generators
|
2
|
+
|
3
|
+
_Antelope_ comes with an assortment of generators; however, if you wish to create a custom generator, here's how.
|
4
|
+
|
5
|
+
First, you'll want to make your generator a subclass of `Antelope::Generator::Base`. This sets up a basic framework for you to build upon.
|
6
|
+
|
7
|
+
```Ruby
|
8
|
+
class MyGenerator < Antelope::Generator::Base
|
9
|
+
|
10
|
+
end
|
11
|
+
```
|
12
|
+
|
13
|
+
Next, you'll want to define a `generate` method on your generator that takes no arguments. This is used internally by _Antelope_ to actually have your generator perform its generation. In the case of this generator, we'll have it copy over a template (after running ERb over it).
|
14
|
+
|
15
|
+
```Ruby
|
16
|
+
class MyGenerator < Antelope::Generator::Base
|
17
|
+
|
18
|
+
def generate
|
19
|
+
template "my_template", "#{file}.my_file"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
`Base` provides a few convienince methods for you, one of them being [`template`](http://rubydoc.info/github/medcat/antelope/master/Antelope/Generator/Base#template-instance_method); `file` is also provided, and it contains the base part of the file name of the parser ace file that this is being generated for. The template, by default, should rest in `<lib path>/lib/antelope/generator/templates` (with `<lib path>` being the place that _Antelope_ was installed); however, if it should be changed, you can overwrite the `source_root` method on the class:
|
25
|
+
|
26
|
+
```Ruby
|
27
|
+
class MyGenerator < Antelope::Generator::Base
|
28
|
+
|
29
|
+
def self.source_root
|
30
|
+
Pathname.new("/path/to/source")
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate
|
34
|
+
template "my_template", "#{file}.my_file"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
In the template, the code is run in the context of the instance of the class, so you have access to instance variables and methods as if you were defining a method on the class:
|
40
|
+
|
41
|
+
```
|
42
|
+
% table.each_with_index do |hash, i|
|
43
|
+
state <%= i %>:
|
44
|
+
% hash.each do |token, action|
|
45
|
+
for <%= token %>, I'll <%= action[0] %> <%= action[1] %>
|
46
|
+
% end
|
47
|
+
% end
|
48
|
+
```
|
49
|
+
|
50
|
+
_Note: in templates, the ERb syntax allows lines starting with `%` to be interpreted as ruby; this helps remove unwanted line space._
|
51
|
+
|
52
|
+
`table` here is defined on the base class, and we're iterating over all of the values of it.
|
53
|
+
|
54
|
+
The last thing to do is to register the generator with _Antelope_. This is as simple as adding a line `register_as "my_generator"` to the class definition. Then, if any grammar file has the type `"my_generator"`, your generator will be run (assuming it's been required by _Antelope_).
|
55
|
+
|
56
|
+
The finialized product:
|
57
|
+
|
58
|
+
```Ruby
|
59
|
+
# my_generator.rb
|
60
|
+
class MyGenerator < Antelope::Generator::Base
|
61
|
+
|
62
|
+
register_as "my_generator"
|
63
|
+
|
64
|
+
def self.source_root
|
65
|
+
Pathname.new("/path/to/source")
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate
|
69
|
+
template "my_template", "#{file}.my_file"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
```
|
75
|
+
# my_template.erb
|
76
|
+
% table.each_with_index do |hash, i|
|
77
|
+
state <%= i %>:
|
78
|
+
% hash.each do |token, action|
|
79
|
+
for <%= token %>, I'll <%= action[0] %> <%= action[1] %>
|
80
|
+
% end
|
81
|
+
% end
|
82
|
+
```
|
data/examples/example.output
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
Productions:
|
2
|
-
|
3
|
-
|
4
|
-
expression
|
5
|
-
expression
|
6
|
-
expression
|
7
|
-
expression
|
8
|
-
expression
|
9
|
-
|
2
|
+
0 $start: expression $
|
3
|
+
1 expression: NUMBER { |a| a[1] }
|
4
|
+
2 expression: expression "+" expression { |a, _, b| a + b }
|
5
|
+
3 expression: expression "-" expression { |a, _, b| a - b }
|
6
|
+
4 expression: expression "*" expression { |a, _, b| a * b }
|
7
|
+
5 expression: expression "/" expression { |a, _, b| a / b }
|
8
|
+
6 expression: "(" expression ")" { |_, a, _| a }
|
9
|
+
7 expression: "(" $error ")"
|
10
10
|
|
11
11
|
Precedence:
|
12
12
|
--- highest
|
data/examples/simple.output
CHANGED
@@ -13,10 +13,6 @@ module Antelope
|
|
13
13
|
[:tableizer, Generation::Tableizer ]
|
14
14
|
].freeze
|
15
15
|
|
16
|
-
DEFAULT_GENERATORS = {
|
17
|
-
"ruby" => [Generator::Ruby]
|
18
|
-
}
|
19
|
-
|
20
16
|
# Handles the generation of output for the grammar.
|
21
17
|
module Generation
|
22
18
|
|
@@ -44,6 +40,7 @@ module Antelope
|
|
44
40
|
# This is when we'd generate
|
45
41
|
|
46
42
|
find_generators(generators, options).each do |gen|
|
43
|
+
puts "Running generator #{gen}..."
|
47
44
|
gen.new(self, hash).generate
|
48
45
|
end
|
49
46
|
end
|
@@ -70,7 +67,7 @@ module Antelope
|
|
70
67
|
type = options[:type] || options["type"] ||
|
71
68
|
compiler.options.fetch(:type)
|
72
69
|
|
73
|
-
generators
|
70
|
+
generators << Generator.generators.fetch(type)
|
74
71
|
|
75
72
|
generators
|
76
73
|
|
@@ -34,7 +34,7 @@ module Antelope
|
|
34
34
|
# @see Ace::Scanner
|
35
35
|
# @see Ace::Compiler
|
36
36
|
def from_string(name, output, string)
|
37
|
-
scanner = Ace::Scanner.scan(string)
|
37
|
+
scanner = Ace::Scanner.scan(string, name)
|
38
38
|
compiler = Ace::Compiler.compile(scanner)
|
39
39
|
new(name, output, compiler)
|
40
40
|
end
|
@@ -22,6 +22,33 @@ module Antelope
|
|
22
22
|
productions.values.flatten.sort_by(&:id)
|
23
23
|
end
|
24
24
|
|
25
|
+
# Finds a token based on its corresponding symbol. First
|
26
|
+
# checks the productions, to see if it's a nonterminal; then,
|
27
|
+
# tries to find it in the terminals; otherwise, if the symbol
|
28
|
+
# is `error`, it returns a {Token::Error}; if the symbol is
|
29
|
+
# `nothing` or `ε`, it returns a {Token::Epsilon}; if it's
|
30
|
+
# none of those, it raises an {UndefinedTokenError}.
|
31
|
+
#
|
32
|
+
# @raise [UndefinedTokenError] if the token doesn't exist.
|
33
|
+
# @param value [String, Symbol, #intern] the token's symbol to
|
34
|
+
# check.
|
35
|
+
# @return [Token]
|
36
|
+
def find_token(value)
|
37
|
+
value = value.intern
|
38
|
+
if productions.key?(value)
|
39
|
+
Token::Nonterminal.new(value)
|
40
|
+
elsif terminal = terminals.
|
41
|
+
find { |term| term.name == value }
|
42
|
+
terminal
|
43
|
+
elsif value == :error
|
44
|
+
Token::Error.new
|
45
|
+
elsif [:nothing, :ε].include?(value)
|
46
|
+
Token::Epsilon.new
|
47
|
+
else
|
48
|
+
raise UndefinedTokenError, "Could not find a token named #{value.inspect}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
25
52
|
private
|
26
53
|
|
27
54
|
# Actually generates the productions. Uses the rules from the
|
@@ -92,33 +119,6 @@ module Antelope
|
|
92
119
|
Token::Terminal.new(:"$")
|
93
120
|
], "", precedence.last, 0)
|
94
121
|
end
|
95
|
-
|
96
|
-
# Finds a token based on its corresponding symbol. First
|
97
|
-
# checks the productions, to see if it's a nonterminal; then,
|
98
|
-
# tries to find it in the terminals; otherwise, if the symbol
|
99
|
-
# is `error`, it returns a {Token::Error}; if the symbol is
|
100
|
-
# `nothing` or `ε`, it returns a {Token::Epsilon}; if it's
|
101
|
-
# none of those, it raises an {UndefinedTokenError}.
|
102
|
-
#
|
103
|
-
# @raise [UndefinedTokenError] if the token doesn't exist.
|
104
|
-
# @param value [String, Symbol, #intern] the token's symbol to
|
105
|
-
# check.
|
106
|
-
# @return [Token]
|
107
|
-
def find_token(value)
|
108
|
-
value = value.intern
|
109
|
-
if productions.key?(value)
|
110
|
-
Token::Nonterminal.new(value)
|
111
|
-
elsif terminal = terminals.
|
112
|
-
find { |term| term.name == value }
|
113
|
-
terminal
|
114
|
-
elsif value == :error
|
115
|
-
Token::Error.new
|
116
|
-
elsif [:nothing, :ε].include?(value)
|
117
|
-
Token::Epsilon.new
|
118
|
-
else
|
119
|
-
raise UndefinedTokenError, "Could not find a token named #{value.inspect}"
|
120
|
-
end
|
121
|
-
end
|
122
122
|
end
|
123
123
|
end
|
124
124
|
end
|
data/lib/antelope/ace/scanner.rb
CHANGED
@@ -54,16 +54,21 @@ module Antelope
|
|
54
54
|
#
|
55
55
|
# @param source [String] the source to scan. This should be compatible
|
56
56
|
# with StringScanner.
|
57
|
+
# @param name [String] the name of the source file. This is primarily
|
58
|
+
# used in backtrace information.
|
57
59
|
# @return [Array<Array<(Symbol, Object, ...)>>]
|
58
60
|
# @see #tokens
|
59
|
-
def self.scan(source)
|
60
|
-
new(source).scan_file
|
61
|
+
def self.scan(source, name = "(ace file)")
|
62
|
+
new(source, name).scan_file
|
61
63
|
end
|
62
64
|
|
63
65
|
# Initialize the scanner with the input.
|
64
66
|
#
|
65
67
|
# @param input [String] The source to scan.
|
66
|
-
|
68
|
+
# @param source [String] the source file. This is primarily
|
69
|
+
# used in backtrace information.
|
70
|
+
def initialize(input, source = "(ace file)")
|
71
|
+
@source = source
|
67
72
|
@scanner = StringScanner.new(input)
|
68
73
|
@tokens = []
|
69
74
|
end
|
@@ -78,10 +83,19 @@ module Antelope
|
|
78
83
|
# @see #scan_third_part
|
79
84
|
# @see #tokens
|
80
85
|
def scan_file
|
86
|
+
@line = 1
|
81
87
|
scan_first_part
|
82
88
|
scan_second_part
|
83
89
|
scan_third_part
|
84
90
|
tokens
|
91
|
+
rescue SyntaxError => e
|
92
|
+
start = [@scanner.pos - 8, 0].max
|
93
|
+
stop = [@scanner.pos + 8, @scanner.string.length].min
|
94
|
+
snip = @scanner.string[start..stop].strip.inspect
|
95
|
+
char = @scanner.string[@scanner.pos].inspect
|
96
|
+
new_line = "#{@source}:#{@line}:unexpected #{char} (near #{snip})"
|
97
|
+
|
98
|
+
raise e, e.message, [new_line, *e.backtrace]
|
85
99
|
end
|
86
100
|
|
87
101
|
# Scans for whitespace. If the next character is whitespace, it
|
@@ -90,7 +104,9 @@ module Antelope
|
|
90
104
|
#
|
91
105
|
# @return [Boolean] if any whitespace was matched.
|
92
106
|
def scan_whitespace
|
93
|
-
@scanner.scan(
|
107
|
+
if @scanner.scan(/(\s+)/)
|
108
|
+
@line += @scanner[1].count("\n")
|
109
|
+
end
|
94
110
|
end
|
95
111
|
|
96
112
|
private
|
@@ -105,7 +121,7 @@ module Antelope
|
|
105
121
|
stop = [@scanner.pos + 8, @scanner.string.length].min
|
106
122
|
snip = @scanner.string[start..stop].strip
|
107
123
|
char = @scanner.string[@scanner.pos]
|
108
|
-
raise SyntaxError, "invalid syntax near `#{snip.inspect}' (#{char.inspect})"
|
124
|
+
raise SyntaxError, "invalid syntax"# near `#{snip.inspect}' (#{char.inspect})"
|
109
125
|
end
|
110
126
|
end
|
111
127
|
end
|
@@ -21,6 +21,8 @@ module Antelope
|
|
21
21
|
# @return [Hash<(Numeric, Recognizer::Rule)>]
|
22
22
|
attr_accessor :rules
|
23
23
|
|
24
|
+
attr_reader :conflicts
|
25
|
+
|
24
26
|
# Initialize.
|
25
27
|
#
|
26
28
|
# @param grammar [Ace::Grammar]
|
@@ -82,6 +84,7 @@ module Antelope
|
|
82
84
|
# resolved using precedence rules.
|
83
85
|
# @return [void]
|
84
86
|
def conflictize
|
87
|
+
@conflicts = Hash.new { |h, k| h[k] = {} }
|
85
88
|
@table.each_with_index do |v, state|
|
86
89
|
v.each do |on, data|
|
87
90
|
if data.size == 1
|
@@ -104,6 +107,9 @@ module Antelope
|
|
104
107
|
|
105
108
|
case result
|
106
109
|
when 0
|
110
|
+
conflicts[state][on] = [
|
111
|
+
rule_part, other_part, terminal, @rules[rule_part[1]].prec
|
112
|
+
]
|
107
113
|
$stderr.puts \
|
108
114
|
"Could not determine move for #{on} in state " \
|
109
115
|
"#{state} (shift/reduce conflict)"
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Antelope
|
2
|
+
module Generator
|
3
|
+
|
4
|
+
# Generates a parser. This is normally the parent class, and the
|
5
|
+
# specific implementations inherit from this. The generated
|
6
|
+
# parser should, ideally, be completely independent (not requiring
|
7
|
+
# any external source code), as well as be under a permissive
|
8
|
+
# license.
|
9
|
+
#
|
10
|
+
# @abstract Subclass and redefine {#generate} to create a
|
11
|
+
# generator.
|
12
|
+
class Base
|
13
|
+
# The modifiers that were applied to the grammar.
|
14
|
+
#
|
15
|
+
# @return [Hash<(Symbol, Object)>]
|
16
|
+
attr_reader :mods
|
17
|
+
|
18
|
+
# The file name (not including the extension) that the grammar
|
19
|
+
# should output to.
|
20
|
+
#
|
21
|
+
# @return [String]
|
22
|
+
attr_reader :file
|
23
|
+
|
24
|
+
# The grammar that the generator is for.
|
25
|
+
#
|
26
|
+
# @return [Ace::Grammar]
|
27
|
+
attr_reader :grammar
|
28
|
+
|
29
|
+
# The source root directory for templates. Overwrite to change.
|
30
|
+
#
|
31
|
+
# @return [Pathname]
|
32
|
+
def self.source_root
|
33
|
+
Pathname.new("../templates").expand_path(__FILE__)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.register_as(*names)
|
37
|
+
Generator.register_generator(self, *names)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Initialize the generator.
|
41
|
+
#
|
42
|
+
# @param grammar [Grammar]
|
43
|
+
# @param mods [Hash<(Symbol, Object)>]
|
44
|
+
def initialize(grammar, mods)
|
45
|
+
@file = grammar.name
|
46
|
+
@grammar = grammar
|
47
|
+
@mods = mods
|
48
|
+
end
|
49
|
+
|
50
|
+
# Actually does the generation. A subclass should implement this.
|
51
|
+
#
|
52
|
+
# @raise [NotImplementedError]
|
53
|
+
# @return [void]
|
54
|
+
def generate
|
55
|
+
raise NotImplementedError
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
# Copies a template from the source, runs it through erb (in the
|
61
|
+
# context of this class), and then outputs it at the destination.
|
62
|
+
# If given a block, it will call the block after the template is
|
63
|
+
# run through erb with the content from erb; the result of the
|
64
|
+
# block is then used as the content instead.
|
65
|
+
#
|
66
|
+
# @param source [String] the source file. This should be in
|
67
|
+
# {.source_root}.
|
68
|
+
# @param destination [String] the destination file. This will be
|
69
|
+
# in {Ace::Grammar#output}.
|
70
|
+
# @yieldparam [String] content The content that ERB created.
|
71
|
+
# @yieldreturn [String] The new content to write to the output.
|
72
|
+
# @return [void]
|
73
|
+
def template(source, destination)
|
74
|
+
src_file = Pathname.new(source)
|
75
|
+
.expand_path(self.class.source_root)
|
76
|
+
src = src_file.open("r")
|
77
|
+
context = instance_eval('binding')
|
78
|
+
erb = ERB.new(src.read, nil, "%")
|
79
|
+
erb.filename = source
|
80
|
+
content = erb.result(context)
|
81
|
+
content = yield content if block_given?
|
82
|
+
dest_file = Pathname.new(destination)
|
83
|
+
.expand_path(grammar.output)
|
84
|
+
dest_file.open("w") do |f|
|
85
|
+
f.write(content)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# The actual table that is used for parsing. This returns an
|
90
|
+
# array of hashes; the array index corresponds to the state
|
91
|
+
# number, and the hash keys correspond to the lookahead tokens.
|
92
|
+
# The hash values are an array; the first element of that array
|
93
|
+
# is the action to be taken, and the second element of the
|
94
|
+
# array is the argument for that action. Possible actions
|
95
|
+
# include `:accept`, `:reduce`, and `:state`; `:accept` means
|
96
|
+
# to accept the string; `:reduce` means to perform the given
|
97
|
+
# reduction; and `:state` means to transition to the given
|
98
|
+
# state.
|
99
|
+
#
|
100
|
+
# @return [Array<Hash<Symbol => Array<(Symbol, Numeric)>>>]
|
101
|
+
def table
|
102
|
+
mods[:tableizer].table
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns an array of the production information of each
|
106
|
+
# production needed by the parser. The first element of any
|
107
|
+
# element in the array is an {Ace::Token::Nonterminal} that
|
108
|
+
# that specific production reduces to; the second element
|
109
|
+
# is a number describing the number of items in the right hand
|
110
|
+
# side of the production; the string represents the action
|
111
|
+
# that should be taken on reduction.
|
112
|
+
#
|
113
|
+
# This information is used for `:reduce` actions in the parser;
|
114
|
+
# the value of the `:reduce` action corresponds to the array
|
115
|
+
# index of the production in this array.
|
116
|
+
#
|
117
|
+
# @return [Array<Array<(Ace::Token::Nonterminal, Numeric, String)>]
|
118
|
+
def productions
|
119
|
+
grammar.all_productions.map do |production|
|
120
|
+
[production[:label],
|
121
|
+
production[:items].size,
|
122
|
+
production[:block]]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Antelope
|
2
|
+
module Generator
|
3
|
+
|
4
|
+
# For use to use multiple generators as a bundle. Works exactly
|
5
|
+
# like a normal generator, i.e. responds to both {.register_as}
|
6
|
+
# and {#generate}, but also responds to {.register_generator},
|
7
|
+
# like {Generator}. Any generators registered to the group are
|
8
|
+
# used to generate the files.
|
9
|
+
#
|
10
|
+
# @abtract Subclass and use {.register_generator} to create a
|
11
|
+
# group generator.
|
12
|
+
class Group < Base
|
13
|
+
|
14
|
+
extend Generator
|
15
|
+
|
16
|
+
# Initialize the group generator. Calls {Base#initialize}, and
|
17
|
+
# then instantizes all of the generators in the group.
|
18
|
+
def initialize(*_)
|
19
|
+
super
|
20
|
+
|
21
|
+
generators.map! do |gen|
|
22
|
+
gen.new(*_)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Generates files using the generators contained within this
|
27
|
+
# group. If it encounters an error in one of the generators, it
|
28
|
+
# will continue to try to generate the rest of the generators.
|
29
|
+
# It will then raise the last error given at the end.
|
30
|
+
#
|
31
|
+
# @return [void]
|
32
|
+
def generate
|
33
|
+
error = nil
|
34
|
+
generators.map do |gen|
|
35
|
+
begin
|
36
|
+
gen.generate
|
37
|
+
rescue => e
|
38
|
+
$stderr.puts "Error running #{gen.class}: #{e.message}"
|
39
|
+
error = e
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
raise error if error
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Retrieve a list of all of the generators that are contained
|
49
|
+
# within this group.
|
50
|
+
#
|
51
|
+
# @return [Array<Generator::Base>]
|
52
|
+
def generators
|
53
|
+
@_generators ||= self.class.generators.values
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -3,11 +3,13 @@
|
|
3
3
|
require "pp"
|
4
4
|
|
5
5
|
module Antelope
|
6
|
-
|
6
|
+
module Generator
|
7
7
|
|
8
8
|
# Generates an output file, mainly for debugging. Included always
|
9
9
|
# as a generator for a grammar.
|
10
|
-
class Output <
|
10
|
+
class Output < Base
|
11
|
+
|
12
|
+
register_as "output"
|
11
13
|
|
12
14
|
# Defines singleton method for every mod that the grammar passed
|
13
15
|
# to the generator.
|
@@ -20,6 +22,21 @@ module Antelope
|
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
25
|
+
def unused_symbols
|
26
|
+
@_unused_symbols ||= begin
|
27
|
+
used = grammar.all_productions.map(&:items).flatten.uniq
|
28
|
+
all = grammar.symbols.map do |s|
|
29
|
+
if Symbol === s
|
30
|
+
grammar.find_token(s)
|
31
|
+
else
|
32
|
+
s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
all - used - [grammar.find_token(:"$start")]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
23
40
|
# Actually performs the generation. Uses the template in
|
24
41
|
# output.erb, and generates the file `<file>.output`.
|
25
42
|
#
|
@@ -3,17 +3,19 @@
|
|
3
3
|
require "pp"
|
4
4
|
|
5
5
|
module Antelope
|
6
|
-
|
6
|
+
module Generator
|
7
7
|
|
8
8
|
# Generates a ruby parser.
|
9
|
-
class Ruby <
|
9
|
+
class Ruby < Base
|
10
|
+
|
11
|
+
register_as "ruby"
|
10
12
|
|
11
13
|
# Creates an action table for the parser.
|
12
14
|
#
|
13
15
|
# @return [String]
|
14
16
|
def generate_action_table
|
15
17
|
out = ""
|
16
|
-
PP.pp(
|
18
|
+
PP.pp(table, out)
|
17
19
|
out
|
18
20
|
end
|
19
21
|
|
@@ -23,15 +25,15 @@ module Antelope
|
|
23
25
|
def generate_productions_list
|
24
26
|
out = "["
|
25
27
|
|
26
|
-
|
27
|
-
out
|
28
|
-
"["
|
29
|
-
|
30
|
-
", "
|
31
|
-
|
28
|
+
productions.each do |(label, size, block)|
|
29
|
+
out <<
|
30
|
+
"[" <<
|
31
|
+
label.name.inspect <<
|
32
|
+
", " <<
|
33
|
+
size.inspect <<
|
32
34
|
", "
|
33
35
|
|
34
|
-
block = if
|
36
|
+
block = if block.empty?
|
35
37
|
"proc { |_| _ }"
|
36
38
|
else
|
37
39
|
"proc #{production.block}"
|
@@ -3,11 +3,17 @@ Productions:
|
|
3
3
|
% productions = grammar.all_productions.
|
4
4
|
% map { |x| ["#{x.label}: #{x.items.join(' ')}", x.block] }
|
5
5
|
% body = productions.map { |_| _.first.size }.max
|
6
|
-
<%= body %>
|
7
6
|
% productions.each_with_index do |prod, i|
|
8
7
|
<%= "%#{len}s" % i %> <%= "%-#{body}s" % prod[0] %> <%= prod[1] %>
|
9
8
|
% end
|
10
9
|
|
10
|
+
% if unused_symbols.any?
|
11
|
+
Symbols unused in grammar:
|
12
|
+
% unused_symbols.each do |sym|
|
13
|
+
<%= sym %>
|
14
|
+
% end
|
15
|
+
% end
|
16
|
+
|
11
17
|
Precedence:
|
12
18
|
--- highest
|
13
19
|
% grammar.precedence.each do |pr|
|
@@ -28,7 +34,7 @@ Precedence:
|
|
28
34
|
% transitions = v.each.select { |_, a| a && a[0] == :state }
|
29
35
|
% reductions = v.each.select { |_, a| a && a[0] == :reduce}
|
30
36
|
% accepting = v.each.select { |_, a| a && a[0] == :accept}
|
31
|
-
% conflicts =
|
37
|
+
% conflicts = tableizer.conflicts[i].each
|
32
38
|
% thing = [:transitions, :reductions, :accepting]
|
33
39
|
% num_type = {transitions: "State", reductions: "Rule", accepting: "Rule"}
|
34
40
|
% h = Hash[thing.zip([transitions, reductions, accepting])]
|
@@ -42,8 +48,8 @@ Precedence:
|
|
42
48
|
% end
|
43
49
|
% if conflicts.any?
|
44
50
|
conflicts:
|
45
|
-
% conflicts.each do |token, (first, second)|
|
46
|
-
<%= token %>: <%= first.join(" ") %>/<%= second.join(" ") %>
|
51
|
+
% conflicts.each do |token, (first, second, rule, terminal)|
|
52
|
+
<%= token %>: <%= first.join(" ") %>/<%= second.join(" ") %> (<%= rule %> vs <%= terminal %>)
|
47
53
|
% end
|
48
54
|
% end
|
49
55
|
|
data/lib/antelope/generator.rb
CHANGED
@@ -1,88 +1,50 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
require "antelope/generator/output"
|
4
|
-
require "antelope/generator/ruby"
|
5
2
|
require "erb"
|
6
3
|
require "pathname"
|
7
4
|
|
8
5
|
module Antelope
|
9
6
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
# any external source code), as well as be under a permissive
|
14
|
-
# license.
|
15
|
-
class Generator
|
16
|
-
|
17
|
-
# The modifiers that were applied to the grammar.
|
18
|
-
#
|
19
|
-
# @return [Hash<(Symbol, Object)>]
|
20
|
-
attr_reader :mods
|
21
|
-
|
22
|
-
# The file name (not including the extension) that the grammar
|
23
|
-
# should output to.
|
24
|
-
#
|
25
|
-
# @return [String]
|
26
|
-
attr_reader :file
|
27
|
-
|
28
|
-
# The grammar that the generator is for.
|
29
|
-
#
|
30
|
-
# @return [Ace::Grammar]
|
31
|
-
attr_reader :grammar
|
32
|
-
|
33
|
-
# The source root directory for templates. Overwrite to change.
|
34
|
-
#
|
35
|
-
# @return [Pathname]
|
36
|
-
def self.source_root
|
37
|
-
Pathname.new("../generator/templates").expand_path(__FILE__)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Initialize the generator.
|
41
|
-
#
|
42
|
-
# @param grammar [Grammar]
|
43
|
-
# @param mods [Hash<(Symbol, Object)>]
|
44
|
-
def initialize(grammar, mods)
|
45
|
-
@file = grammar.name
|
46
|
-
@grammar = grammar
|
47
|
-
@mods = mods
|
48
|
-
end
|
7
|
+
# Contains the classes that generate parsers. This contains a
|
8
|
+
# registery of all of the generators available to antelope.
|
9
|
+
module Generator
|
49
10
|
|
50
|
-
#
|
11
|
+
# Returns a hash of all of the generators registered within this
|
12
|
+
# module. If a generator is accessed that does not exist on the
|
13
|
+
# hash, it by default returns the {Generator::Null} class.
|
51
14
|
#
|
52
|
-
# @
|
53
|
-
|
54
|
-
|
55
|
-
raise NotImplementedError
|
15
|
+
# @return [Hash<(Symbol, String) => Generator::Base>]
|
16
|
+
def generators
|
17
|
+
@_generators ||= Hash.new { |h, k| h[k] = Generator::Null }
|
56
18
|
end
|
57
19
|
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# If given a block, it will call the block after the template is
|
63
|
-
# run through erb with the content from erb; the result of the
|
64
|
-
# block is then used as the content instead.
|
20
|
+
# Registers a generator with the given names. If multiple names
|
21
|
+
# are given, they are assigned the generator as a value in the
|
22
|
+
# {#generators} hash; otherwise, the one name is assigned the
|
23
|
+
# generator as a value.
|
65
24
|
#
|
66
|
-
# @param
|
67
|
-
#
|
68
|
-
# @param
|
69
|
-
#
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
content = yield content if block_given?
|
81
|
-
dest_file = grammar.output + destination
|
82
|
-
dest_file.open("w") do |f|
|
83
|
-
f.write(content)
|
25
|
+
# @param generator [Generator::Base] the generator class to
|
26
|
+
# associate the key with.
|
27
|
+
# @param name [String, Symbol] a name to associate the generator
|
28
|
+
# with.
|
29
|
+
def register_generator(generator, *names)
|
30
|
+
names = [names].flatten
|
31
|
+
raise ArgumentError,
|
32
|
+
"Requires at least one name" unless names.any?
|
33
|
+
raise ArgumentError,
|
34
|
+
"All name values must be a Symbol or string" unless names.
|
35
|
+
all? {|_| [Symbol, String].include?(_.class) }
|
36
|
+
|
37
|
+
names.each do |name|
|
38
|
+
generators[name] = generator
|
84
39
|
end
|
85
40
|
end
|
86
41
|
|
42
|
+
extend self
|
43
|
+
|
87
44
|
end
|
88
45
|
end
|
46
|
+
|
47
|
+
require "antelope/generator/base"
|
48
|
+
require "antelope/generator/group"
|
49
|
+
require "antelope/generator/output"
|
50
|
+
require "antelope/generator/ruby"
|
data/lib/antelope/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: antelope
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Rodi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|
@@ -106,6 +106,8 @@ files:
|
|
106
106
|
- ".rspec"
|
107
107
|
- ".travis.yml"
|
108
108
|
- ".yardopts"
|
109
|
+
- CONTRIBUTING.md
|
110
|
+
- GENERATORS.md
|
109
111
|
- Gemfile
|
110
112
|
- LICENSE.txt
|
111
113
|
- README.md
|
@@ -152,6 +154,9 @@ files:
|
|
152
154
|
- lib/antelope/generation/recognizer/state.rb
|
153
155
|
- lib/antelope/generation/tableizer.rb
|
154
156
|
- lib/antelope/generator.rb
|
157
|
+
- lib/antelope/generator/base.rb
|
158
|
+
- lib/antelope/generator/group.rb
|
159
|
+
- lib/antelope/generator/null.rb
|
155
160
|
- lib/antelope/generator/output.rb
|
156
161
|
- lib/antelope/generator/ruby.rb
|
157
162
|
- lib/antelope/generator/templates/output.erb
|