Skip to content

InKryption/comath

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

comath: comptime math

The main function this library provides is eval, which is of the form

fn eval(
    comptime expr: []const u8,
    ctx: anytype,
    inputs: anytype,
) !Eval(expr, @TypeOf(ctx), @TypeOf(inputs))

wherein the expr is expected to be source code representing an expression comprised of:

  • Identifiers, whose value should be assigned by a field in inputs of the same name, or be defined by the ctx.
  • Integer, float, or character literals
  • Grouped expressions delimited by parentheses
  • Field accesses (expression.identifier|operator symbols)
  • Index accesses (expression[expressions])
  • Function calls (expression(expressions))
  • Unary operations (operator expression)
  • Binary operations (expression operator expression)

Given an expression that is successfully parsed into an AST at comptime, said AST will then be evaluated. The semantics of all the operations are defined by the ctx parameter, which should be a type that conforms to a concrete interface, which can be described in pseudo-code as:

{
    /// If the context namespace does not have it declared, it will be assumed to be false.
    /// If true, the `inputs` struct parameter of the `eval` function may contain
    /// fields which are not used in the `expr`.
    /// If false, a compile error will be issued should any of the fields in `inputs`
    /// not be referenced in `expr`.
    pub const allow_unused_inputs: bool = ...; // defaults to `false`

    /// Should return `true` for any string of symbols corresponding to a recognized unary operator.
    pub inline fn matchUnOp(comptime str: []const u8) bool {...}

    /// Should return `true` for any string of symbols corresponding to a recognized binary operator.
    pub inline fn matchBinOp(comptime str: []const u8) bool {...}

    /// Returns the order of the binary operators `lhs` and `rhs`, where `matchBinOp(lhs) = true`, and
    /// `matchBinOp(rhs) = true`.
    pub inline fn orderBinOp(comptime lhs: []const u8, comptime rhs: []const u8) ?Order {...}

    /// Determines the value and type of number literals.
    pub fn EvalNumberLiteral(comptime src: []const u8) type {...}
    pub fn evalNumberLiteral(comptime src: []const u8) EvalNumberLiteral(src) {...}

    /// Determines the value and type of identifiers, overriding those which would otherwise be
    /// determined via the `inputs` struct. `EvalIdent` returning `noreturn` causes
    /// `eval` to instead look for the identifier in the `inputs` struct.
    pub fn EvalIdent(comptime ident: []const u8) type {...}
    pub fn evalIdent(ctx: @This(), comptime ident: []const u8) !EvalIdent(ident) {...}

    /// Corresponds to `lhs.accessor`.
    pub fn EvalProperty(comptime Lhs: type, comptime accessor: []const u8) type {...}
    pub fn evalProperty(ctx: @This(), lhs: anytype, comptime accessor: []const u8) !EvalProperty(@TypeOf(lhs), accessor) {...}

    /// Corresponds to `lhs[rhs...]`, where `rhs` is a tuple whose elements are the list of indices used to access `lhs`.
    pub fn EvalIndexAccess(comptime Lhs: type, comptime Rhs: type) type {...}
    pub fn evalIndexAccess(ctx: @This(), lhs: anytype, rhs: anytype) !EvalIndexAccess(@TypeOf(lhs), @TypeOf(rhs)) {...}

    /// Corresponds to `callee(args...)`, where `args` is a tuple whose elements are the arguments to `callee`.
    pub fn EvalFuncCall(comptime Callee: type, comptime Args: type) type {...}
    pub fn evalFuncCall(ctx: @This(), callee: anytype, args: anytype) !EvalFuncCall(@TypeOf(callee), @TypeOf(args)) {...}

    /// Corresponds to `op value`.
    /// In most contexts, it should be sufficient to assume `@hasField(UnOp, op)`.
    pub fn EvalUnOp(comptime op: []const u8, comptime T: type) type {...}
    pub fn evalUnOp(ctx: @This(), comptime op: []const u8, value: anytype) !EvalUnOp(op, @TypeOf(value)) {...}

    /// Corresponds to `lhs op rhs`
    /// In most contexts, it should be sufficient to assume `@hasField(BinOp, op)`.
    pub fn EvalBinOp(comptime Lhs: type, comptime op: []const u8, comptime Rhs: type) type {...}
    pub fn evalBinOp(ctx: @This(), lhs: anytype, comptime op: []const u8, rhs: anytype) !EvalBinOp(@TypeOf(lhs), op, @TypeOf(rhs)) {...}
}

Some built-in contexts are provided in the contexts namespace, both for use and for reference.

Example code:

const std = @import("std");
const comath = @import("comath");

test {
    const ctx = comath.ctx.simple({});
    const value = comath.eval("a * 2", ctx, .{ .a = 4 }) catch |err| switch (err) {};
    try std.testing.expect(value == 8);
}

Notable Implementations

To see an example of the terse notation enabled by comath, see zilliam.