Parserlib
March 3, 2026 ยท View on GitHub
Writing a lexer and a parser
The library can be used to write a lexer and a parser.
This is achieved by using a parse context for character parsing, and then a parse context for the matches of the lexer parse context.
The lexer
Creating a lexer means to create a grammar around a parse context that parses characters.
It usually involves:
- specifying an enumeration with lexer ids
- specifying an enumeration lexer error ids
- defining the tokens
- creating a loop which also catches invalid characters
For example:
//id for lexer items
enum class LEXER_ID {
NUMBER,
IDENTIFIER,
STRING,
LEFT_PARENTHESIS,
RIGHT_PARENTHESIS,
ASSIGNMENT,
TERMINATOR
};
//lexer error id
enum class LEXER_ERROR_ID {
INVALID_CHARACTERS
};
using lp = parser<std::string::const_iterator, LEXER_ID, LEXER_ERROR_ID>;
//whitespace
auto space = lp::range('\0', ' ');
auto comment = "(*" >> *(lp::any() - "*)") >> "*)";
//number
auto digit = lp::range('0', '9');
auto number = +digit >> -('.' >> digit);
//identifier
auto letter = lp::range('a', 'z') | lp::range('A', 'Z');
auto identifier = (letter >> *(letter | digit))->*LEXER_ID::IDENTIFIER;
//string
auto string = ('"' >> *(lp::any() - '"') >> '"')->*LEXER_ID::STRING;
//symbols
auto left_parenthesis = lp::terminal('(')->*LEXER_ID::LEFT_PARENTHESIS;
auto right_parenthesis = lp::terminal(')')->*LEXER_ID::RIGHT_PARENTHESIS;
auto assigment = lp::terminal('=')->*LEXER_ID::ASSIGNMENT;
auto terminator = lp::terminal(';')->*LEXER_ID::TERMINATOR;
//token
auto token = space | comment | identifier | string | left_parenthesis | right_parenthesis | assignment;
//lexer
auto lexer = *(token | lp::error(LEXER_ERROR_ID::INVALID_CHARACTERS, lp::skip_before(token)));
The lexer can be used like this:
std::string source = ...;
lp::parse_context lexer_pc(source);
const bool ok = lexer.parse(lexer_pc);
The parser
Creating a parser from a lexer means to create a parser grammar around the lexer ids.
It usually involves:
- specifying an enumeration with parser ids
- specifying an enumeration parser error ids
- defining the rules
- creating the top level grammar for the parser
For example, a parser on the lexer defined above can be:
enum class PARSER_ID {
NAMED_VALUE,
NUMERIC_LITERAL,
STRING_LITERAL
};
enum class PARSER_ERROR_ID {
...
};
using pp = parser<lp::match_container_type::const_iterator, PARSER_ID, PARSER_ERROR_ID>;
extern pp::rule expression;
auto named_value = pp::terminal(LEXER_ID::IDENTIFIER)->*PARSER_ID::NAMED_VALUE;
auto numeric_literal = pp::terminal(LEXER_ID::NUMBER)->*PARSER_ID::NUMERIC_LITERAL;
auto string_literal = pp::terminal(LEXER_ID::STRING)->*PARSER_ID::STRING_LITERAL;
auto value = named_value
| numeric_literal
| string_literal
| LEXER_ID::LEFT_PARENTHESIS >> expression >> LEXER_ID::RIGHT_PARENTHESIS
;
pp::rule expression = *value;
auto declaration = pp::terminal(LEXER_ID::IDENTIFIER) >> LEXER_ID::ASSIGNMENT >> expression >> LEXER_ID::TERMINATOR;
auto parser = *declaration;
The parser can be used like this:
auto parser_pc = lexer_pc.derive_parse_context<PARSER_ID, PARSER_ERROR_ID>();
parser.parse(parser_pc);
The member function derive_parse_context equals the following:
pp::parse_context parser_pc(lexer_pc.get_matches());