Traqula allows you to create a new parser by manipulating an existing parser through its builder. To create a parser, read how to create a parser. A tutorial on parser manipulation within Comunica provides a practical example on parser modifications.
In case your modification requires an additional lexerToken, or the removal of a lexerToken, you should manipulate the original LexerBuilder accordingly. LexerBuilder expose various functions to facilitate this behavior:
It is essential that you DO NOT manipulate the LexerBuilder created by another, but instead start by copying it:
LexerBuilder.create(existingBuilder)
Just as described in the creating a parser docs
you should construct the required parser rules in a similar way as the existing parser.
To this end, the implementation of an existing parser rule might help you,
to that end you can request the currently registered grammar rule object from some ParserBuilder using its name.
Say we cant to add an additional alternative to a rule named graphPatternNotTriples, meaning we either parse our new subrule, or the original rule:
// get the original rule definition. Note that the string is in fact type checked!
// The builder knows it has such an implementation from the typing
const originalGraphPatternNotTriples = sparqlParserBuilder.getRule('graphPatternNotTriples');
const additionalOptionsRule: typeof originalGraphPatternNotTriples = <const> {
name: originalGraphPatternNotTriples.name,
impl: $ => C => $.OR([
{ ALT: () => $.SUBRULE(myNewRule) },
{ ALT: originalGraphPatternNotTriples.impl($)(C) }
]),
};
Just like before, start by creating a copy of the ParserBuilder using ParserBuilder.create(existingParserBuilder).
The ParserBuilder has a few functions that facilitate parser modification:
What remains is to create the modified parser. This can be done in the same way as when creating a parser from scratch.
When exposing your parser, note that the parser is not all that counts. Help the ecosystem by exposing your ParserBuilder, tokens and grammar rules.
It is possible that you modify a certain parser rule in a way that its interface (e.g. arguments or returned type) does not match the original, breaking the parsers' implementation.
Take a parser rule myRule that returns an object of type number, when we patch that rule to now return number | string,
the rules that call of myRule using SUBRULE (e.g. otherRule) will might need to be patched to handle this new type.
In case no actual rule patching is needed, but the return-type of otherRule changes, we can inform the ParserBuilder of this changed type interface from otherRule using typePatch instead of patchRule.
TypePatch is a generic function that takes an object where the keys match the name of rules that need to change and the value is the new type of the returnType and arguments.
ParserBuilder
.create(originalBuilder)
.patchRule(myRule)
.typePatch<{
// Patch only the returnType
otherRule: [ NewReturnType ],
// Patch both the returnType and the arguments
anotherRule: [ NewReturnType, [ NewArgumentType1, NewArgumentType2 ]]
// Rules not listed remain untouched
}>();
Once again, it is essential that you DO NOT manipulate the ParserBuilder created by another, but define your own builder starting from an existing one:
ParserBuilder.create(existingBuilder)
Although type patching is not strictly necessary for people modifying a parser, it is hugely helpful for people using your modified parser builder. The SPARQL 1.2 parser Traqula provides for example performs type patching to ensure correctness of all rules in that parser builder.