Generators

Antelope comes with an assortment of generators; however, if you wish to create a custom generator, here's how.

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.

class MyGenerator < Antelope::Generator::Base

end

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 the templating generator over it over it).

class MyGenerator < Antelope::Generator::Base

  def generate
    template "my_template", "#{file}.my_file"
  end
end

Base provides a few convienince methods for you, one of them being template; 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:

class MyGenerator < Antelope::Generator::Base

  def self.source_root
    Pathname.new("/path/to/source")
  end

  def generate
    template "my_template", "#{file}.my_file"
  end
end

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:

{{ table.each_with_index do |hash, i| }}
  state {{= i }}:
{{   hash.each do |token, action| }}
    for {{= token }}, I'll {{= action[0] }} {{= action[1] }}
{{   end }}
{{ end }}

Note: in templates, blocks that start at the beginning of a line and end at the end of a line do not produce any whitespace.

table here is defined on the base class, and we're iterating over all of the values of it.

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).

The finialized product:

# my_generator.rb
class MyGenerator < Antelope::Generator::Base

  register_as "my_generator"

  def self.source_root
    Pathname.new("/path/to/source")
  end

  def generate
    template "my_template.erb", "#{file}.my_file"
  end
end
# my_template.ant
{{ table.each_with_index do |hash, i| }}
  state {{= i }}:
{{   hash.each do |token, action| }}
    for {{= token }}, I'll {{= action[0] }} {{= action[1] }}
{{   end }}
{{ end }}

Bundling

If you want to bundle a few generators together such that the bundle is generated together, you can use an Antelope::Generator::Group.
This would be useful for something like a C language generator, which may need to generate both a header and a source file:

class CHeader < Antelope::Generator::Base
  # ...
end

class CSource < Antelope::Generator::Base
  # ...
end


class C < Antelope::Generator::Group
  register_generator CHeader, "c-header"
  register_generator CSource, "c-source"
end

The register_generator takes a generator class and a name for the generator, and adds the generator to the list of generators on the receiver (in this case, the C class). Now, when C#generate is run, it will run both CHeader#generate and CSource#generate.

Using Compiler Directives

Directives are statements that are used in Ace files in order to pass information to Antelope. They normally follow the syntax %<directive name> [directive arguments]*. See the Ace file format for more information about directives.

In some cases, like in the Ruby generator, options from the Ace file are needed for generation. In the case of the Ruby generator, we need the error class that the developer wants the generator to use; and we reference it through the ruby.error-class directive. In order to define directives that can be used in custom generators, you just need to add a few lines:

class MyGenerator < Antelope::Generator::Base

  has_directive "my-generator.some-value", Boolean

end

In this example, we define a directive named my-generator.some-value; this directive is eventually coerced into a true/false value. In order to actually use the value of the directive, in either the template or a method on the generator, you can reference directives["my-generator.some-value"], which will be nil (it wasn't defined), true (it was defined, with any arguments), or false (it was explicitly defined with one argument, "false"). Some other values you can pass in place of Boolean would be :single (or :one), which only gives the first argument passed to the directive; an Array of types, which would coerce each argument into its corresponding element of the array; Array, which will give an array of the given arguments; String, which gives a string representation of the first argument; any Numeric subclass, which would coerce the first argument into an integer; Float, which would coerce the first argument into a float; any class, which would be instantized with the arguments to the directive. Any other values would yield an error.

It is recommended that you namespace directives that only your generator would use, using dashed syntax, like in our example above.
However, some directives are not namespaced, or are not namespaced under a generator; these may be used by any generator. It is also recommended that you declare every directive that you use.