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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bded4fc112838ad4b250647b7b15fbecac25ab32
4
- data.tar.gz: 25e8b92170861126767829c19a85b1407a31fcb8
3
+ metadata.gz: a92fce8aab3e6c5107252ea4a33c51f7da399473
4
+ data.tar.gz: 6833313957e6ae84009644b24ce78093bf049285
5
5
  SHA512:
6
- metadata.gz: bd091e4348fadd6caa15865e87ad60d849959c2d76baae2e3aa6bed62111a76169310ed0b3ea842e746f566d097ae87fc29580379f1ebe0a732afbaeea348ada
7
- data.tar.gz: ba5288283b0e6c46ae9671c92f19d5776bacfe6c232ca565d93f17d8056a96859c2e231e8a095a14eb583e87c25fbf295ebfe56394ae5bd7370d3270e8068639
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
+ ```
@@ -1,10 +1,10 @@
1
1
  Productions:
2
- s e
3
- e t ";"
4
- e t "+" e
5
- t NUMBER
6
- t → "(" e ")"
7
- $start s $
2
+ 0 $start: s $
3
+ 1 s: e
4
+ 2 e: t ";"
5
+ 3 e: t "+" e
6
+ 4 t: NUMBER
7
+ 5 t: "(" e ")"
8
8
 
9
9
  Precedence:
10
10
  --- highest
@@ -1,12 +1,12 @@
1
1
  Productions:
2
- expression NUMBER { |a| a[1] }
3
- expression expression "+" expression { |a, _, b| a + b }
4
- expression expression "-" expression { |a, _, b| a - b }
5
- expression expression "*" expression { |a, _, b| a * b }
6
- expression expression "/" expression { |a, _, b| a / b }
7
- expression "(" expression ")" { |_, a, _| a }
8
- expression "(" $error ")"
9
- $start expression $
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
@@ -1,10 +1,10 @@
1
1
  Productions:
2
- e l "=" r
3
- e r
4
- l IDENT
5
- l → "*" r
6
- r l
7
- $start e $
2
+ 0 $start: e $
3
+ 1 e: l "=" r
4
+ 2 e: r
5
+ 3 l: IDENT
6
+ 4 l: "*" r
7
+ 5 r: l
8
8
 
9
9
  Precedence:
10
10
  --- highest
@@ -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 += DEFAULT_GENERATORS.fetch(type)
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
@@ -48,6 +48,10 @@ module Antelope
48
48
  0
49
49
  end
50
50
  end
51
+
52
+ def to_s
53
+ "#{type.to_s[0]}#{level}"
54
+ end
51
55
  end
52
56
  end
53
57
  end
@@ -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
- def initialize(input)
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(/\s+/)
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
@@ -0,0 +1,13 @@
1
+ module Antelope
2
+ module Generator
3
+
4
+ # Represents a generator that does not generate anything.
5
+ class Null < Base
6
+
7
+ # Does nothing.
8
+ #
9
+ # @return [void]
10
+ def generate; end
11
+ end
12
+ end
13
+ end
@@ -3,11 +3,13 @@
3
3
  require "pp"
4
4
 
5
5
  module Antelope
6
- class Generator
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 < Generator
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
- class Generator
6
+ module Generator
7
7
 
8
8
  # Generates a ruby parser.
9
- class Ruby < Generator
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(mods[:tableizer].table, out)
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
- grammar.all_productions.each do |production|
27
- out <<
28
- "[" <<
29
- production.label.name.inspect <<
30
- ", " <<
31
- production.items.size.inspect <<
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 production.block.empty?
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 = v.each.select { |_, (a, b)| a.is_a?(Array) }
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
 
@@ -1,3 +1,4 @@
1
+
1
2
  # This file assumes that the output of the generator will be placed
2
3
  # within a module or a class. However, the module/class requires a
3
4
  # `type` method, which takes a terminal and gives its type, as a
@@ -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
- # Generates a parser. This is normally the parent class, and the
11
- # specific implementations inherit from this. The generated
12
- # parser should, ideally, be completely independent (not requiring
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
- # Actually does the generation. A subclass should implement this.
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
- # @raise [NotImplementedError]
53
- # @return [void]
54
- def generate
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
- 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.
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 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 = self.class.source_root + source
75
- src = src_file.open("r")
76
- context = instance_eval('binding')
77
- erb = ERB.new(src.read, nil, "%")
78
- erb.filename = source
79
- content = erb.result(context)
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"
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Antelope
4
4
  # The current running version of antelope.
5
- VERSION = "0.1.7".freeze
5
+ VERSION = "0.1.8".freeze
6
6
  end
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.7
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-06-29 00:00:00.000000000 Z
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