Skip to main content

Funcparserlib

Recursive descent parsing library for Python based on functional combinators.

PyPI PyPI - Downloads

Description

The primary focus of funcparserlib is parsing little languages or external DSLs (domain specific languages).

Parsers made with funcparserlib are pure-Python LL(*) parsers. It means that it's very easy to write parsers without thinking about lookaheads and other hardcore parsing stuff. However, recursive descent parsing is a rather slow method compared to LL(k) or LR(k) algorithms. Still, parsing with funcparserlib is at least twice faster than PyParsing, a very popular library for Python.

The source code of funcparserlib is only 1.2K lines of code, with lots of comments. Its API is fully type hinted. It features the longest parsed prefix error reporting, as well as a tiny lexer generator for token position tracking.

The idea of parser combinators used in funcparserlib comes from the Introduction to Functional Programming course. We have converted it from ML into Python.

Installation

You can install funcparserlib from PyPI:

$ pip install funcparserlib

There are no dependencies on other libraries.

Documentation

There are several examples available in the tests/ directory:

See also the changelog.

Example

Let's consider a little language of numeric expressions with a syntax similar to Python expressions. Here are some expression strings in this language:

0
1 + 2 + 3
-1 + 2 ** 32
3.1415926 * (2 + 7.18281828e-1) * 42

Here is the complete source code of the tokenizer and the parser for this language written using funcparserlib:

from typing import List, Tuple, Union
from dataclasses import dataclass

from funcparserlib.lexer import make_tokenizer, TokenSpec, Token
from funcparserlib.parser import tok, Parser, many, forward_decl, finished


@dataclass
class BinaryExpr:
    op: str
    left: "Expr"
    right: "Expr"


Expr = Union[BinaryExpr, int, float]


def tokenize(s: str) -> List[Token]:
    specs = [
        TokenSpec("whitespace", r"\s+"),
        TokenSpec("float", r"[+\-]?\d+\.\d*([Ee][+\-]?\d+)*"),
        TokenSpec("int", r"[+\-]?\d+"),
        TokenSpec("op", r"(\*\*)|[+\-*/()]"),
    ]
    tokenizer = make_tokenizer(specs)
    return [t for t in tokenizer(s) if t.type != "whitespace"]


def parse(tokens: List[Token]) -> Expr:
    int_num = tok("int") >> int
    float_num = tok("float") >> float
    number = int_num | float_num

    expr: Parser[Token, Expr] = forward_decl()
    parenthesized = -op("(") + expr + -op(")")
    primary = number | parenthesized
    power = primary + many(op("**") + primary) >> to_expr
    term = power + many((op("*") | op("/")) + power) >> to_expr
    sum = term + many((op("+") | op("-")) + term) >> to_expr
    expr.define(sum)

    document = expr + -finished

    return document.parse(tokens)


def op(name: str) -> Parser[Token, str]:
    return tok("op", name)


def to_expr(args: Tuple[Expr, List[Tuple[str, Expr]]]) -> Expr:
    first, rest = args
    result = first
    for op, expr in rest:
        result = BinaryExpr(op, result, expr)
    return result

Now, consider this numeric expression: 3.1415926 * (2 + 7.18281828e-1) * 42.

Let's tokenize() it using the tokenizer we've created with funcparserlib.lexer:

[
    Token('float', '3.1415926'),
    Token('op', '*'),
    Token('op', '('),
    Token('int', '2'),
    Token('op', '+'),
    Token('float', '7.18281828e-1'),
    Token('op', ')'),
    Token('op', '*'),
    Token('int', '42'),
]

Let's parse() these tokens into an expression tree using our parser created with funcparserlib.parser:

BinaryExpr(
    op='*',
    left=BinaryExpr(
        op='*',
        left=3.1415926,
        right=BinaryExpr(op='+', left=2, right=0.718281828),
    ),
    right=42,
)

Learn how to write this parser using funcparserlib in the Getting Started guide!

Used By

Some open-source projects that use funcparserlib as an explicit dependency:

  • Hy, a Lisp dialect that's embedded in Python
    • 4.2K stars, version >= 1.0.0a0, Python 3.7+
  • Splash, a JavaScript rendering service with HTTP API, by Scrapinghub
    • 3.6K stars, version *. Python 3 in Docker
  • graphite-beacon, a simple alerting system for Graphite metrics
    • 459 stars, version ==0.3.6, Python 2 and 3
  • blockdiag, generates block-diagram image file from spec-text file
    • 148 stars, version >= 1.0.0a0, Python 3.7+
  • kll, Keyboard Layout Language (KLL) compiler
    • 109 stars, copied source code, Python 3.5+

Next

Read the Getting Started guide to start learning funcparserlib.

Metadata

Release files for funcparserlib 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for funcparserlib 1.0.1
File Size Uploaded Provenance
funcparserlib-1.0.1.tar.gz 17.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for funcparserlib 1.0.1
File Interpreter ABI Platform
funcparserlib-1.0.1-py2.py3-none-any.whl Python 2, Python 3 none any Details

Total release size: 35.1 kB

Release files / funcparserlib-1.0.1.tar.gz

Download URL funcparserlib-1.0.1.tar.gz
Size 17.2 kB
Tags Source
SHA-256 checksum
How to use checksums
a2c4a0d7942f7a0e7635c369d921066c8d4cae7f8b5bf7914466bec3c69837f4
BLAKE2b-256 checksum
How to use checksums
9344a21dfd9c45ad6909257e5186378a4fedaf41406824ce1ec06bc2a6c168e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.9.15

Release files / funcparserlib-1.0.1-py2.py3-none-any.whl

Download URL funcparserlib-1.0.1-py2.py3-none-any.whl
Size 17.8 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
95da15d3f0d00b9b6f4bf04005c708af3faa115f7b45692ace064ebe758c68e8
BLAKE2b-256 checksum
How to use checksums
8366acd740d84f59c655935f586c113a863aa404dfe932052a68a1163d88ea63
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.9.15

Security for funcparserlib 1.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page