Getting Help

To see a list of available commands and options that apply to all commands:

daedalus --help

To see a list of options for a specific command:

daedalus COMMAND --help

Command show-types

To type-check a DaeDaLus specification and see the types of the declarations:

daedalus show-types MyParserSpec.ddl

The command prints information about the types and parsers defined by the given specification. For example:

Sample output for show-types
Types
======

def List a =
  union
    nil: {}
    cons: NonEmptyList a

def NonEmptyList a =
  struct
    head: a
    tail: List a


Parsers and Semantic Values
===========================

def List:
  for any type a:
  parameter: parser of a
  defines: parser of List a

def NonEmptyList:
  for any type a:
  parameter: parser of a
  defines: parser of NonEmptyList a

def Main:
  defines: parser of List (uint 8)

The resulting types have the following form:

def ParserName:
  parameter: <type A>
  parameter: <type B>
  ...
  defines: <type C>

This resembles a C type declaration as follows:

<type C> ParserName(<type A>, <type B>, ...);

The types themselves may be simple types such as integers or arrays, but they often have the form parser of <type A>. This indicates that the parameter or result is a parser, that itself generates semantic values of type A.

Command run

This command is used to run a DaeDaLus specification using the interpreter:

daedalus run MyParserSpec.ddl --input=input.txt

This command will run the parser defined in MyParserSpec.ddl on the input from file input.txt.

If successful, the resulting semantic value will be shown on stdout.

Flags

--input=FILE

Specifies that the input for the parser should be read from the given file. If no --input is specified, then the interpreter will run with an empty input.

--entry=[MODULE.]NAME

Specifies which parser defined by the specification should be executed. If no --entry is specified, then the interpreter will run the parser called Main.

The entry parser should have a fixed type (i.e., it should not be polymorphic) and it should not have any parameters, implicit or explicit.

--json

Show the parsed semantic valus using a JSON based format.

--html

Show the parsed semantic value rendered as HTML.

--core

Use the Core IR interpreter.

--vm

Use the VM IR interpreter.

Command: compile-hs

To compile a DaeDaLus parser specification to Haskell:

daedalus compile-hs MyParserSpec.ddl --out-dir=some_dir_name

The result is a directory populated with a Haskell module containing definitions for the parsers and functions defined in the specification. In addition, the DaeDaLus compiler will generate a sample executable driver and Cabal package description for easy prototyping. To use the generated code you’ll have to compile it with the rts-hs package provided in this distribution.

Warning

At present the command compile-hs uses the old Haskell backend which may go away in future. To use the new backend, which is actively maintained, please use Template Haskell. If there is demand, we may adapt the new backend to generate explicit Haskell files, in which case this command will start using the new backend.

Command: compile-c++

To compile a DaeDaLus package specification C++:

daedalus compile-c++ MyParserSpec.ddl --out-dir=some_dir_name

Generated Files

The compiled parser is in two files:
  • main_parser.h contains the interface to the generated parser, and

  • main_parser.cpp contains the implementation of the parser.

In addition, the DaeDaLus compiler will generate a sample executable driver and Makefile illustrating how to build the parser and generate Doxygen documentation. The sample executable expects that the compiled specification contains a parser named Main that has a fixed type and no parameters of any kind—if this is not the case, the sample driver will fail to compile.

The sample executable driver will be generated when:

  • there is no explicit --entry specified, and

  • there is no custom user state --user-state, and

  • the flag --lazy-streams is not enabled

Flags

--out-dir=DIRECTORY

Save generated files in the given directory. The directory will be created if it does not exist.

--out-dir-header=DIRECTORY

Save generated header files in the given directory. The directory will be created if it does not exist. If this is not specified, then the headers will be saved in --out-dir

--file-root=STRING

Use the given string for the names of the generated parser implementation and header. If not specified this will be main_parser.

--user-namespace=NAMESPACE

Place generated type declarations in the given name space. Note that at present this applies only to generate types. Generated parser entry point always reside in the default namespace. If this is not specified, this will be User

--no-error-stack

Disable call stack tracking in errors. Without this option, we generate code that leads to more detailed parse error messaged, at some runtime cost. Adding this flag leads to less detailed parse errors, but some potential performance gain.

--user-state=SATE_TYPE

Generate a parser where the parser’s state will be extended with some user state of the given type. This is useful in certain situations (e.g., caching of data), but care needs to be taken that updates to this state make sense even when the parsers backtracks.

--add-include=INCLUDE

Add an additional #include to the generated parser header file. Typically, this is used to support custom user state, or integration with an pre-compiled DaeDaLus parser. The parameter should just contain the thing to be incuded (e.g., "file.h" or <file.h>).

--entry=[MODULE.]NAME

Declare that the given parser is an entry point for the generated parser. Specifying an entry point will disable the generation of a sample driver executable. The name of the C++ function corresponding to DaeDaLus declaration F is parseF. The entry point should have a fixed type, but MAY have argument.

Sample entry point for parser Main with no extra arguments, returning a semantic value of type T.
void parseMain ( DDL::ParseError &error
               , std::vector<T>> &results
               , DDL::Input      input
               );

If an entry point has arguments, they’ll come after the input argument. The entry point returns any parsed semantic values in the results vector, there could be multiple results if the grammar is ambiguous. If there are no results (i.e., results is empty), then error will contain information about the parser error.

--inline

Perform aggressive inlining—all parsers that can be inlined will be inlined at each use site. Sometimes this may improve performance, but sometimes it may reduce it, due to a significant increase in the size of the generate code.

--inline-this=[MODULE.]NAME

Inline uses of a specific parser.

--extern=MODULE[:NAMESPACE]

Do not generate declarations for the types declared in the given module. This is useful when interfacing with a pre-compiled DaeDaLus parser, which already contains declarations for the given types. Often, this option will be combined with add-include to include the declarations of the already compiled code. If a namespace is provided, then uses of types in the given module will be qualified with the given namespace. Otherwise, the default namespace User will be used.

--lazy-streams

Generate code that uses streams where not all data is available when the parser starts. The generated code uses coroutines, as defined by the fiber class from boost-context library. If the parser reaches the end of an incomplete data stream, it yields to another coroutine which should either provide more data or terminate the stream before resuming the parser. For more details see ddl/stream.h. The data streams used by the parser are as defined in class DDL::Stream. The interaction between the data provider and the parser should be done using class DDL::ParserThread.

Command: compile-rust

To compile a DaeDaLus parser specification to Rust:

daedalus compile-rust MyParserSpec.ddl

By default, the output will be saved in a directory whose name is derived from the name of the input file (e.g., MyParserSpec.ddl becomes MyParserSpec). You can specify a different output directory using the --out-dir flag.

The result is a directory containing a complete Rust crate with the generated parser and type definitions. The generated code uses the Daedalus Rust runtime system (RTS), which provides core parsing functionality.

Generated Files

The compiled parser is placed in a standard Rust crate structure:
  • src/lib.rs contains the generated parser implementation and type definitions

  • Cargo.toml provides the Rust package configuration

  • A sample executable driver src/main.rs (if no explicit --entry is specified)

To build the generated parser:

cd MyParserSpec
cargo build --release

The --save-rts flag can be used to include the RTS source code directly in the output directory, which is useful for standalone builds that do not depend on an external RTS installation.

Flags

--out-dir=DIRECTORY

Save generated files in the given directory. The directory will be created if it does not exist. If not specified, a directory name is derived from the input file name.

--rts-path=PATH

Specify the path to the Daedalus Rust runtime system. This is used when the RTS is located in a non-standard location. If not specified, the default path is ../rts-rust relative to the output directory.

--save-rts

Include the source code for the Daedalus RTS in the output directory. This creates a standalone build that does not depend on an external RTS installation. The RTS will be copied to a rts-rust subdirectory within the output directory.

--no-error-stack

Disable call stack tracking in parse errors. Without this option, the generated code includes grammar stack traces in error messages, which provide more detailed diagnostic information at some runtime cost. Adding this flag produces less detailed parse errors but may improve performance.

--entry=[MODULE.]NAME

Declare that the given parser is an entry point for the generated parser. The entry point should have a fixed type, but MAY have arguments. Multiple entry points can be specified by using this flag multiple times. Specifying at least one entry point will disable the generation of the sample executable driver.

--determinize

Apply determinization transformations to the Core IR. This optimization can improve parser performance by eliminating non-deterministic choice where possible.

--keep-match

Do not remove matching constructs during compilation. By default, the compiler removes certain matching patterns as an optimization.

--no-shrink-biased

Do not attempt to shrink the scope of biased choice operations. By default, the compiler optimizes biased choice by reducing its scope where possible.

--no-case-of-case

Do not attempt to eliminate case-of-case patterns. By default, the compiler applies this optimization to simplify nested pattern matching.

--inline

Perform aggressive inlining—all parsers that can be inlined will be inlined at each use site. This may improve or reduce performance depending on the specific parser structure and the size of the generated code.

--inline-this=[MODULE.]NAME

Inline uses of a specific parser. This flag can be used multiple times to inline different parsers selectively.

--strip-fail

Remove failure nodes from the Core IR during optimization. This can reduce code size but may make debugging more difficult.

--no-loops

Remove loop constructs from the Core IR, transforming them into recursive functions.

--no-bitdata

Remove bitdata constructs from the Core IR, transforming them into equivalent operations on standard integer types.

--spec-types

Specialize polymorphic types during compilation. This can improve performance but may increase code size.

--no-core-check

Skip validation of the Core IR after transformations. This can speed up compilation but should be used with caution as it may allow invalid IR to reach code generation.

--extern=MODULE[:NAMESPACE]

Do not generate definitions for the types declared in the given module. This is useful when interfacing with another pre-compiled DaeDaLus parser that already contains definitions for the given types. If a namespace is provided, uses of types in the given module will be qualified with that namespace.

Unsupported Features

The Rust backend currently does not support the following features:

  • Stack capture: Parsers that capture the execution stack (closures that capture parser state) are not supported.

  • External functions: Externally defined functions (primitives defined outside of DaeDaLus) cannot be compiled to Rust.

  • User types iwth numeric parameters: Types that are parameterized by numeric values are not supported.

  • Builder emission: The emitBuilder operation for emitting builder values into other builders is not yet implemented.

  • Integer ranges: These only work for sint and uint, but not for int (i.e., arbitrary-precision integers).

If your parser uses any of these features, the compiler will report an error indicating which feature is unsupported.

Command: dump

It shows the various IR structures of the DaeDaLus compiler. This command is largely for debugging.

--parsed

Show the results of just parsing a specification. This ensures that a specification is syntactically correct.

--resolved

Show the results of name resultion. This associates uses of names with their definitions.

--tc

Show the results of type-checking. This validates the specification and esnures that parsers are used correctly (e.g. have the correct number of arguments with the correct types). The dump command will show this phase, unless another is specified.

--spec

Show the IR after specialization. Specialization eliminates polymorphism and parsers with other parsers as arguments. This works by generating custom instances of a parser (similar to C++ template expansion). After this pahse, parsers are monomorphic and have only semantic values as parameters.

--core

Show the Core IR representation. A number of DaeDaLus constructs are desugared into simpler constructs.

--vm

This is the lowest level IR, which is used to generate code. It is essentially a control flow graph in SSA form, except that instead of using “phi” blocks, basic blocks have parameters.