A lightweight lexical analyzer
Project description
alex
alex is a lexical analyzer tool.
It reads an input string and converts it into a list of Token objects.
A Token contains:
- a name (token type)
- the lexeme (the exact matched text)
- the position in the input where it was found
A lexeme is a sequence of input characters that together form a single token.
alex provides a clean, predictable, and idiomatic Python API for building lexers that are easy to understand, test, and extend.
With the right composition of input rules, it can scan any text file and extract the tokens you define.
Key Features
-
Straightforward token model
Every token carries its type, matched text, and position. -
Composable rule system
Define patterns using regular expressions and map them to token classes. -
Deterministic lexer pipeline
Input is processed left‑to‑right with clear, explicit behavior. -
Domain‑specific exceptions
Errors are raised with meaningful context to simplify debugging. -
Minimal dependencies
Lightweight and suitable for both small utilities and large applications.
🚀 Installation
Install directly from PyPI:
pip install alex-lexer
Usage
Instantiate the class Alex and call one of the functions
- scan(text)
- scan_file(path)
- generate(text)
- generate_file(path)
The scan functions returns a list of Tokens while the generate functions creates a generator that returns one Token at a time.
When instantiating the class Alex you must provide definitions of the tokens that can appear in your input text.
The definitions to be defined are:
- operators (A list/tuple of tuples)
- regexps (A list/tuple of tuples)
- keywords (A list of strings)
Example - simple.py
import alex
OPERATORS = (
("ADD", "+"),
("SUB", "-"),
)
REGEXPS = (
("NUM", '^["0123456789"]*'),
)
lexer = alex.Alex(
operators=OPERATORS,
regexps=REGEXPS)
lexer.scan('1 + 2 - 123')
print('----(Tokens found)----------')
for token in lexer.tokens:
print(token)
will output the following:
----(Tokens found)----------
1: 1 NUM 1
1: 3 ADD +
1: 5 NUM 2
1: 7 SUB -
1: 9 NUM 123
The first column contains the line number and the second column contains the column number in witch the lexical token was found.
The third column contains the name of the token given in the definitions and the last column contains the lexeme of the token.
Example - keywords.py
import alex
OPERATORS = (
("EQ", "="),
("EQEQ", "=="),
)
REGEXPS = (
("ID", f"^[a-zA-Z_]*"),
("NUM", '^["0123456789"]*'),
)
KEYWORDS = [
'if',
'then',
]
lexer = alex.Alex(
operators=OPERATORS,
regexps=REGEXPS,
keywords=KEYWORDS)
lexer.scan('''if foo == bar
then x = 17''')
print('----(Tokens found)----------')
for token in lexer.tokens:
print(token)
In this example we have added a KEYWORDS definition and added a REGEXP to find identifiers.
The output is:
----(Tokens found)----------
1: 1 KEYWORD if
1: 4 ID foo
1: 8 EQEQ ==
1: 11 ID bar
2: 1 KEYWORD then
2: 6 ID x
2: 8 EQ =
2: 10 NUM 17
Error handling
When an error occurs one of the following exceptions is issued.
- AlexDefinitionError,
- AlexScanError,
AlexDefinitionError is raised when there is something wrong with your definitions and AlexScanError is raised when a problem is detected during the scanning of the input text.
AlexDefinitionError example - def_error.py
import alex
OPERATORS = (
("ADD", "+"),
("ADD", "-"),
)
REGEXPS = (
("NUM", '^["0123456789"]*'),
)
try:
lexer = alex.Alex(
operators=OPERATORS,
regexps=REGEXPS)
lexer.scan('1 + 2 - 123')
except alex.AlexDefinitionError as ex:
print('----(Error report)----------')
print(ex)
The output is
----(Error report)----------
Operator (ADD, '-'): Key 'ADD' already in use!
AlexScanError example - scan_error.py
import alex
OPERATORS = (
("EQ", "="),
)
REGEXPS = (
("ID", f"^[a-zA-Z_]*"),
("NUM", '^["0123456789"]*'),
)
lexer = alex.Alex(
operators=OPERATORS,
regexps=REGEXPS)
try:
lexer.scan('x = 1 + 1 + 3')
except alex.AlexScanError as ex:
print('----(Error report)----------')
print(ex)
print()
print('----(Tokens found)----------')
for token in lexer.tokens:
print(token)
The output is:
----(Error report)----------
Unrecognized char '+' ordinal 43
Following 10 characters are: 1 + 3
Last scanned Token: 1: 5 NUM 1
----(Tokens found)----------
1: 1 ID x
1: 3 EQ =
1: 5 NUM 1
If you don't want an exception you can define what shall happen when an unrecognized character is found.
-
skip_unrecognized_chars
-
treat_unrecognized_chars_as_an_operator
-
define a set of skip characters
lexer = alex.Alex( operators=OPERATORS, regexps=REGEXPS, skip_unrecognized_chars=True)
Then the output will be:
----(Tokens found)----------
1: 1 ID x
1: 3 EQ =
1: 5 NUM 1
1: 8 NUM 1
1: 11 NUM 3
or you can do
lexer = alex.Alex(
operators=OPERATORS,
regexps=REGEXPS,
treat_unrecognized_chars_as_an_operator=True)
and the output will be:
----(Tokens found)----------
1: 1 ID x
1: 3 EQ =
1: 5 NUM 1
1: 7 UNRECOGNIZED-CHAR +
1: 9 NUM 1
1: 11 UNRECOGNIZED-CHAR +
1: 13 NUM 3
or define a set of characters to skip. By default, space and tab are defined as characters to skip, but you can define your own set.
lexer = alex.Alex(
operators=OPERATORS,
regexps=REGEXPS,
skip_chars=' \t+')
And the output will be:
----(Tokens found)----------
1: 1 ID x
1: 3 EQ =
1: 5 NUM 1
1: 9 NUM 1
1: 13 NUM 3
Regular expressions
In the definitions of regular expressions the ^ character must be the first character in the expression.
REGEXPS = (
("NUM", '^["0123456789"]*'),
)
Keywords lists
Duplicate definitions of the same keywords will be silently removed.
KEYWORDS = [
'if',
'if',
'then',
]
by converting the keywords to a set.
{'if', 'then'}
The Token class
A Token class object has four attributes.
- name
- lexeme
- line_nbr
- col_nbr
and a repr() function that returns these attributes as one string.
Example - scanning python
This example shows how you can get tokens from a python file. It has been tested, but no garanti given that it works for every case.
It uses the flag
scan_python_indents=True
to save indent information as a token. The lexeme of such a token is the length of the indent.
This only works correct if the indents only consists of spaces and not tabs.
import alex
OPERATORS = (
("PLUS", "+"),
("MINUS", "-"),
("MUL", "*"),
("DIV", "/"),
("DOT", "."),
("COMMA", ","),
("COLON", ":"),
("SEMI", ";"),
("AT", "@"),
("MOD", "%"),
("EQ", "="),
("GT", ">"),
("LT", "<"),
("LP", "("),
("RP", ")"),
("LBR", "["),
("RBR", "]"),
("LCBR", "{"),
("RCBR", "}"),
("OR", "|"),
("XOR", "^"),
("NOT", "~"),
("AND", "&"),
("ADDEQ", "+="),
("SUBEQ", "-="),
("MULEQ", "*="),
("DIVEQ", "/="),
("IDIVEQ", "//="),
("MODEQ", "%="),
("LSHIFT", "<<"),
("RSHIFT", ">>"),
("IDIV", "//"),
("EXP", "**"),
("GE", ">="),
("LE", "<="),
("TYPE", "->"),
("NEQ", "!="),
("EQEQ", "=="),
("OREQ", "|="),
("XOREQ", "^="),
("ANDEQ", "&="),
("WALRUS", ":="),
("EXPEQ", "**="),
("LSHIFTEQ", "<<="),
("RSHIFTEQ", ">>="),
)
REGEXPS = (
("TSTR", r'^f?"""(?:\\.|(?!""").)*?"""|^f?\'\'\'(?:\\.|(?!\'\'\').)*?\'\'\''),
("STR", r'^f?"(?:\\.|[^"\\])*"|^f?\'(?:\\.|[^\'\\])*\''),
("NUM", '^["0123456789"]*'),
("REM", "^#[^\n]*"),
("ID", f"^[a-zA-Z_0-9]*"),
)
KEYWORDS = [
"False",
"None",
"True",
"and",
"as",
"assert",
"async",
"await",
"break",
"class",
"continue",
"def",
"del",
"elif",
"else",
"except",
"finally",
"for",
"from",
"global",
"if",
"import",
"in",
"is",
"lambda",
"nonlocal",
"not",
"or",
"pass",
"raise",
"return",
"try",
"while",
"with",
"yield",
]
lexer = alex.Alex(
operators=OPERATORS, regexps=REGEXPS, keywords=KEYWORDS, scan_python_indents=True
)
lexer.scan_file("test.py")
print("----(Tokens found)----------")
for token in lexer.tokens:
print(token)
Building distribution
To build a distribution that can be uploaded to PyPi, run the following command:
tools/build_dist.cmd
In order for this to work the build package must be installed.
Upload distribution to PyPi
In order to upload a new distribution you first have to bump up the version number in pyproject.toml. You can't upload the same version twice.
Thereafter, run the command
tools/install_pypi.cmd
Run documentation server locally
To test the documentation web pages you can start a local server with the command:
tools/run_docs_local_server.cmd
Upload documentation to Github
The documentation is found at https://rogerlindberg.github.io/alex
To upload new or editied documentation, use the command:
tools/docs_to_github.cmd
The documentation is created with mkdocs, which must be installed.
Run tests
For testing the package pytest must be installed.
All tests are found. in the tests directory.
To run all tests:
python tools/run_tests.cmd
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file alex_lexer-0.1.6.tar.gz.
File metadata
- Download URL: alex_lexer-0.1.6.tar.gz
- Upload date:
- Size: 14.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e25481f1d865ad9e389e4d3ba634e55a701ac564b2a631fe903a3de35a2e8f4
|
|
| MD5 |
24ea669197c3a15b2adc002c8a2e3776
|
|
| BLAKE2b-256 |
fd3e1006a9b0cbb1d7e22b2cf60f6405ff77d917164114bae893c8dd2a3688d0
|
File details
Details for the file alex_lexer-0.1.6-py3-none-any.whl.
File metadata
- Download URL: alex_lexer-0.1.6-py3-none-any.whl
- Upload date:
- Size: 10.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e75943da6c5bd4de4f1805305f4ecbbfcb305b709dcd851bdcf6efa1f08da67
|
|
| MD5 |
616a0e16b638a3bcb300d65714a8b696
|
|
| BLAKE2b-256 |
af9f996f0ec87c51d49529dc9e08a84e567143bf6f4acdb8be1b4e3539ad746b
|