marianoguerra/pure-py/value does not have a README file

    Env

    An environment: ρ. Immutable, because a closure captures one by value.

    DefClosure

    pub(all) struct DefClosure {
    env :
    HashMap
    [String, Value]
    region : Array[
    Stmt
    ]
    index : Int
    in_module : String
    } derive(
    Debug
    )

    LamClosure

    pub(all) struct LamClosure {
    env :
    HashMap
    [String, Value]
    params : Array[String]
    body :
    Expr

    in_module : String
    } derive(
    Debug
    )

    in_module is not part of LamΓ: the spec's closures carry an environment and a body, and nothing about where they were written. It is here because a position without a file is a lie the moment a program has two modules -- a helper.py function that aborts on its line 4 would be read as line 4 of __main__. The evaluator's import stack cannot supply it, because during a call it names the module that IMPORTED the callee, not the one that defined it. Only the closure knows.

    MatchResult

    pub(all) enum MatchResult {
    Match(
    HashMap
    [String, Value])
    NoMatch
    MatchStuck(String)
    } derive(
    Debug
    )

    Whether a pattern matched, and what it bound.

    MatchResult::then

    fn MatchResult::then(self : MatchResult, other : MatchResult) -> MatchResult

    m ⊗ m': a match result composes by union of bindings, and no-match is absorbing.

    Outcome

    pub(all) enum Outcome {
    Val(Value)
    Aborts(Termination)
    Stuck(String)
    } derive(
    Debug
    )

    The outcome of evaluating an expression: a value, an abort, or an operation the semantics leaves undefined.

    Primitive

    pub(all) enum Primitive {
    Print
    Len
    Range
    Exit
    Sqrt
    Exp
    Log
    Sin
    Cos
    Tan
    MathFloor
    MathCeil
    Opaque(String)
    Foreign(String)
    } derive(Eq,
    Debug
    )

    The predefined functions of Figure 2.7.

    Opaque is for the three members that are values only so that importing them binds something: typing.Any, typing.Callable and dataclasses.dataclass are never called.

    StmtResult

    pub(all) enum StmtResult {
    Assigns(
    HashMap
    [String, Value])
    Returns(Value)
    ResultAborts(Termination)
    ResultStuck(String)
    } derive(
    Debug
    )

    The result of evaluating a statement.

    StmtResult::outcome

    fn StmtResult::outcome(self : StmtResult) -> Outcome

    outcome(r): a statement's result read as the outcome of calling it.

    StmtResult::then

    fn StmtResult::then(self : StmtResult, other : StmtResult) -> StmtResult

    Sequential composition of results: assigns ρ ⊗ r.

    Termination

    pub(all) enum Termination {
    TypeError
    IndexError
    KeyError
    ZeroDivisionError
    AttributeError
    AssertionError(String?)
    SystemExit(
    BigInt
    )
    } derive(Eq,
    Debug
    )

    How a run ends. Every kind but SystemExit is named after the exception the same program raises under Python.

    Termination::name

    fn Termination::name(self : Termination) -> String

    The name Python prints for a termination, and the message after it.

    Termination::to_text

    fn Termination::to_text(self : Termination) -> String

    AssertionError: message when there is one, the bare name otherwise -- the last line of the traceback Python prints.

    Value

    Runtime values, environments and evaluation results: Figure 4.1.

    Two things about this type are the whole shape of the language:

    • Nothing is mutable. A list, a tuple, a dictionary and an object are built once and never changed, so an environment can be shared by every closure that captures it and nothing has to be copied defensively.
    • Stuck is a result, not a crash. The semantics leaves some operations undefined, and Chapter 1 allows an implementation either to abort or to produce Python's answer. This one aborts, with the operation named, so that the conformance suite's dynamically excluded bucket is CHECKED rather than merely tolerated.

    Value::kind_name

    fn Value::kind_name(self : Value) -> String

    What kind of value this is, for a message naming an undefined operation.

    Value::repr

    fn Value::repr(self : Value) -> String?

    Value::str

    fn Value::str(self : Value) -> String?

    Python's str and repr of a value.

    The two differ in exactly one place -- a string is itself under str and quoted under repr -- and print uses str while a container prints its elements with repr. Everything else is the same function.

    A closure, a module, a class or a primitive has no printable form here. Python prints one with an address in it, which no implementation can reproduce, so printing one is an undefined operation and is reported as such rather than invented.

    big_to_double

    fn big_to_double(n :
    BigInt
    ) -> Double

    A BigInt as a double, correctly rounded -- which is what Python's float(n) gives. MoonBit's BigInt has no conversion, so it goes through the decimal text, which parse_double rounds correctly.

    binop

    The operators: ⊙̂(v, v') and ⊖̂(v).

    Arithmetic is Python's, with three things this port has to supply that MoonBit does not:

    • // and % floor. MoonBit's / and % on BigInt and Int64 truncate toward zero; Python's floor toward negative infinity, so the remainder takes the sign of the DIVISOR: -7 // 2 is -4 and 7 % -2 is -1.
    • bool is not a number. True + 1, True < 2 and -False are UNDEFINED, even though Python accepts all three. Undefined and not TypeError: a termination kind is "named after the exception the same program raises under Python in the same circumstances" (operational-semantics.tex), and Python raises nothing for True + 1 -- it says 2. This is the same rule that makes True == 1 undefined, and the conformance suite has a test for that.
    • An integer and a float compare exactly, not by converting the integer, so a large integer is not made equal to the double nearest it.

    + and * are tried over sequences first, because they are not arithmetic there. The spec's arithmetic table is a \todo reading "Arithmetic, as in Python ... aborts TypeError where an operand is not a number", and the two halves disagree about "a" + "b": the sentence was written for 1 + "a", which the conformance suite tests and which IS a TypeError in Python. Concatenation is not, and CPython is the oracle for what run prints, so "as in Python" is the half that decides.

    compare

    ==, !=, in, not in and the four orderings.

    contains

    fn contains(haystack : Value, needle : Value) -> Bool?

    Whether needle is in haystack: an element of a list or tuple, a key of a dictionary, a substring of a string.

    contains_elems

    fn contains_elems(xs : Array[Value], needle : Value) -> Bool?

    elems

    fn elems(v : Value) -> Array[Value]?

    The elements of a list, a tuple or a string. A string's elements are its CODE POINTS, which is what Python iterates.

    empty_env

    entries

    fn entries(base : Array[(String, Value)], more : Array[(String, Value)]) -> Array[(String, Value)]

    entries(δ, δ'): the left entries extended by the right ones in order.

    env_of

    fn env_of(entries : Array[(String, Value)]) ->
    HashMap
    [String, Value]

    fn eq(a : Value, b : Value) -> Bool?

    The functions on values of Annex A.3: equality, membership, elements, iteration, subscripting and the dictionary builders.

    Equality is PARTIAL, and the order in which elements are compared is therefore observable: eq short-circuits at the first pair that decides the answer, so an undefined comparison later in a sequence does not make the whole comparison undefined. [1, "a"] == [1, 3] is stuck and [1, "a"] == [2, 3] is False, and the difference is deliberate.

    Every function that can be undefined returns a Bool? or an Outcome; None and Stuck mean "no rule", not "false".

    eq_elems

    fn eq_elems(xs : Array[Value], ys : Array[Value]) -> Bool?

    Elementwise equality, stopping at the first pair that decides it.

    exact_integer

    fn exact_integer(d : Double) ->
    BigInt
    ?

    The integer a double exactly equals, or None when it is not one.

    Read off the bit pattern rather than through decimal text, so it is exact at every magnitude: a double is mantissa * 2^exponent, and when it is integral the shift below loses nothing.

    extend_env

    ρ ⊲ ρ': extension, which differs from override only on modules -- a loaded module is preferred to a stub of the same module, and two loadings of one module merge member by member.

    extend_value

    fn extend_value(a : Value, b : Value) -> Value

    float_mod

    fn float_mod(x : Double, y : Double) -> Double

    Python's % on floats: the result takes the sign of the divisor.

    floor_div

    Python's // on integers: floor, not truncation.

    floor_mod

    Python's % on integers: the remainder takes the sign of the divisor.

    getitem

    fn getitem(v : Value, key : Value) -> Outcome

    Subscripting: a dictionary by a string key, a sequence by an integer index counting from the end when negative.

    host_fn

    fn host_fn(name : String) -> Value

    A function the host supplies, as a value the guest can call.

    The host puts one of these in a module (@eval.HostModule) and answers calls to it by name.

    iter

    fn iter(v : Value) -> Array[Value]?

    What a generator draws from a value: the KEYS of a dictionary, and otherwise its elements.

    order

    fn order(a : Value, b : Value) -> Int?

    Three-way comparison, None where the two values have no order.

    Numbers with numbers, strings by code point, lists with lists and tuples with tuples lexicographically. A nan anywhere makes the pair unordered, which is how Python's comparisons all come out False.

    override_env

    ρ ⊗ ρ': the right-hand side wins outright.

    unop

    not, unary + and unary -.

    update

    fn update(entries : Array[(String, Value)], key : String, v : Value) -> Array[(String, Value)]

    update(δ, w, v): the entries with w bound to v, in place if it was already there and appended otherwise.