Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Enki Directives

(Back to Syntax Guide)

Directives provide a way to tell Enki what to do during the semantic and code-generation passes. They supplement the information provided by the rules in a way that is very specific to how Enki functions - there's just no way to do these kinds of things with EBNF.

All Directives begin with '.' before, after or in between rules in your grammar file. For the exception of the .code directive, they all end with ';'.

Import

The .import directive instructs Enki to declare a D import for a particular module in your generated parser.

  .import("std.stdio");

Base Class

The .baseclass directive specifies what the base class for your generated parser should be.

  .baseclass("My.Base.Class");

Class Name

The .classname directive specifies what the classname of your generated parser should be.

  .classname("My.Class.Name");

Define

The .define directive is the most complicated of all of Enki's directives. In short, it provides a way to assure Enki that a particular rule parse function exists, so it won't complain that it's EBNF is not specified in the input file. It takes 4 parameters:

  • type - the D type that this rule returns
  • name - the name of the rule.
  • isTerminal - this is a true/false value that denotes if this rule is a terminal or not
  • description (optional) - a friendly description for this rule.

Examples:

  .define("String","MyRule",true,"Friendly description for this terminal rule");
  .define("uint","AnotherRule",false);

Enki uses this in its own grammar to define many rules that are actually coded in D within BaseParser?. While this may seem like an admission of a weakness in Enki itself, its actually by design. Only the most frequently used, typically invariant, and succinctly specified rules are hard-coded rather than left to the code generator. Rules of this nature are typically better off coded by hand as they will usually wind up more efficent than their machine-generated counterparts.

Include

The .include directive instructs Enki to parse an additional source file before continuing. T

  .include("some.other.file.bnf");

The additional file is parsed via a child Enki instance, and the new rules from this new instance are folded back into the rule set for the parent. At present, Enki requires each sub-file to be 100% parsable on its own (this may change). In short, its best used when you want to break a grammar into multiple pieces, and have a grammar that doesn't have any forward references.

Typelib

A cousin of the Include Directive is the Typelib Directive. This specifies the default type library that is used by the generated parser. If not specified, "enki.types" is used instead.

.typelib("my.type.lib");

Alias

The .alias directive is used to provide an alternate name for a given rule.

  .alias("whitespace","ws");

Module

The .module directive specifies the module name, for the D module statement, in the generated parser file.

  .module("My.Module.Name");

Code

The Code Directive allows for arbitrary input, delimited by "" and "". Enki is quite braindead about how it parses this and will not pay any attention to how your code is formatted between those two tokens.

  .code
  {{{
    uint foobar(){
      writefln("hello world"); 
    }
  }}}

The code will be inserted into the generated parser class body, logical to the position in which it is viewed within the input file. Care must be taken with the .code directive to not break the generated output, or disrupt how Enki is attempting to layout the rest of your code.

Boilerplate

Simliar to the Code Directive, the Boilerplate Directive is used to place arbitrary input into the generated parser. However, it will only be processed if it exists within the root-most input file - Boilerplates specified within Included files will not be processed.

The output all the Boilerplate Directives, within the root-most input file is placed at the top of the output file, before all other output. As the name suggests, this is usually used for any kind of header or boilerplate like version control information, source license text and change history.

.boilerplate{{{"/+
Released under the BSD License.  See license.txt for details.
+/}}}

The Header Directive is much more like the Code Directive than the Boilerplate Directive in that all Headers are processed, regardless of where they may exist in included files. The output of the header sections are placed just before the parser class definition in the generated output.

.header{{{"/* Some header text */);}}}

Parsetype

The parsetype directive tells the parser generator to use a particular type as the default for Rule Predicates. This way, one can develop a parser that is not based on 'String' types, which is the default.

The directive accepts any string or identifier as the name of the new type.

.parsetype("Token");

UTF

Along the same lines as the Parsetype directive, the UTF Directive emits some versioning code into the generated parser, that directs the compiler to use a particular character width. This is another way that the code generator can be steered towards using a particular type set.

.utf("32");

Only the values "8", "16" or "32" are valid as arguments to the UTF Directive.