This project comprises of a library which enables its users to define lexical parsers for their specific use-case.
Commonly known as lexers, lexical parsers are utilzed, typically, to break down source code into individual tokens. These tokens, when parsed, are used as the basic building blocks of language grammar.
The idea is simple, take some input string or byte array and identify each of its parts. Say for the example below, where we are assuming whitespace needs to be ignored and punctuation has its own identity, we would break the input down as such:
Example input: "Mary had a little lamb."
Example output: ["Mary", "had", "a", "little", "lamb", "."]Alexi works in the same fashion as the previous parsing example. Once a lexer
has been generated from the engine, it then will search through the stream one
token at a time with the Lexer::next_token() method. This will return a
Token instance as the basic information necessary for downstream IR
parsers, consumers and processors.
Listed below are examples of what the components this library is composed of. With some luck, their descriptions will assist you in understanding how to best take advantage of the library's features.
The Engine type is perhaps the highest level type in Alexi. This type
allows us to preconfigure the anticipated behavior of any Lexers generated
from it. The core intent is to be able define allowed token types (We use
Kind as a type definition), declare expected keywords and ensures common types
are included before producing a lexer.
| Method Name | Variations | Return Type |
|---|---|---|
| from_data | 2 | Lexer |
| from_file | 2 | Lexer |
| use_keywords | 2 | Engine |
| use_kind | 2 | Engine |
The Kind type is an Alexi primitive. When declared, it describes the rules
of what a Token should look like and how it should be handled. We use this
type throughout the rest of the library as the foundation for all other types.
This allows us to drive analysis according to whatever the expected syntax of
a given input should be.
There are some rules that govern a Kind:
- A
Kindmust be unique to otherKinddefinitions. - A
Kindmust have a unique name. - A
Kindpattern may be shared with otherKinds, but must use theKind::predicateto be distinguished from otherKinds. Kind::patternmay not capture all possible characters (R"(.*)").- The
Kind::ordermust be afloatbetween 0 and 1. - The
Kind::actionas a bit map must not contain bothAction::CONSUMEandAction::IGNORE.Action::CONSUMEwill overrideAction::IGNORE. - The
Kind::actioncannot beAction::NOTHING. This will produce and error.
#include "alexi/kind.hpp"
using namespace alexi;
static const Kind my_kind{
.pattern = R"(\w+)",
.name = "WORD",
// .action = Action::CONSUME,
// .order = 0.1,
// .natural = false,
// .predicate = [](auto _){ return true; }
};Kind member attributes can be found here.
Since most syntaxes are often reused or rarely change, Alexi provides a series
of predefined Kinds that can be found here. These definitions include
syntaxes for arithmetic operations, unnatural identifiers, strings and many
more.
The Matcher type enforces the primary purpose of a Kind. When consumed by
a Matcher, the contained Kind::pattern is compiled into a proper Regular
Expression. This will then be used to match against a buffer to try and
create tokens.
#include "alexi/kind.hpp"
#include "alexi/matcher.hpp"
using namespace alexi;
static const Kind my_kind{
.pattern = R"(\w+)",
.name = "WORD",
// .action = Action::CONSUME,
// .order = 0.1,
// .natural = false,
// .predicate = [](auto _){ return true; }
};
static const Matcher my_matcher(my_kind);
int main(int argc, const char *const *argv) {
// Should produce a std::optional<Token>;
auto token = my_matcher.match("Mary had a little lamb.");
return 0;
}The main driver of lexical analysis. Reads through a data stream attempting to match against its configured token matchers.
| Method Name | Variations | Return Type |
|---|---|---|
| next_token | 1 | Token |