-
Notifications
You must be signed in to change notification settings - Fork 40
Developer guide
Explain what the arena allocator (HAllocator
) is for, and how to use it, since it's going to come up a lot. The HACKING
file will be useful here.
stdint.h is your friend, and other things we should really have some kind of checkstyle template for anyway
-
Combinators are declared in src/hammer.h, defined in src/parsers/, and listed under the 'parsers' key in src/SConscript.
-
Declaration is done with the
HAMMER_FN_DECL
family of macros (see hammer.h) -
What gets declared is a function that returns an
HParser*
; this function needs to call a corresponding__m
function that callsh_new_parser
to instantiate anHParser*
with the combinator's vtable. Example, suitable for boilerplate:HParser* h_foo(const int n) { return h_foo__m(&system_allocator, n); } HParser* h_foo__m(HAllocator* mm__, const int n) { return h_new_parser(mm__, &foo_vt, (void*)(intptr_t)n); }
h_new_parser expects its third argument to be a void*; if a combinator has more than one argument, the combinator implementation should also define a struct to hold the arguments (referred to as the "environment"). See src/parsers/token.c for a simple example.
- Each vtable is a struct that must define the following members, all of which are function pointers:
-
.parse
- Signature:
static HParseResult* parse_foo(void *env, HParseState *state)
- destructures the environment that was passed in (if necessary)
- if a primitive combinator (e.g.
h_ch
), consumes input directly off the input stream usingh_read_bits
- if a higher-order combinator (e.g.
h_sequence
), applies its component parsers to the input stream usingh_do_parse
- constructs result
HParsedToken*
and wraps it in anHParseResult*
- Signature:
-
.isValidRegular
- for primitive combinators, this will just be
h_true
- for higher-order combinators that can never be regular (e.g.
h_indirect
), useh_false
- for higher-order combinators that need a function to determine whether their expansion is regular (e.g.
h_sequence
), the signature isstatic bool foo_isValidRegular(void *env)
- for primitive combinators, this will just be
-
.isValidCF
- for primitive combinators, this will just be
h_true
- for higher-order combinators that can never be context-free (e.g.
h_length_value
), useh_false
- for higher-order combinators that need a function to determine whether their expansion is context-free (e.g.
h_choice
), the signature isstatic bool foo_isValidCF(void *env)
- for primitive combinators, this will just be
-
.desugar
- Signature:
static void desugar_foo(HAllocator *mm__, HCFStack *stk, void *env)
- converts an
HParser*
to a sum-of-products representation for the context-free backends - there are some macros to make this easier, defined in src/backends/contextfree.h
- doesn't need to be defined if
.isValidCF = h_false
- Signature:
-
.compile_to_rvm
- Signature:
static void foo_ctrvm(HRVMProg *prog, void *env)
- converts an
HParser*
to a corresponding set of instructions for the regex VM. Those are defined in src/backends/regex.h.
- Signature:
-
- declare a new
extern HParserBackendVTable
in internal.h with the existing ones - add it to the
HParserBackend
enum in hammer.h - add it to
*backends
in hammer.c - add it to the 'backends' list in src/SConscript
- the vtable in your backend implementation is a struct which must define the following members, all function pointers:
-
.compile
- Signature:
int h_bar_compile(HAllocator* mm__, HParser* parser, const void* params)
- does any setup work for using this backend (e.g., generating parse tables)
- returns 0 on success, -1 on failure
- Signature:
-
.parse
- Signature:
HParseResult *h_bar_parse(HAllocator* mm__, const HParser* parser, HInputStream* stream)
- Signature:
-
.free
- Signature:
void h_bar_free(HParser *parser)
- for cleaning up anything that
.compile
created
- Signature:
-
Bindings live in src/bindings/LANGUAGE, and each such directory must have its own SConscript.
Template:
Import the environment as declared by the SConstruct. Add any other symbols you might need that were exported in SConstruct
or src/SConscript
. These are usually as follows:
Import('env libhammer_shared testruns targets')
Clone the environment, so that changes that need to be made for this part of the build don't affect other parts.
exampleenv = env.Clone(IMPLICIT_COMMAND_DEPENDENCIES = 0)
Add environment variables, compiler flags, &c for this part of the build.
exampleenv.Append(...)` # CPPPATH, CFLAGS, LDFLAGS, whatever
Set up your target, exampletarget
, with the appropriate builder (usually Command()
or SharedLibrary()
).
exampletarget = exampleenv.SharedLibrary(sources)
Then declare it as the default target.
Default(exampletarget)
Clone a test environment from your build environment, set up a target, then alias it to the "test" target using the Alias builder:
exampletest = testenv.Alias("test", [exampletesttarget], exampletesttarget)
Then tell it to AlwaysBuild()
, so that scons test
will always run tests even if they're already up to date:
AlwaysBuild(exampletest)
And add the target to the list of tests to run:
testruns.append(exampletest)
The list of supported bindings is near the top of the SConstruct: vars.Add(ListVariable('bindings', ...))
Add the name of the language you're binding to this list (it must also be the name of the directory you added to src/bindings) otherwise SCons won't be able to find it.
Using the build environment you created, set up your install target with the appropriate builder (usually Command()
):
exampleinstallexec = exampleenv.Command(...)
If the language you're binding has its own installation utilities, like Python's distutils or Perl's MakeMaker, use those for maximum cross-platform compatibility.
Then use the Alias builder to add your target to the install
alias:
exampleinstall = Alias('installexample', [exampleinstallexec], exampleinstallexec)
Then add the it to the list of targets to install:
targets.append(exampleinstall)