[−][src]Crate arithmetic_eval
Simple interpreter for ASTs produced by arithmetic-parser
.
How it works
- A
Block
of statements is compiled into anExecutableModule
. Internally, compilation processes the AST of the block and transforms it into a non-recusrive form. AnExecutableModule
may require imports (such asNativeFn
s or constantValue
s), which can be taken from aVariableMap
(e.g., anEnvironment
). 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
Value
s 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 tomap((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).
OpaqueRef
s either use thePartialEq
impl of the underlying type or the pointer equality, depending on how the reference was created; seeOpaqueRef
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 useOrdArithmetic
. -
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 fromstd
, such as theError
trait, and propagates to dependencies. Importantly,std
is necessary for floating-point arithmetics.complex
. ImplementsNumber
for floating-point complex numbers from thenum-complex
crate (i.e.,Complex32
andComplex64
). Enables complex number parsing inarithmetic-parser
.bigint
. ImplementsNumber
and a couple of other helpers for big integers from thenum-bigint
crate (i.e.,BigInt
andBigUint
). Enables big integer parsing inarithmetic-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 |
|
error | Evaluation errors. |
fns | Standard functions for the interpreter, and the tools to define new native functions. |
Macros
wrap_fn | An alternative for |
wrap_fn_with_context | Analogue of |
Structs
Assertions | Container for assertion functions: |
CallContext | Context for native function calls. |
Comparisons | Container with the comparison functions: |
Environment | Environment containing named |
ExecutableModule | Executable module together with its imports. |
ExecutableModuleBuilder | Builder for an |
IndexedId | Indexed module ID containing a prefix part (e.g., |
InterpretedFn | Function defined within the interpreter. |
ModuleImports | Imports of an |
OpaqueRef | Opaque reference to a native value. |
Prelude | Commonly used constants and functions from the |
WildcardId | Module identifier that has a single possible value, which is displayed as |
WithArithmetic | Container for an |
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 |
Traits
CompilerExt | Compiler extensions defined for some AST nodes, most notably, |
ModuleId | Identifier of an |
NativeFn | Function on zero or more |
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. |