Skip to main content

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)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

alex_lexer-0.1.5.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

alex_lexer-0.1.5-py3-none-any.whl (31.6 kB view details)

Uploaded Python 3

File details

Details for the file alex_lexer-0.1.5.tar.gz.

File metadata

  • Download URL: alex_lexer-0.1.5.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for alex_lexer-0.1.5.tar.gz
Algorithm Hash digest
SHA256 1301f2307597775b5be15e2b9d258e094bbb89d51f01be75b02ea19ed810acf8
MD5 20057a36e776e6fb8b6247261fd14497
BLAKE2b-256 adaba0e8794fde13a0ac62f88150e71926484cd7d943d77a090f60a98d6eb78c

See more details on using hashes here.

File details

Details for the file alex_lexer-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: alex_lexer-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 31.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for alex_lexer-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 2c2ed5f3dfbc3c3180c1f0791aae57a8c765d48e6b88d6e369c50567aa4d0f57
MD5 d0f2b1ac80f7e391d7bf48a3145dd3fc
BLAKE2b-256 a333f0d71f3deaae01e53e7d9323a6e0b14c142193fac1cfcd1a8f3af2e99b4a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page