PurePy (a pure functional subset of Python 3.12): tokenizer, parser, checker and interpreter
Dependencies
moon add marianoguerra/pure-pyimport {
"marianoguerra/pure-py" @purepy,
"marianoguerra/pure-py/ast",
"marianoguerra/pure-py/eval",
"marianoguerra/pure-py/value",
}///|
test "parsing" {
let m = @purepy.parse("print(\"hello\")\n")
inspect(
@ast.dump(m),
content=(
#|Module
#| body:
#| Expr
#| value:
#| Call
#| func:
#| Name id=print ctx=Load
#| args:
#| Constant value='hello'
#| keywords:
#|
),
)
}///|
test "the sieve rejects what PurePy excludes" {
let m = @purepy.parse("for x in [1]:\n print(x)\n")
match @purepy.sieve(m) {
Some(d) => {
inspect(d.message(), content="for loops prohibited")
// 1 for a form PurePy excludes, 2 for one it plans to accept.
inspect(d.exit_code(), content="1")
}
None => fail("a for loop is not PurePy")
}
}
///|
test "the checker decides definite assignment and the capture rules" {
let good = @purepy.parse("x = 1\nprint(x)\n")
inspect(@purepy.check(good) is None, content="true")
let bad = @purepy.parse("x = 1\nf = lambda: x\nx = 2\n")
match @purepy.check(bad) {
Some(d) =>
inspect(
d.message(),
content="'x' captured by previous statement, reassigned here",
)
None => fail("a captured name may not be reassigned")
}
}///|
test "running a program" {
let modules = Map([
("__main__", @purepy.parse("from lib import double\nprint(double(21))\n")),
("lib", @purepy.parse("def double(n):\n return n * 2\n")),
])
let (output, result) = @purepy.run(@purepy.source_tree(modules))
inspect(result is Finished, content="true")
inspect(
output,
content=(
#|42
#|
),
)
}///|
test "an undefined operation" {
let modules = Map([("__main__", @purepy.parse("if 5:\n print(1)\n"))])
let (_, result) = @purepy.run(@purepy.source_tree(modules))
match result {
Undefined(op) => inspect(op, content="an if condition on int")
_ => fail("Python's truthiness is not PurePy's")
}
}///|
test "a guest calling a function the host supplied" {
let sink = @eval.Sink::new()
let host = @eval.Host::new(
write=fn(t) { sink.write(t) },
modules=[
@eval.HostModule::{
name: "clock",
members: [("now", @value.host_fn("clock.now"))],
},
],
call=fn(name, _) {
match name {
"clock.now" => Val(Int(1757260800N))
_ => Stuck("no such host function")
}
},
)
let modules = Map([
("__main__", @purepy.parse("from clock import now\nprint(now())\n")),
])
let tree = @purepy.source_tree(modules)
// The host goes to the checker too, so `from clock import now` resolves
// before anything runs.
inspect(@purepy.check_program(tree, host~) is None, content="true")
inspect(@purepy.run_with(tree, host) is Finished, content="true")
inspect(
sink.text(),
content=(
#|1757260800
#|
),
)
}///|
test "building a tree and printing it" {
let m = @ast.module_of([
@ast.Stmt::dataclass("Point", ["x", "y"]),
@ast.Stmt::expr_stmt(
@ast.Expr::call(@ast.Expr::name("print"), [
@ast.Expr::call(@ast.Expr::name("Point"), [
@ast.Expr::int(1),
@ast.Expr::int(2),
]),
]),
),
])
inspect(
@purepy.unparse(m),
content=(
#|@dataclass
#|class Point:
#| x: Any
#| y: Any
#|print(Point(1, 2))
#|
),
)
}pure-py tokens FILE one token per line
pure-py dump FILE [--pos] the abstract syntax tree
pure-py unparse FILE Python source regenerated from the tree
pure-py parse FILE... reject what PurePy excludes syntactically
pure-py check FILE... decide module well-formedness
pure-py check-program MAIN decide program well-formedness
pure-py run MAIN [ARGS...] evaluate a programerror[purepy::captured-reassignment]: 'x' captured by previous statement, reassigned here
╭─[ shadow_captured.py:6:5 ]
│
4 │ ╭ def g():
5 │ ├ return x
│ ╰─ 'x' captured here
6 │ x = 6
│ ──┬──
│ ╰─ reassigned here
│
├─ help: a closure captured this name, and PurePy has no cell for it to see
│ a later value; bind a new name instead
╰─just quick type-check, format, unit tests, layering, conformance
just conform every oracle, held to its ratchet
just one PATTERN the tests whose name contains PATTERN, in fullfn run_with(tree : SourceTree, host : Host, max_depth? : Int, done? : (RunResult) -> Unit) -> RunResultInstall
Download zipPurePy (a pure functional subset of Python 3.12): tokenizer, parser, checker and interpreter
Dependencies