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