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:
Kind must be unique to other Kind definitions.Kind must have a unique name.Kind pattern may be shared with other Kinds, but must use the Kind::predicate to be distinguished from other Kinds.Kind::pattern may not capture all possible characters (R"(.*)").Kind::order must be a float between 0 and 1.Kind::action as a bit map must not contain both Action::CONSUME and Action::IGNORE. Action::CONSUME will override Action::IGNORE.Kind::action cannot be Action::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 |
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:
Kind must be unique to other Kind definitions.Kind must have a unique name.Kind pattern may be shared with other Kinds, but must use the Kind::predicate to be distinguished from other Kinds.Kind::pattern may not capture all possible characters (R"(.*)").Kind::order must be a float between 0 and 1.Kind::action as a bit map must not contain both Action::CONSUME and Action::IGNORE. Action::CONSUME will override Action::IGNORE.Kind::action cannot be Action::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 |