Minimum Viable Interpreter (for single Excel formulas)
Project description
mvin: Minimum Viable Interpreter for Excel Formulas
mvin is a lightweight, dependency-free interpreter for evaluating single Excel-like formulas from
tokenized input. It is built around a shunting-yard parser with a small, extensible
function/operator surface.
If this library saved your team hours of manual formatting, consider buying me a coffee! ☕ Donations help prioritize support for new Excel formulas and complex CSS mapping.
Why mvin
- No runtime dependencies.
- Works with tokenizer output (for example,
openpyxltokens). - Supports numeric, comparison, and string-concatenation operators.
- Supports unary prefix operators (
+x,-x). - Allows custom function maps and operator maps.
- Dual licensed under MIT or Apache-2.0.
Installation
pip install mvin
Python support: >=3.9,<4.0.
Quick Start
from mvin import TokenNumber, TokenOperator
from mvin.interpreter import get_interpreter
tokens = [
TokenNumber(1),
TokenOperator("+"),
TokenNumber(2),
]
run = get_interpreter(tokens)
result = run({}) if run else None
assert result == 3
get_interpreter(...) returns a callable that evaluates the expression. Inputs for cell references
are passed as a dictionary.
Token Contract
mvin accepts any token object with these attributes:
type: strsubtype: strvalue: Any
Built-in token classes are available in mvin (TokenNumber, TokenString, TokenBool,
TokenOperator, etc.), but third-party tokenizers are supported if they follow the same shape.
Supported Operators
| Operator | Meaning |
|---|---|
+ |
Addition |
- |
Subtraction |
* |
Multiplication |
/ |
Division |
^ |
Exponentiation |
& |
String concatenation |
= / == |
Equality |
<> / != |
Inequality |
< |
Less than |
<= |
Less than or equal |
> |
Greater than |
>= |
Greater than or equal |
+x |
Unary plus (prefix) |
-x |
Unary minus (prefix) |
Built-in Functions
Built-ins are defined in DEFAULT_FUNCTIONS in src/mvin/functions/excel_lib.py.
| Function | Notes |
|---|---|
NOT(value) |
Accepts logical or numeric values. |
ISERROR(value) |
Returns whether value is an error token. |
SEARCH(find_text, within_text, [start_num]) |
1-based index; defaults start_num to 1. |
LEFT(text, [num_chars]) |
Defaults num_chars to 1. |
RIGHT(text, [num_chars]) |
Defaults num_chars to 1. |
LEN(text) |
Length of text representation. |
Working with References (Ranges)
If a token has type="OPERAND" and subtype="RANGE", its value is treated as an input key.
- Required keys are exposed as
run.inputs. - Inputs should map reference name to token objects.
from mvin import BaseToken, TokenNumber
from mvin.interpreter import get_interpreter
class RefToken(BaseToken):
def __init__(self, ref: str):
super().__init__()
self._value = ref
self._type = "OPERAND"
self._subtype = "RANGE"
tokens = [RefToken("A1")]
run = get_interpreter(tokens)
assert run is not None
assert run.inputs == {"A1"}
assert run({"A1": TokenNumber(10)}) == 10
Customizing Functions
Pass a custom function map through proposed_functions.
Function keys follow tokenizer function-open values (for example, "MYFUNC(").
from mvin import BaseToken, TokenNumber
from mvin.interpreter import get_interpreter
from mvin.functions.excel_lib import DEFAULT_FUNCTIONS
class T(BaseToken):
def __init__(self, value: str, token_type: str, subtype: str):
super().__init__()
self._value = value
self._type = token_type
self._subtype = subtype
def excel_double(value):
if value is not None and value.type == "OPERAND" and value.subtype == "NUMBER":
return TokenNumber(value.value * 2)
return value
custom_functions = dict(DEFAULT_FUNCTIONS)
custom_functions["DOUBLE("] = ([None], excel_double)
tokens = [
T("DOUBLE(", "FUNC", "OPEN"),
TokenNumber(21),
T(")", "FUNC", "CLOSE"),
]
run = get_interpreter(tokens, proposed_functions=custom_functions)
assert run is not None
assert run({}) == 42
Public API Stability
mvin follows semantic versioning.
- Patch: bug fixes only.
- Minor: backward-compatible features.
- Major: breaking API changes.
Public API guarantees are documented in API_STABILITY.md.
Development
Setup
pdm install -G dev
Run tests
pdm run pytest -q
Run lint + types
pdm run ruff check src tests
pdm run mypy
Build
pdm build
CI/CD
GitHub Actions workflows in .github/workflows/ci.yml and .github/workflows/release.yml run:
- tests on Python 3.9-3.13
- lint + type checks
- build +
twine check - wheel smoke test
Tag pushes matching v* also publish to PyPI (requires PYPI_API_TOKEN repository secret).
Contributing and Security
- Contribution guide:
CONTRIBUTING.md - Security policy:
SECURITY.md - Release checklist:
RELEASE.md - Changelog:
CHANGELOG.md
License
Licensed under either of:
- Apache License, Version 2.0 (
LICENSE_APACHEor https://www.apache.org/licenses/LICENSE-2.0) - MIT license (
LICENSE_MITor https://opensource.org/licenses/MIT)
at your option.
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 mvin-0.15.0rc1.tar.gz.
File metadata
- Download URL: mvin-0.15.0rc1.tar.gz
- Upload date:
- Size: 25.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.26.6 CPython/3.14.3 Darwin/24.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3f6a1b3a99e0f8b7418036165daf661b61738848f64040cc78f00e634c1645b
|
|
| MD5 |
4ae157f6f4f87dcfe0c3688bb993fb0e
|
|
| BLAKE2b-256 |
4a1abb93f516f1e1eac2c8f18b3eda9d7e77e97ddb8aca406559338ca4443e5e
|
File details
Details for the file mvin-0.15.0rc1-py3-none-any.whl.
File metadata
- Download URL: mvin-0.15.0rc1-py3-none-any.whl
- Upload date:
- Size: 25.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.26.6 CPython/3.14.3 Darwin/24.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9424da99db8be34cb41bedcef902340586467395d16639131588dcc418d67a50
|
|
| MD5 |
04336b9efbb657a8dbb3bb1797531a2a
|
|
| BLAKE2b-256 |
d0c26c86745dc8bb8904f4bcb02edb222d28bd5e3a6a8c4e24ead31ae3b80ec1
|