[][src]Crate arithmetic_eval

Simple interpreter for ASTs produced by arithmetic-parser.

How it works

  1. A Block of statements is compiled into an ExecutableModule. Internally, compilation processes the AST of the block and transforms it into a non-recusrive form. An ExecutableModule may require imports (such as NativeFns or constant Values), which can be taken from a VariableMap (e.g., an Environment).
  2. ExecutableModule can then be executed, for the return value and/or for the changes at the top-level variable scope. There are two major variables influencing the execution outcome. An arithmetic is used to define arithmetic ops (+, unary and binary -, *, /, ^) and comparisons (==, !=, >, <, >=, <=). Imports may be redefined at this stage as well.

Type system

Values have 4 major types:

  • Numbers corresponding to literals in the parsed Block
  • Boolean values
  • Functions, which are further subdivided into native functions (defined in the Rust code) and interpreted ones (defined within a module)
  • Tuples / arrays.

Besides these types, there is an auxiliary one: OpaqueRef, which represents a reference-counted native value, which can be returned from native functions or provided to them as an arg, but is otherwise opaque from the point of view of the interpreted code (cf. anyref in WASM).

Semantics

  • All variables are immutable. Re-declaring a var shadows the previous declaration.

  • Functions are first-class (in fact, a function is just a variant of the Value enum).

  • Functions can capture variables (including other functions). All captures are by value.

  • Arithmetic operations are defined on numbers and tuples. Ops or numbers are defined via the Arithmetic. With tuples, operations are performed per-element. Binary operations require tuples of the same size, or a tuple and a primitive value. As an example, (1, 2) + 3 and (2, 3) / (4, 5) are valid, but (1, 2) * (3, 4, 5) isn't.

  • Methods are considered syntactic sugar for functions, with the method receiver considered the first function argument. For example, (1, 2).map(sin) is equivalent to map((1, 2), sin).

  • Equality comparisons (==, !=) are defined on all types of values.

    • For bool values, the comparisons work as expected.
    • For functions, the equality is determined by the pointer (2 functions are equal iff they alias each other).
    • OpaqueRefs either use the PartialEq impl of the underlying type or the pointer equality, depending on how the reference was created; see OpaqueRef docs for more details.
    • Equality for numbers is determined by the Arithmetic.
    • Tuples are equal if they contain the same number of elements and elements are pairwise equal.
    • Different types of values are always non-equal.
  • Order comparisons (>, <, >=, <=) are defined for primitive values only and use OrdArithmetic.

  • No type checks are performed before evaluation.

  • Type annotations are completely ignored. This means that the interpreter may execute code that is incorrect with annotations (e.g., assignment of a tuple to a variable which is annotated to have a numeric type).

Crate features

  • std. Enables support of types from std, such as the Error trait, and propagates to dependencies. Importantly, std is necessary for floating-point arithmetics.
  • complex. Implements Number for floating-point complex numbers from the num-complex crate (i.e., Complex32 and Complex64). Enables complex number parsing in arithmetic-parser.
  • bigint. Implements Number and a couple of other helpers for big integers from the num-bigint crate (i.e., BigInt and BigUint). Enables big integer parsing in arithmetic-parser.

Examples

use arithmetic_parser::grammars::{F32Grammar, Parse, Untyped};
use arithmetic_eval::{
    fns, Assertions, Comparisons, Environment, Prelude, Value, VariableMap,
};

let program = r#"
    // The interpreter supports all parser features, including
    // function definitions, tuples and blocks.
    order = |x, y| (min(x, y), max(x, y));
    assert_eq(order(0.5, -1), (-1, 0.5));
    (_, M) = order(3^2, { x = 3; x + 5 });
    M
"#;
let program = Untyped::<F32Grammar>::parse_statements(program)?;

let mut env = Environment::new();
// Add some native functions to the environment.
env.extend(Prelude.iter());
env.extend(Assertions.iter());
env.extend(Comparisons.iter());

// To execute statements, we first compile them into a module.
let module = env.compile_module("test", &program)?;
// Then, the module can be run.
assert_eq!(module.run()?, Value::Number(9.0));

More complex examples are available in the examples directory of the crate.

Re-exports

pub use self::error::Error;
pub use self::error::ErrorKind;
pub use self::error::EvalResult;

Modules

arith

Arithmetic trait and its implementations.

error

Evaluation errors.

fns

Standard functions for the interpreter, and the tools to define new native functions.

Macros

wrap_fn

An alternative for wrap function which works for arguments / return results with non-'static lifetime.

wrap_fn_with_context

Analogue of wrap_fn macro that injects the CallContext as the first argument. This can be used to call functions within the implementation.

Structs

Assertions

Container for assertion functions: assert and assert_eq.

CallContext

Context for native function calls.

Comparisons

Container with the comparison functions: cmp, min and max.

Environment

Environment containing named Values.

ExecutableModule

Executable module together with its imports.

ExecutableModuleBuilder

Builder for an ExecutableModule.

IndexedId

Indexed module ID containing a prefix part (e.g., snippet).

InterpretedFn

Function defined within the interpreter.

ModuleImports

Imports of an ExecutableModule.

OpaqueRef

Opaque reference to a native value.

Prelude

Commonly used constants and functions from the fns module.

WildcardId

Module identifier that has a single possible value, which is displayed as *.

WithArithmetic

Container for an ExecutableModule together with an OrdArithmetic.

Enums

Function

Function definition. Functions can be either native (defined in the Rust code) or defined in the interpreter.

Value

Values produced by expressions during their interpretation.

ValueType

Possible high-level types of Values.

Traits

CompilerExt

Compiler extensions defined for some AST nodes, most notably, Block.

ModuleId

Identifier of an ExecutableModule. This is usually a "small" type, such as an integer or a string.

NativeFn

Function on zero or more Values.

Number

Marker trait for possible literals.

VariableMap

Encapsulates read access to named variables.

Type Definitions

SpannedValue

Value together with a span that has produced it.