Sign in

    moonpython

    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    4 hours ago
    Downloads
    5

    #moonbit-community/moonpython

    A small Python parser + interpreter implemented in MoonBit.

    #Status

    Recent additions:
    • PEP 701 f-strings (escaped braces, nested format specs, debug =).
    • PEP 695 syntax support for type parameters and type statements (parse/AST only).
    • Improved SyntaxError reporting for bracket mismatches and unclosed brackets.

    Notes:
    • Type parameters and type aliases are parsed and preserved in the AST; runtime behavior is currently a no-op.

    #Roadmap

    See ROADMAP.md for the current checklist and milestone status.

    #Testing

    This repo has two test layers:

    1. MoonBit tests in this repo (fast feedback for parser/runtime/bytecode)
    2. A pragmatic CPython Lib/test smoke runner (compatibility tracking)

    #Quick checks

    moon check --target native moon fmt

    #Run MoonBit tests

    Note: the generated spec suite (spec_generated_test.mbt) is large, so the first compile/run can take a while. Prefer --target native for speed.

    moon test --target native

    Useful variants:

    # Run a single test file moon test --target native -p moonbit-community/moonpython -f parser_line_test.mbt # Run a subset by name (glob) moon test --target native -F 'generated/expr/1454' # Update snapshots when behavior intentionally changes moon test --target native --update

    #Parse-only smoke check

    cmd/main supports --parse-only to validate parsing without execution:

    moon run cmd/main -- --parse-only path/to/file.py

    #Run a Python program (file)

    From the moonpython/ directory:

    moon run cmd/main -- path/to/program.py

    Example:

    moon run cmd/main -- examples/tasks.py

    #Run a module (-m)

    cmd/main supports -m (package __main__.py preferred when present):

    moon run cmd/main -- --stdlib Lib -m test.test_range

    #Using the CPython Lib/ snapshot

    This repo vendors CPython's Lib/ under moonpython/Lib/. You can point the interpreter to it with --stdlib:

    moon run cmd/main -- --stdlib Lib examples/stdlib_imports.py

    Notes:
    • --stdlib accepts any directory; Lib/ is the current CPython snapshot.
    • We do not support C extensions; only pure-Python modules are expected to run.

    #Smoke-run Lib/test

    There's a pragmatic runner in scripts/run_libtests.py that executes Lib/test/test_*.py modules via unittest (import-style execution) and reports pass/fail/timeout status:

    python3 scripts/run_libtests.py --target native --target-dir _build

    You can also run a single Lib/test module directly:

    moon run cmd/main --target native -- --stdlib Lib -m unittest test.test_unittest.test_case

    Useful flags:
    • --pattern REGEX: only run matching modules
    • --timeout SECONDS: per-module timeout (default: 20)
    • --slow-timeout SECONDS: timeout for known slow modules (default: 600)
    • --target-dir DIR: pass through to moon --target-dir to avoid build locks
    • --native-release: build the native runner in release mode
    • --runner direct: run modules as __main__ instead of importing via unittest

    Example: focus on unittest-related modules:

    python3 scripts/run_libtests.py --target native --target-dir _build \ --pattern '^test\\.test_unittest($|\\.)' --timeout 120

    #Compatibility checklist

    This is a coarse, user-facing checklist (not a full spec).

    Not implemented / incomplete (selected highlights):

    #REPL / stdin runner

    Use cmd/repl to execute code from stdin (line-by-line):

    printf 'print("hello")\n' | moon run cmd/repl

    Notes:
    • cmd/main runs a file path argument.
    • cmd/repl reads stdin, splits by lines, and executes each line.
    • In cmd/repl, use :quit / :exit to stop early.

    #Library usage

    If you want to call the interpreter from MoonBit code:

    let source = "print(\"hi\")\n"
    let result = @mpython.Interpreter::new().exec_source(source)
    match result {
    Ok(run) => {
    // run.value / run.stdout / run.stderr
    }
    Err(err) => {
    let msg = @mpython.format_runtime_error(err)
    }
    }

    MpythonError

    pub suberror MpythonError {
    MpythonError(RuntimeError)
    }

    Wrap RuntimeError as an error type for use with raise

    BcBuilder

    pub(all) struct BcBuilder {
    code : BcCode
    }

    BcBuilder::add_const

    fn BcBuilder::add_const(self : BcBuilder, value : Value) -> Int

    BcBuilder::add_genexp

    fn BcBuilder::add_genexp(self : BcBuilder, spec : GenExpSpec) -> Int

    BcBuilder::add_pattern

    fn BcBuilder::add_pattern(self : BcBuilder, pattern : Pattern) -> Int

    BcBuilder::emit

    fn BcBuilder::emit(self : BcBuilder, op : BcOp, span : Span?) -> Int

    BcBuilder::intern_name

    fn BcBuilder::intern_name(self : BcBuilder, name : String) -> Int

    BcBuilder::new

    fn BcBuilder::new(name : String, filename : String) -> BcBuilder

    BcCode

    pub(all) struct BcCode {
    name : String
    filename : String
    consts : Array[Value]
    names : Array[String]
    patterns : Array[Pattern]
    genexps : Array[GenExpSpec]
    ops : Array[BcOp]
    spans : Array[Span?]
    }

    BcCode::empty

    fn BcCode::empty(name : String, filename : String) -> BcCode

    BcOp

    pub(all) enum BcOp {
    Nop
    PopTop
    DupTop
    RotTwo
    RotThree
    LoadConst(Int)
    LoadName(Int)
    StoreName(Int)
    DeleteName(Int)
    LoadAttr(Int)
    StoreAttr(Int)
    DeleteAttr(Int)
    LoadSubscr
    StoreSubscr
    DeleteSubscr
    BuildTuple(Int)
    BuildList(Int)
    BuildMap(Int)
    BuildSet(Int)
    BuildSlice
    ListAppend
    ListExtend
    SetAdd
    SetUpdate
    DictSetItem
    DictUpdate
    KwListExtendFromDict
    ListToTuple
    EvalFString
    UnpackSequence(Int)
    UnpackEx(Int, Int)
    GetIter
    ForIter(Int)
    Unary(UnaryOp)
    Binary(BinaryOp)
    Compare(CompareOp)
    Jump(Int)
    JumpIfFalse(Int)
    JumpIfTrue(Int)
    JumpIfFalseOrPop(Int)
    JumpIfTrueOrPop(Int)
    UnwindJump(Int, Int)
    SetupExcept(Int)
    SetupFinally(Int)
    PopExcept
    EnterFinally(Int)
    EndExcept
    EndFinally
    CheckExceptionMatch
    LoadException
    LoadExceptionType
    LoadExceptionTypeName
    LoadExceptionTraceback
    HasPendingException
    ClearPendingException
    EnsureExceptionGroup
    ExceptStarInit
    ExceptStarMatch
    ExceptStarEndHandler
    ExceptStarRaiseRemaining
    Raise
    RaiseFrom
    Reraise
    AssertFail
    AssertFailNone
    Await
    MatchPattern(Int)
    MatchBind
    MatchRestore
    GenExpNew(Int, Int, Int)
    AsyncGenExpNew(Int, Int, Int)
    CallFunction(Int)
    CallFunctionKw(Int)
    CallFunctionVar
    MakeFunction(Int, Int, Int)
    MakeClass(Int, Int, Int)
    MakeTypeAlias(Int)
    StoreAnnotation(Int)
    AugAssignName(Int, BinaryOp)
    AugAssignAttr(Int, BinaryOp)
    AugAssignSubscr(BinaryOp)
    ImportName(Int, Int)
    FromImport(Int, Int, Int)
    CheckInFunctionScope(String)
    CheckInCoroutineScope(String)
    ReturnValue
    }

    BinaryOp

    pub enum BinaryOp {
    Add
    Sub
    Mul
    MatMul
    Div
    FloorDiv
    Mod
    Pow
    ShiftLeft
    ShiftRight
    BitAnd
    BitXor
    BitOr
    } derive(ToJson)

    Binary operators

    BinaryOp::to_json

    fn BinaryOp::to_json(BinaryOp) -> Json

    BoolOp

    pub enum BoolOp {
    And
    Or
    } derive(ToJson)

    Boolean operators

    BoolOp::to_json

    fn BoolOp::to_json(BoolOp) -> Json

    BoundMethodValue

    pub struct BoundMethodValue {
    function : FunctionValue
    self : Value
    } derive(ToJson)

    A bound method (minimal descriptor result)

    BoundMethodValue::to_json

    ClassValue

    pub struct ClassValue {
    name : String
    bases : Array[Value]
    dict : Array[(String, Value)]
    } derive(ToJson)

    A Python class object (minimal)

    dict is the class namespace holding methods and attributes.

    ClassValue::to_json

    fn ClassValue::to_json(ClassValue) -> Json

    CompClause

    pub struct CompClause {
    is_async : Bool
    targets : Array[String]
    target : Target
    iter : Expr
    filters : Array[Expr]
    } derive(ToJson)

    A comprehension clause: for <targets> in <iter> (optionally async for).

    CompClause::to_json

    fn CompClause::to_json(CompClause) -> Json

    CompareOp

    pub enum CompareOp {
    Eq
    NotEq
    Lt
    Lte
    Gt
    Gte
    In
    NotIn
    Is
    IsNot
    } derive(ToJson)

    Comparison operators

    CompareOp::to_json

    fn CompareOp::to_json(CompareOp) -> Json

    Config

    pub struct Config {
    max_recursion : Int
    traceback_limit : Int
    track_spans : Bool
    bytecode_strict : Bool
    allow_filesystem_imports : Bool
    import_paths : Array[String]
    argv : Array[String]
    main_path : String?
    } derive(ToJson)

    Interpreter configuration

    Config::default

    fn Config::default() -> Config

    Config::for_cli

    fn Config::for_cli(import_paths : Array[String], main_path : String?, argv : Array[String]) -> Config

    Config::to_json

    fn Config::to_json(Config) -> Json

    Config::with_bytecode_strict

    fn Config::with_bytecode_strict(self : Config, strict : Bool) -> Config

    ExceptHandler

    pub struct ExceptHandler {
    span : Span
    is_star : Bool
    exc : Expr?
    name : String?
    body : Array[Stmt]
    } derive(ToJson)

    Try/except handler (supports except*)

    ExceptHandler::to_json

    Expr

    pub enum Expr {
    Literal(Literal)
    Name(String)
    Tuple(Array[Expr])
    List(Array[Expr])
    ListComp(elt~ : Expr, clauses~ : Array[CompClause], filters~ : Array[Expr])
    GenExp(elt~ : Expr, clauses~ : Array[CompClause], filters~ : Array[Expr])
    Dict(Array[(Expr, Expr)])
    DictComp(key~ : Expr, value~ : Expr, clauses~ : Array[CompClause], filters~ : Array[Expr])
    Set(Array[Expr])
    SetComp(elt~ : Expr, clauses~ : Array[CompClause], filters~ : Array[Expr])
    Lambda(params~ : Array[String], defaults~ : Array[Expr], body~ : Expr)
    IfExpr(condition~ : Expr, then_expr~ : Expr, else_expr~ : Expr)
    FString(String)
    NamedExpr(name~ : String, value~ : Expr)
    Yield(Expr?)
    YieldFrom(Expr)
    Await(Expr)
    Slice(start~ : Expr?, end~ : Expr?, step~ : Expr?)
    Attribute(value~ : Expr, attr~ : String)
    Subscript(value~ : Expr, index~ : Expr)
    Call(callee~ : Expr, args~ : Array[Expr])
    Keyword(name~ : String, value~ : Expr)
    Starred(value~ : Expr)
    DoubleStarred(value~ : Expr)
    Unary(op~ : UnaryOp, expr~ : Expr)
    Binary(op~ : BinaryOp, left~ : Expr, right~ : Expr)
    BoolOp(op~ : BoolOp, values~ : Array[Expr])
    Compare(left~ : Expr, ops~ : Array[CompareOp], comparators~ : Array[Expr])
    WithSpan(span~ : Span, expr~ : Expr)
    } derive(ToJson)

    Expression nodes

    Expr::to_json

    fn Expr::to_json(Expr) -> Json

    FunctionValue

    pub struct FunctionValue {
    name : String
    params : Array[String]
    defaults : Array[Value]
    body : Array[Stmt]
    is_generator : Bool
    is_async : Bool
    closure : Array[(String, Value)]
    } derive(ToJson)

    Runtime values

    FunctionValue::to_json

    GenExpSpec

    pub(all) struct GenExpSpec {
    elt : Expr
    clauses : Array[CompClause]
    filters : Array[Expr]
    }

    InstanceValue

    pub struct InstanceValue {
    class : ClassValue
    dict : Array[(String, Value)]
    } derive(ToJson)

    A Python instance object (minimal)

    InstanceValue::to_json

    Interpreter

    pub struct Interpreter {
    config : Config
    globals : Array[(String, Value)]
    builtins : Array[(String, Value)]
    io : MockIO
    }

    Stateful interpreter

    Interpreter::eval

    fn Interpreter::eval(self : Interpreter, expr : Expr) -> Result[Value, RuntimeError]

    Interpreter::eval_source

    fn Interpreter::eval_source(self : Interpreter, source : String) -> Result[RunResult, RuntimeError]

    Interpreter::exec

    fn Interpreter::exec(self : Interpreter, program : Module) -> Result[RunResult, RuntimeError]

    Interpreter::exec_source

    fn Interpreter::exec_source(self : Interpreter, source : String) -> Result[RunResult, RuntimeError]

    Interpreter::execute_test_dir

    fn Interpreter::execute_test_dir(self : Interpreter, path : String) -> TestResult raise MpythonError

    Interpreter::get_global

    fn Interpreter::get_global(self : Interpreter, name : String) -> Value?

    Interpreter::new

    Interpreter::new_spec

    fn Interpreter::new_spec() -> Interpreter

    Interpreter::set_global

    fn Interpreter::set_global(self : Interpreter, name : String, value : Value) -> Unit

    Interpreter::set_global_str

    fn Interpreter::set_global_str(self : Interpreter, name : String, value : String) -> Unit

    Interpreter::set_main_module_spec

    fn Interpreter::set_main_module_spec(self : Interpreter, module_name : String, origin : String, parent : String, is_package_main : Bool) -> Unit

    Interpreter::with_builtins

    fn Interpreter::with_builtins(builtins : Array[(String, Value)], config : Config) -> Interpreter

    Interpreter::with_config

    fn Interpreter::with_config(config : Config) -> Interpreter

    Interpreter::with_io

    fn Interpreter::with_io(config : Config, io : MockIO) -> Interpreter

    Literal

    pub enum Literal {
    None
    Bool(Bool)
    Int(
    BigInt
    )
    Float(Double)
    Complex(Double, Double)
    Str(String)
    Bytes(Array[Int])
    } derive(ToJson)

    Literal values that appear in source code

    Literal::to_json

    fn Literal::to_json(Literal) -> Json

    MockIO

    pub struct MockIO {
    stdin : Array[String]
    stdin_pos :
    Ref
    [Int]
    stdout : StringBuilder
    stderr : StringBuilder
    }

    In-memory IO buffers for testing

    MockIO::new

    fn MockIO::new(stdin : Array[String]) -> MockIO

    MockIO::read_line

    fn MockIO::read_line(self : MockIO) -> String?

    MockIO::take_stderr

    fn MockIO::take_stderr(self : MockIO) -> String

    MockIO::take_stdout

    fn MockIO::take_stdout(self : MockIO) -> String

    MockIO::write_stderr

    fn MockIO::write_stderr(self : MockIO, text : String) -> Unit

    MockIO::write_stdout

    fn MockIO::write_stdout(self : MockIO, text : String) -> Unit

    Module

    pub struct Module {
    body : Array[Stmt]
    } derive(ToJson)

    Parsed module

    Module::to_json

    fn Module::to_json(Module) -> Json

    ParseError

    pub(all) struct ParseError {
    kind : ParseErrorKind
    message : String
    span : Span
    } derive(ToJson)

    Parse error details

    ParseError::to_json

    fn ParseError::to_json(ParseError) -> Json

    ParseErrorKind

    pub(all) enum ParseErrorKind {
    Syntax
    Indentation
    UnexpectedToken
    UnexpectedEof
    } derive(ToJson)

    Parse error categories

    ParseErrorKind::to_json

    Pattern

    pub enum Pattern {
    Wildcard
    Literal(Literal)
    Name(String)
    TypeCapture(type_name~ : String, name~ : String)
    Tuple(Array[Pattern])
    } derive(ToJson)

    Match statement patterns (minimal)

    Pattern::to_json

    fn Pattern::to_json(Pattern) -> Json

    RunResult

    pub struct RunResult {
    value : Value
    stdout : String
    stderr : String
    globals : Array[(String, Value)]
    } derive(ToJson)

    Results produced after executing code

    RunResult::to_json

    fn RunResult::to_json(RunResult) -> Json

    RuntimeError

    pub(all) struct RuntimeError {
    kind : RuntimeErrorKind
    message : String
    span : Span?
    traceback : Array[TracebackFrame]
    traceback_envs : Array[TracebackEnv]
    exc_type : String
    exc_args : Array[String]
    exc_value : Value?
    exc_cause : Value?
    exc_context : Value?
    exc_suppress_context : Bool
    } derive(ToJson)

    Runtime error details

    RuntimeError::to_json

    RuntimeErrorKind

    pub(all) enum RuntimeErrorKind {
    Name
    Type
    Attribute
    Index
    Key
    ZeroDivision
    NotImplemented
    Runtime
    } derive(ToJson)

    Runtime error categories

    RuntimeErrorKind::to_json

    Span

    pub(all) struct Span {
    start : Int
    end : Int
    line : Int
    column : Int
    } derive(ToJson)

    Source span for errors and diagnostics

    Span::to_json

    fn Span::to_json(Span) -> Json

    Stmt

    pub enum Stmt {
    ExprStmt(Expr)
    Assign(target~ : Target, value~ : Expr)
    AnnAssign(target~ : Target, annotation~ : Expr, value~ : Expr?)
    AugAssign(target~ : Target, op~ : BinaryOp, value~ : Expr)
    TypeAlias(name~ : String, type_params~ : Array[String], value~ : Expr)
    Return(Expr?)
    Yield(Expr?)
    YieldFrom(Expr)
    If(condition~ : Expr, body~ : Array[Stmt], else_body~ : Array[Stmt])
    Match(subject~ : Expr, cases~ : Array[(Pattern, Expr?, Array[Stmt])])
    While(condition~ : Expr, body~ : Array[Stmt], else_body~ : Array[Stmt])
    For(target~ : Target, iter~ : Expr, body~ : Array[Stmt], else_body~ : Array[Stmt])
    AsyncFor(target~ : Target, iter~ : Expr, body~ : Array[Stmt], else_body~ : Array[Stmt])
    Break
    Continue
    Function(name~ : String, decorators~ : Array[Expr], type_params~ : Array[String], params~ : Array[String], defaults~ : Array[Expr], annotations~ : Array[(String, Expr)], return_annotation~ : Expr?, body~ : Array[Stmt], is_generator~ : Bool, is_async~ : Bool)
    Pass
    Global(Array[String])
    Nonlocal(Array[String])
    Assert(condition~ : Expr, message~ : Expr?)
    Raise(exc~ : Expr?, cause~ : Expr?)
    Del(Array[Target])
    Import(module_name~ : String, alias_name~ : String?)
    FromImport(module_name~ : String, names~ : Array[(String, String?)])
    With(context~ : Expr, target~ : String?, body~ : Array[Stmt])
    AsyncWith(context~ : Expr, target~ : String?, body~ : Array[Stmt])
    Try(body~ : Array[Stmt], handlers~ : Array[ExceptHandler], else_body~ : Array[Stmt], finally_body~ : Array[Stmt])
    Class(name~ : String, decorators~ : Array[Expr], type_params~ : Array[String], bases~ : Array[Expr], keywords~ : Array[(String, Expr)], body~ : Array[Stmt])
    WithSpan(span~ : Span, stmt~ : Stmt)
    } derive(ToJson)

    Statement nodes

    Stmt::to_json

    fn Stmt::to_json(Stmt) -> Json

    Target

    pub enum Target {
    Name(String)
    Tuple(Array[Target])
    List(Array[Target])
    Starred(Target)
    Attribute(value~ : Expr, attr~ : String)
    Subscript(value~ : Expr, index~ : Expr)
    } derive(ToJson)

    Assignment targets

    Target::to_json

    fn Target::to_json(Target) -> Json

    TestResult

    pub struct TestResult {
    passed : Int
    failed : Int
    errors : Array[String]
    } derive(ToJson)

    Test result from running a test directory

    TestResult::to_json

    fn TestResult::to_json(TestResult) -> Json

    TracebackEnv

    pub(all) struct TracebackEnv {
    locals : Array[(String, Value)]
    globals : Array[(String, Value)]
    } derive(ToJson)

    Best-effort environment snapshot for a traceback frame.

    This is captured at raise-time so traceback formatting (e.g. suggestions in Lib/traceback.py) can access locals/globals even after stack unwinding.

    TracebackEnv::to_json

    TracebackFrame

    pub(all) struct TracebackFrame {
    name : String
    filename : String
    line : Int
    column : Int
    end_column : Int
    } derive(ToJson)

    Traceback frame data captured at runtime

    TracebackFrame::to_json

    UnaryOp

    pub enum UnaryOp {
    Pos
    Neg
    Not
    Invert
    } derive(ToJson)

    Unary operators

    UnaryOp::to_json

    fn UnaryOp::to_json(UnaryOp) -> Json

    Value

    pub enum Value {
    None
    Bool(Bool)
    Int(
    BigInt
    )
    Float(Double)
    Complex(Double, Double)
    Str(String)
    Bytes(Array[Int])
    ByteArray(Array[Int])
    MemoryView(Array[Int])
    List(Array[Value])
    Tuple(Array[Value])
    Dict(Array[(Value, Value)])
    Set(Array[Value])
    Function(FunctionValue)
    Class(ClassValue)
    Instance(InstanceValue)
    BoundMethod(BoundMethodValue)
    } derive(ToJson)

    Runtime values available to user code

    Value::to_json

    fn Value::to_json(Value) -> Json

    bc_disassemble

    fn bc_disassemble(code : BcCode) -> String

    bc_exec

    fn bc_exec(code : BcCode, locals : Array[(String, Value)], globals : Array[(String, Value)], builtins : Array[(String, Value)], io : MockIO) -> Result[Value, RuntimeError]

    bc_patch_jump

    fn bc_patch_jump(code : BcCode, pc : Int, target : Int) -> Unit

    compile_module_to_bc

    fn compile_module_to_bc(program : Module, filename : String) -> Result[BcCode, RuntimeError]

    compile_stmts_to_bc

    fn compile_stmts_to_bc(body : Array[Stmt], name : String, filename : String) -> Result[BcCode, RuntimeError]

    compile_stmts_to_bc_with_future

    fn compile_stmts_to_bc_with_future(body : Array[Stmt], name : String, filename : String, future_annotations : Bool) -> Result[BcCode, RuntimeError]

    coroutine_await

    fn coroutine_await(coroutine_value : Value) -> Result[Value, RuntimeError]

    coroutine_is_active

    fn coroutine_is_active() -> Bool

    format_parse_error

    fn format_parse_error(error : ParseError) -> String

    format_runtime_error

    fn format_runtime_error(error : RuntimeError) -> String

    format_runtime_error_with_traceback

    fn format_runtime_error_with_traceback(error : RuntimeError, limit : Int) -> String

    parse

    fn parse(source : String) -> Result[Module, ParseError]

    parse_expr

    fn parse_expr(source : String) -> Result[Expr, ParseError]