Embedding API (Python)
ragul/embed.py lets you call Ragul directly from Python — no subprocess, no
shelling out to the ragul CLI, no parsing stdout. This is the entry point
for:
- A Python REPL or notebook — run Ragul snippets cell-by-cell and get the resulting values back as plain Python objects.
- A library dependency — another Python project can embed a Ragul pipeline as one step in a larger program.
- Tooling — anything that wants Ragul's diagnostics as structured
Python objects (
RagulDiagnostic) instead of formatted CLI text.
Status
The embedding API is new and unreleased (see CHANGELOG.md, [Unreleased]).
It ships as ragul.run, ragul.eval, and ragul.RagulError, re-exported
from the top-level ragul package.
ragul.eval(expr)
Evaluate a single Ragul expression/pipeline and return its value as a native Python object.
Under the hood this is sugar for ragul.run(): it wraps expr as a single
assignment sentence (result-into {expr}-it.), runs it, and returns the
result binding. Anything that is legal on the right-hand side of -into /
-be works here — arithmetic chains, string suffixes, list pipelines, and so
on:
ragul.run(source, filename="<embed>")
Run a Ragul program and get back its final top-level bindings as a plain
Python dict[str, Any].
import ragul
source = (
'sum-into 10-3-add-it.\n'
'product-into sum-2-mul-it.\n'
)
bindings = ragul.run(source)
print(bindings) # {'sum': 13, 'product': 26}
filename is only used to label diagnostics (compile errors will report this
string as the "file"). Pass something meaningful — e.g. a notebook cell id —
if you're embedding Ragul in a tool that runs many separate snippets.
ragul.run() follows the exact same Lex → Parse → Interpret pipeline as
the ragul futtat / ragul run CLI command — it does not run the type
checker. That has two consequences worth knowing:
- Static checks that only exist in the type checker (E001, E003, E004, E005,
E006, E007, E009, W001 — see Error Codes)
are not enforced by
ragul.run()/ragul.eval(). If you want those checks, runragul ellenőriz/ragul checkon the source separately (via the CLI, or by drivingragul.pipeline.orchestrator.CompilerPipelinedirectly with the"ellenőriz"command). - Because effect-boundary checking (E004) is one of those type-checker-only
rules, effect suffixes such as
-print-doing/-nyomtat-vado execute immediately even in a bare top-level sentence passed toragul.run()— there is no static gate stopping them the way there would be if you ranragul ellenőrizon the same source.
Known limitation: bare top-level sentences only
ragul.run() only captures bindings made by bare top-level sentences —
the same shape used above, with no scope wrapper and no leading tab. This is
not how a normal .ragul file is written; ordinary programs wrap their
body in a named effect scope:
Do not do this with ragul.run(). If you wrap the source in
program-ours-effect / -nk-hatás, the assignments happen inside that
child scope's own environment, which is never merged back into the
top-level bindings run() returns — you get back {} instead of an error.
This is a pre-existing interpreter environment-scoping limitation, tracked
separately from the embedding API itself (see CHANGELOG.md, [Unreleased]).
Errors — ragul.RagulError
Both run() and eval() raise ragul.RagulError — never a bare exit code —
on a compile-time (lexer/parser) failure. This is deliberate: embedded
callers want exceptions, not shell exit code conventions.
import ragul
try:
ragul.run("broken-t-va.\n") # case suffix (-t) before action suffix (-va): E002
except ragul.RagulError as e:
print(e) # formatted diagnostic text, same as the CLI shows
for d in e.diagnostics: # list[RagulDiagnostic]
print(d.code, d.file, d.line, d.message)
e.diagnosticsis the list ofRagulDiagnosticobjects (seeragul/errors.py) produced by whichever phase failed — the same objects the CLI prints.str(e)joins each diagnostic's.format()output with newlines, so printing the exception directly gives you CLI-quality error text.
Printing diagnostics on Windows
A formatted RagulDiagnostic can contain non-ASCII characters — for
example the suggestion line is prefixed with → (U+2192 RIGHTWARDS
ARROW). A plain Windows terminal defaults to a legacy codepage (e.g.
cp1252) that can't encode that character, so print(e) / print(d)
can raise UnicodeEncodeError and crash your script. ragul/main.py
works around this for the CLI by reconfiguring stdout/stderr to
UTF-8 at startup:
import sys
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
Do the same near the top of any script that prints a RagulError /
RagulDiagnostic directly — see examples/embedding/quickstart.py,
which needs exactly this to run on a default Windows console.
Runtime errors carry no diagnostics
Because ragul.run() / ragul.eval() skip the type checker, most
problems that would be a clean, structured diagnostic under
ragul ellenőriz instead surface at runtime — e.g. an undefined root,
or a suffix applied to a value of the wrong type. The interpreter itself
catches those, prints a message to stderr, and returns a non-zero
exit code — it does not add anything to the diagnostics list. embed.run()
still raises ragul.RagulError in that case (since the underlying
TaskResult.ok is False), but e.diagnostics will be an empty
list and str(e) will be an empty string. If you need the actual
message, check stderr, or treat "a RagulError with no diagnostics" as
"a runtime failure" in your own error handling.
When to reach for this vs. the CLI
| Use case | Recommended |
|---|---|
Running a .ragul project file, want console I/O and exit codes |
ragul futtat / ragul run (CLI) |
| Calling a small Ragul pipeline from a Python script or notebook, want the result as a Python value | ragul.eval() / ragul.run() |
| Embedding Ragul as one step of a larger Python program | ragul.run() |
| Type-checking source without running it, from Python | not yet exposed as a dedicated function — use the CLI (ragul ellenőriz) or drive ragul.pipeline.orchestrator.CompilerPipeline().run("ellenőriz", ...) directly |
See also examples/embedding/quickstart.py
for a runnable script covering both functions, the bare-top-level-sentences
limitation, and the error path.