Skip to documentation
SLOP

tiny.pluck.pexpr

Reference tiny.pluck pexpr

Defined in tiny.pluck.

API (48)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.pluckpexpr
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callerstiny.choirbackends.artifact.Symboldeinitpexpr.DefinitionsclearUserDefinitions
Static calls · unresolved targets: 1 · external targets: 4.
Called byCallstest sourcelib.pluck.src.ordertest: definition order min-fill choos...test sourcelib.pluck.src.ordertest: definition order topological re...private sourcelib.pluck.src.pexpr.DefinitionsdefineWithOptionspexpr.Definitionsdefine
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.pluck.src.pexpr.DefinitionsdefineWithOptionspexpr.DefinitionsdefineStdlib
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.pluck.src.pexpr.DefinitionsdefineWithOptionspexpr.DefinitionsdefineStdlibWithDoc
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.pluck.src.pexpr.DefinitionsdefineWithOptionspexpr.DefinitionsdefineWithDoc
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.pluck.src.evaluatortest: IntDist enumeration - determini...test sourcelib.pluck.src.evaluatortest: IntDist enumeration - weighted ...test sourcelib.pluck.src.evaluatortest: bindMonad frees input worlds sl...test sourcelib.pluck.src.evaluatortest: factor WeightDD composes multip...test sourcelib.pluck.src.evaluatortest: factor WeightDD respects branch...+40 morepexpr.Definitionsdeinit
Static calls · unresolved targets: 1 · external targets: 4.
Called byCallsNo direct callstest sourcelib.pluck.src.evaluatortest: IntDist enumeration - determini...test sourcelib.pluck.src.evaluatortest: IntDist enumeration - weighted ...test sourcelib.pluck.src.evaluatortest: bindMonad frees input worlds sl...test sourcelib.pluck.src.evaluatortest: factor WeightDD composes multip...test sourcelib.pluck.src.evaluatortest: factor WeightDD respects branch...+64 morepexpr.Definitionsinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callspexpr.ParserparseExprpexpr.DefinitionsisDefined
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callspexpr.PExprformatpexpr.Headformat
Static calls · unresolved targets: 2 · external targets: 1.
Called byCallsNo direct callstest sourcelib.pluck.src.evaluatortest: ThunkId stability across lookupstest sourcelib.pluck.src.evaluatortest: ThunkRegistry basic operationstest sourcelib.pluck.src.evaluatortest: ThunkRegistry refineVariable re...test sourcelib.pluck.src.identitytest: ContentHash from expressiontest sourcelib.pluck.src.identitytest: SourceThunkId stability across ...+10 morepexpr.PExprdeinit
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsprivate sourcelib.pluck.src.pexpr.PExprformatApplicationChainpexpr.Headformatprivate sourcelib.pluck.src.pexpr.PExprformatApplicationChainprivate sourcelib.pluck.src.pexpr.PExprformatLetApplicationpexpr.PExprmaybeConstpexpr.PExprformat
Static calls · unresolved targets: 5 · external targets: 1.
Called byCallstest sourcelib.pluck.src.evaluatortest: IntDist enumeration - determini...test sourcelib.pluck.src.evaluatortest: IntDist enumeration - weighted ...test sourcelib.pluck.src.evaluatortest: ThunkId stability across lookupstest sourcelib.pluck.src.evaluatortest: ThunkRegistry basic operationstest sourcelib.pluck.src.evaluatortest: ThunkRegistry refineVariable re...+33 morepexpr.PExprinitWithArgspexpr.PExprinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.pluck.src.evaluatortest: IntDist enumeration - determini...test sourcelib.pluck.src.evaluatortest: IntDist enumeration - weighted ...test sourcelib.pluck.src.evaluatortest: flip produces path-condition-in...test sourcelib.pluck.src.evaluatortest: get args extracts arguments fro...test sourcelib.pluck.src.evaluatortest: get args extracts empty argumen...+38 morepexpr.PExprinitWithArgs
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callspexpr.PExprformatpexpr.PExprmaybeConst
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callspexprparseExprtoplevel.formsprocessDefinetoplevel.formsprocessDefineFunctiontoplevel.formsprocessExprQuerytoplevel.formsprocessQuerypexpr.Parserdeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callspexprparseExprtoplevel.formsprocessDefinetoplevel.formsprocessDefineFunctiontoplevel.formsprocessExprQuerytoplevel.formsprocessQuerypexpr.Parserinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.pluck.src.pexpr.ParserparseApplicationprivate sourcelib.pluck.src.pexpr.ParserparseCaseprivate sourcelib.pluck.src.pexpr.ParserparseConstructorprivate sourcelib.pluck.src.pexpr.ParserparseDiscreteprivate sourcelib.pluck.src.pexpr.ParserparseIf+11 morepexpr.DefinitionsisDefinedpexpr.PExprinitprivate sourcelib.pluck.src.pexpr.Parseradvanceprivate sourcelib.pluck.src.pexpr.ParserconstToExprprivate sourcelib.pluck.src.pexpr.ParserdupeStr+7 morepexpr.ParserparseExpr
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.pluck.src.pexpr.ParserparseApplicationprivate sourcelib.pluck.src.pexpr.ParserparseCaseprivate sourcelib.pluck.src.pexpr.ParserparseCompoundprivate sourcelib.pluck.src.pexpr.ParserparseConstructorprivate sourcelib.pluck.src.pexpr.ParserparseDiscrete+6 morepexpr.Parserpeek
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.pluck.src.pexpr.ParserparseCaseprivate sourcelib.pluck.src.pexpr.ParserparseLambdaprivate sourcelib.pluck.src.pexpr.ParserparseLettoplevel.formsprocessDefineFunctionpexpr.ParserpopEnv
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsprivate sourcelib.pluck.src.pexpr.ParserparseCaseprivate sourcelib.pluck.src.pexpr.ParserparseLambdaprivate sourcelib.pluck.src.pexpr.ParserparseLettoplevel.formsprocessDefineFunctionpexpr.ParserpushEnv
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsNo direct callspexpr.ParserparseExprpexpr.TypeRegistryconstructorArity
Static calls · unresolved targets: 1 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.pluck.src.pexpr.ParserparseCompoundpexpr.ParserparseExprpexpr.TypeRegistryhasConstructor
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callstest sourcelib.pluck.src.evaluatortest: factor WeightDD composes multip...test sourcelib.pluck.src.evaluatortest: factor WeightDD respects branch...test sourcelib.pluck.src.evaluatortest: factor defers WeightDD refineme...test sourcelib.pluck.src.evaluatortest: factor prunes zero-weight branchtest sourcelib.pluck.src.evaluatortest: factor supports guarded weights+28 morepexpr.TypeRegistryinitWithDefaults
Static calls · unresolved targets: 2 · external targets: 0.
Called byCallsNo direct callspexprparseExprtest sourcelib.pluck.src.pexprtest: tokenize adjacent syntaxtest sourcelib.pluck.src.pexprtest: tokenize basictest sourcelib.pluck.src.pexprtest: tokenize with commenttoplevel.formsprocessFormSexpr+2 morepexprfreeTokens
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.pluck.src.evaluatortest: factor WeightDD composes multip...test sourcelib.pluck.src.evaluatortest: factor WeightDD respects branch...test sourcelib.pluck.src.evaluatortest: factor defers WeightDD refineme...test sourcelib.pluck.src.evaluatortest: factor prunes zero-weight branchtest sourcelib.pluck.src.evaluatortest: factor supports guarded weights+28 morepexpr.Parserdeinitpexpr.Parserinitpexpr.ParserparseExprpexprfreeTokenspexprtokenizepexprparseExpr
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallspexprparseExprtest sourcelib.pluck.src.pexprtest: tokenize adjacent syntaxtest sourcelib.pluck.src.pexprtest: tokenize basictest sourcelib.pluck.src.pexprtest: tokenize with commenttoplevel.formsprocessFormSexpr+2 moreprivate sourcelib.pluck.src.pexprnextTokenSpanpexprtokenize
Static calls · unresolved targets: 3 · external targets: 0.

Source: lib/pluck/src/pexpr.zig

zig
const std = @import("std");const log = @import("logger.zig");const builtin = @import("builtin");const Allocator = std.mem.Allocator;pub const Symbol = []const u8;pub const NativeValue = union(enum) {    int: i64,    float: f64,    symbol: Symbol,    bool_val: bool,    pub fn format(        self: NativeValue,        comptime fmt: []const u8,        options: std.fmt.Options,        writer: anytype,    ) !void {        _ = fmt;        _ = options;        switch (self) {            .int => |i| try writer.print("@{d}", .{i}),            .float => |f| try writer.print("{d}", .{f}),            .symbol => |s| try writer.print("'{s}", .{s}),            .bool_val => |b| try writer.print("{}", .{b}),        }    }};pub const CaseOfGuard = struct {    constructor: Symbol,    args: []const Symbol,    pub fn format(        self: CaseOfGuard,        comptime fmt: []const u8,        options: std.fmt.Options,        writer: anytype,    ) !void {        _ = fmt;        _ = options;        try writer.print("{s}", .{self.constructor});        for (self.args) |arg| {            try writer.print(" {s}", .{arg});        }    }};pub const ConstructorDef = struct {    name: Symbol,    args: []const Symbol,};pub const Head = union(enum) {    app: void,    abs: struct { var_name: Symbol },    var_ref: struct { name: Symbol },    defined: struct { name: Symbol },    const_native: NativeValue,    case_of: struct { branches: []const CaseOfGuard },    construct: struct { constructor: Symbol },    type_def: struct {        type_name: Symbol,        constructors: []const ConstructorDef,    },    y_combinator: void,    flip: void,    factor: void,    native_eq: void,    get_args: void,    get_constructor: void,    pbool: void,    get_config: void,    mk_int: void,    mk_int_weighted: void,    int_dist_eq: void,    print_op: void,    f_div: void,    f_mul: void,    f_add: void,    f_sub: void,    error_op: void,    pub fn format(        self: Head,        comptime fmt: []const u8,        options: std.fmt.Options,        writer: anytype,    ) !void {        switch (self) {            .app => try writer.writeAll("App"),            .abs => |a| try writer.print("λ{s}", .{a.var_name}),            .var_ref => |v| try writer.print("${s}", .{v.name}),            .defined => |d| try writer.print("{s}", .{d.name}),            .const_native => |c| try c.format(fmt, options, writer),            .case_of => try writer.writeAll("caseof"),            .construct => |c| try writer.print("{s}", .{c.constructor}),            .type_def => |t| try writer.print("type {s}", .{t.type_name}),            .y_combinator => try writer.writeAll("Y"),            .flip => try writer.writeAll("flip"),            .factor => try writer.writeAll("factor"),            .native_eq => try writer.writeAll("native_eq"),            .get_args => try writer.writeAll("get_args"),            .get_constructor => try writer.writeAll("get_constructor"),            .pbool => try writer.writeAll("pbool"),            .get_config => try writer.writeAll("get_config"),            .mk_int => try writer.writeAll("mk_int"),            .mk_int_weighted => try writer.writeAll("mk_int_weighted"),            .int_dist_eq => try writer.writeAll("int_dist_eq"),            .print_op => try writer.writeAll("print"),            .f_div => try writer.writeAll("/."),            .f_mul => try writer.writeAll("*."),            .f_add => try writer.writeAll("+."),            .f_sub => try writer.writeAll("-."),            .error_op => try writer.writeAll("error"),        }    }    pub fn primArity(self: Head) ?usize {        return switch (self) {            .y_combinator => 1,            .flip => 1,            .factor => 1,            .native_eq => 2,            .get_args => 1,            .get_constructor => 1,            .pbool => 1,            .get_config => 0,            .mk_int => 2,            .mk_int_weighted => 2,            .int_dist_eq => 2,            .print_op => 1,            .f_div => 2,            .f_mul => 2,            .f_add => 2,            .f_sub => 2,            .error_op => 1,            else => null,        };    }};pub const PExpr = struct {    head: Head,    args: []const *PExpr,    const Self = @This();    pub fn init(allocator: Allocator, head: Head) !*Self {        return initWithArgs(allocator, head, &[_]*Self{});    }    pub fn initWithArgs(allocator: Allocator, head: Head, args: []const *Self) !*Self {        const self = try allocator.create(Self);        const args_copy = try allocator.alloc(*Self, args.len);        @memcpy(args_copy, args);        self.* = Self{            .head = head,            .args = args_copy,        };        return self;    }    pub fn deinit(self: *Self, allocator: Allocator) void {        for (self.args) |arg| {            arg.deinit(allocator);        }        switch (self.head) {            .case_of => |c| {                for (c.branches) |b| {                    allocator.free(b.args);                }                allocator.free(c.branches);            },            else => {},        }        allocator.free(self.args);        allocator.destroy(self);    }    pub fn format(        self: *const Self,        comptime fmt: []const u8,        options: std.fmt.Options,        writer: anytype,    ) !void {        switch (self.head) {            .var_ref => |v| {                try writer.print("${s}", .{v.name});                return;            },            .defined => |d| {                try writer.print("{s}", .{d.name});                return;            },            .const_native => |c| {                try c.format(fmt, options, writer);                return;            },            .abs => |a| {                try writer.print("(λ{s}", .{a.var_name});                var body = self.args[0];                while (body.head == .abs) {                    const inner_abs = body.head.abs;                    try writer.print(" {s}", .{inner_abs.var_name});                    body = body.args[0];                }                try writer.writeAll(" -> ");                try body.format(fmt, options, writer);                try writer.writeAll(")");                return;            },            .app => {                if (self.args[0].head == .abs) {                    try self.formatLetApplication(fmt, options, writer);                    return;                }                try writer.writeAll("(");                try self.formatApplicationChain(fmt, options, writer);                try writer.writeAll(")");                return;            },            .case_of => |c| {                if (c.branches.len == 2 and                    std.mem.eql(u8, c.branches[0].constructor, "True") and                    std.mem.eql(u8, c.branches[1].constructor, "False"))                {                    try writer.writeAll("(if ");                    try self.args[0].format(fmt, options, writer);                    try writer.writeAll(" ");                    try self.args[1].format(fmt, options, writer);                    try writer.writeAll(" ");                    try self.args[2].format(fmt, options, writer);                    try writer.writeAll(")");                    return;                }                try writer.writeAll("(case ");                try self.args[0].format(fmt, options, writer);                try writer.writeAll(" of ");                for (c.branches, 0..) |branch, idx| {                    try branch.format(fmt, options, writer);                    try writer.writeAll(" => ");                    try self.args[idx + 1].format(fmt, options, writer);                    if (idx < c.branches.len - 1) try writer.writeAll(" | ");                }                try writer.writeAll(")");                return;            },            .construct => |c| {                if (self.maybeConst()) |n| {                    try writer.print("{d}", .{n});                    return;                }                try writer.print("({s}", .{c.constructor});                for (self.args) |arg| {                    try writer.writeAll(" ");                    try arg.format(fmt, options, writer);                }                try writer.writeAll(")");                return;            },            else => {},        }        try writer.writeAll("(");        try self.head.format(fmt, options, writer);        for (self.args) |arg| {            try writer.writeAll(" ");            try arg.format(fmt, options, writer);        }        try writer.writeAll(")");    }    fn formatLetApplication(        self: *const Self,        comptime fmt: []const u8,        options: std.fmt.Options,        writer: anytype,    ) anyerror!void {        try writer.writeAll("(let [");        var current = self;        var first = true;        while (current.head == .app and current.args[0].head == .abs) {            if (!first) try writer.writeAll(" ");            const abs_head = current.args[0].head.abs;            try writer.print("{s} ", .{abs_head.var_name});            try current.args[1].format(fmt, options, writer);            first = false;            current = current.args[0].args[0];        }        try writer.writeAll("] ");        try current.format(fmt, options, writer);        try writer.writeAll(")");    }    fn formatApplicationChain(        self: *const Self,        comptime fmt: []const u8,        options: std.fmt.Options,        writer: anytype,    ) anyerror!void {        if (self.head != .app) {            try self.format(fmt, options, writer);            return;        }        try self.args[0].formatApplicationChain(fmt, options, writer);        try writer.writeAll(" ");        try self.args[1].format(fmt, options, writer);    }    pub fn maybeConst(self: *const Self) ?i64 {        switch (self.head) {            .construct => |c| {                if (std.mem.eql(u8, c.constructor, "O")) {                    return 0;                } else if (std.mem.eql(u8, c.constructor, "S")) {                    if (self.args.len == 1) {                        if (self.args[0].maybeConst()) |inner| {                            return inner + 1;                        }                    }                }                return null;            },            else => return null,        }    }};pub const TypeRegistry = struct {    allocator: Allocator,    type_of_constructor: std.StringHashMap(Symbol),    constructors_of_type: std.StringHashMap([]const Symbol),    args_of_constructor: std.StringHashMap([]const Symbol),    const Self = @This();    pub fn init(allocator: Allocator) Self {        return Self{            .allocator = allocator,            .type_of_constructor = std.StringHashMap(Symbol).init(allocator),            .constructors_of_type = std.StringHashMap([]const Symbol).init(allocator),            .args_of_constructor = std.StringHashMap([]const Symbol).init(allocator),        };    }    pub fn deinit(self: *Self) void {        var iter = self.constructors_of_type.iterator();        while (iter.next()) |entry| {            self.allocator.free(entry.value_ptr.*);        }        var iter2 = self.args_of_constructor.iterator();        while (iter2.next()) |entry| {            self.allocator.free(entry.value_ptr.*);        }        self.type_of_constructor.deinit();        self.constructors_of_type.deinit();        self.args_of_constructor.deinit();    }    pub fn defineType(        self: *Self,        type_name: Symbol,        constructors: []const struct { name: Symbol, args: []const Symbol },    ) !void {        var constructor_names = try self.allocator.alloc(Symbol, constructors.len);        for (constructors, 0..) |ctor, i| {            try self.type_of_constructor.put(ctor.name, type_name);            const args_copy = try self.allocator.alloc(Symbol, ctor.args.len);            @memcpy(args_copy, ctor.args);            try self.args_of_constructor.put(ctor.name, args_copy);            constructor_names[i] = ctor.name;        }        try self.constructors_of_type.put(type_name, constructor_names);    }    pub fn hasConstructor(self: *const Self, name: Symbol) bool {        return self.args_of_constructor.contains(name);    }    pub fn constructorArity(self: *const Self, name: Symbol) ?usize {        if (self.args_of_constructor.get(name)) |args| {            return args.len;        }        return null;    }    pub fn initWithDefaults(allocator: Allocator) !Self {        var self = Self.init(allocator);        try self.defineType("nat", &.{            .{ .name = "O", .args = &.{} },            .{ .name = "S", .args = &.{"nat"} },        });        try self.defineType("list", &.{            .{ .name = "Nil", .args = &.{} },            .{ .name = "Cons", .args = &.{ "nat", "list" } },        });        try self.defineType("snoclist", &.{            .{ .name = "SNil", .args = &.{} },            .{ .name = "Snoc", .args = &.{ "snoclist", "nat" } },        });        try self.defineType("bool", &.{            .{ .name = "True", .args = &.{} },            .{ .name = "False", .args = &.{} },        });        try self.defineType("unit", &.{            .{ .name = "Unit", .args = &.{} },        });        return self;    }};pub const Definition = struct {    name: Symbol,    expr: *PExpr,    is_stdlib: bool = false,    doc: ?[]const u8 = null,};pub const Definitions = struct {    allocator: Allocator,    defs: std.StringHashMap(Definition),    const Self = @This();    pub const DefineOptions = struct {        is_stdlib: bool = false,        doc: ?[]const u8 = null,    };    pub fn init(allocator: Allocator) Self {        return Self{            .allocator = allocator,            .defs = std.StringHashMap(Definition).init(allocator),        };    }    pub fn deinit(self: *Self) void {        var iter = self.defs.iterator();        while (iter.next()) |entry| {            entry.value_ptr.expr.deinit(self.allocator);            if (entry.value_ptr.doc) |doc| {                self.allocator.free(doc);            }        }        self.defs.deinit();    }    pub fn define(self: *Self, name: Symbol, expr: *PExpr) !void {        try self.defineWithOptions(name, expr, .{});    }    pub fn defineStdlib(self: *Self, name: Symbol, expr: *PExpr) !void {        try self.defineWithOptions(name, expr, .{ .is_stdlib = true });    }    pub fn defineWithDoc(self: *Self, name: Symbol, expr: *PExpr, doc: ?[]const u8) !void {        try self.defineWithOptions(name, expr, .{ .doc = doc });    }    pub fn defineStdlibWithDoc(self: *Self, name: Symbol, expr: *PExpr, doc: ?[]const u8) !void {        try self.defineWithOptions(name, expr, .{ .is_stdlib = true, .doc = doc });    }    fn defineWithOptions(self: *Self, name: Symbol, expr: *PExpr, options: DefineOptions) !void {        if (self.defs.get(name)) |existing| {            existing.expr.deinit(self.allocator);            if (existing.doc) |doc| {                self.allocator.free(doc);            }        }        const doc_copy: ?[]const u8 = if (options.doc) |d| try self.allocator.dupe(u8, d) else null;        try self.defs.put(name, Definition{            .name = name,            .expr = expr,            .is_stdlib = options.is_stdlib,            .doc = doc_copy,        });    }    pub fn lookup(self: *const Self, name: Symbol) ?*PExpr {        if (self.defs.get(name)) |def| {            return def.expr;        }        return null;    }    pub fn lookupDefinition(self: *const Self, name: Symbol) ?Definition {        return self.defs.get(name);    }    pub fn isDefined(self: *const Self, name: Symbol) bool {        return self.defs.contains(name);    }    pub fn remove(self: *Self, name: Symbol) ?*PExpr {        if (self.defs.fetchRemove(name)) |kv| {            return kv.value.expr;        }        return null;    }    pub fn clearUserDefinitions(self: *Self) void {        var to_remove: std.ArrayList(Symbol) = .empty;        defer to_remove.deinit(self.allocator);        var iter = self.defs.iterator();        while (iter.next()) |entry| {            if (!entry.value_ptr.is_stdlib) {                to_remove.append(self.allocator, entry.key_ptr.*) catch continue;            }        }        for (to_remove.items) |name| {            if (self.defs.fetchRemove(name)) |kv| {                kv.value.expr.deinit(self.allocator);            }        }    }};pub const Token = []const u8;fn isTokenDelimiter(c: u8) bool {    return switch (c) {        '(', ')', '{', '}', '[', ']', ',', '~', '`' => true,        else => false,    };}fn isTokenWhitespace(c: u8) bool {    return switch (c) {        ' ', '\t', '\r', '\n' => true,        else => false,    };}fn skipIgnored(source: []const u8, index: *usize) void {    while (index.* < source.len) {        const i = index.*;        const c = source[i];        if (c == ';' and i + 1 < source.len and source[i + 1] == ';') {            index.* += 2;            while (index.* < source.len and source[index.*] != '\n') {                index.* += 1;            }            continue;        }        if (isTokenWhitespace(c)) {            index.* += 1;            continue;        }        break;    }}fn isTwoByteTokenAt(source: []const u8, i: usize) bool {    if (i + 1 >= source.len) return false;    const c = source[i];    const next = source[i + 1];    return (c == '-' and next == '>') or        (c == '=' and next == '>') or        (c == 0xCE and next == 0xBB);}fn nextTokenSpan(source: []const u8, index: *usize) ?struct { start: usize, end: usize } {    skipIgnored(source, index);    if (index.* >= source.len) return null;    const start = index.*;    const c = source[start];    if (isTwoByteTokenAt(source, start)) {        index.* += 2;        return .{ .start = start, .end = index.* };    }    if (isTokenDelimiter(c) or c == '|') {        index.* += 1;        return .{ .start = start, .end = index.* };    }    while (index.* < source.len) {        const i = index.*;        const current = source[i];        if (isTokenWhitespace(current)) break;        if (current == ';' and i + 1 < source.len and source[i + 1] == ';') break;        if (isTwoByteTokenAt(source, i)) break;        if (isTokenDelimiter(current) or current == '|') break;        index.* += 1;    }    return .{ .start = start, .end = index.* };}pub fn tokenize(allocator: Allocator, source: []const u8) ![]Token {    var tokens: std.ArrayList(Token) = .empty;    errdefer tokens.deinit(allocator);    var index: usize = 0;    while (nextTokenSpan(source, &index)) |span| {        try tokens.append(allocator, source[span.start..span.end]);    }    return try tokens.toOwnedSlice(allocator);}pub fn freeTokens(allocator: Allocator, tokens: []Token) void {    allocator.free(tokens);}pub const ParseError = error{    UnexpectedEndOfInput,    ExpectedClosingParen,    ExpectedClosingBracket,    ExpectedArrow,    InvalidIdentifier,    InvalidExpression,    UnknownToken,    WrongArgumentCount,    DuplicateConstructor,    ExpectedOf,    OutOfMemory,};pub const Parser = struct {    allocator: Allocator,    tokens: []const Token,    pos: usize,    env: std.ArrayList(Symbol),    types: *const TypeRegistry,    defs: *const Definitions,    const Self = @This();    pub fn init(        allocator: Allocator,        tokens: []const Token,        types: *const TypeRegistry,        defs: *const Definitions,    ) Self {        return Self{            .allocator = allocator,            .tokens = tokens,            .pos = 0,            .env = .empty,            .types = types,            .defs = defs,        };    }    pub fn deinit(self: *Self) void {        self.env.deinit(self.allocator);    }    pub fn peek(self: *const Self) ?Token {        if (self.pos < self.tokens.len) {            return self.tokens[self.pos];        }        return null;    }    fn advance(self: *Self) ?Token {        if (self.pos < self.tokens.len) {            const token = self.tokens[self.pos];            self.pos += 1;            return token;        }        return null;    }    fn expect(self: *Self, expected: []const u8) !void {        const token = self.advance() orelse return ParseError.UnexpectedEndOfInput;        if (!std.mem.eql(u8, token, expected)) {            return ParseError.ExpectedClosingParen;        }    }    pub fn pushEnv(self: *Self, name: Symbol) !void {        try self.env.append(self.allocator, name);    }    fn dupeStr(self: *Self, s: []const u8) ![]const u8 {        return try self.allocator.dupe(u8, s);    }    pub fn popEnv(self: *Self) void {        _ = self.env.pop();    }    fn inEnv(self: *const Self, name: Symbol) bool {        for (self.env.items) |item| {            if (std.mem.eql(u8, item, name)) return true;        }        return false;    }    pub fn parseExpr(self: *Self) ParseError!*PExpr {        const token = self.advance() orelse return ParseError.UnexpectedEndOfInput;        if (std.mem.eql(u8, token, "(")) {            return self.parseCompound();        }        if (std.mem.eql(u8, token, "[")) {            return self.parseList();        }        if (token.len > 1 and token[0] == '\'') {            return PExpr.init(self.allocator, .{                .const_native = .{ .symbol = try self.dupeStr(token[1..]) },            });        }        if (token.len > 1 and token[0] == '@') {            const val = std.fmt.parseInt(i64, token[1..], 10) catch return ParseError.InvalidIdentifier;            return PExpr.init(self.allocator, .{                .const_native = .{ .int = val },            });        }        if (isInteger(token)) {            const val = std.fmt.parseInt(i64, token, 10) catch return ParseError.InvalidIdentifier;            return self.constToExpr(val);        }        if (isFloat(token)) {            const val = std.fmt.parseFloat(f64, token) catch return ParseError.InvalidIdentifier;            return PExpr.init(self.allocator, .{                .const_native = .{ .float = val },            });        }        if (std.mem.eql(u8, token, "true")) {            return PExpr.init(self.allocator, .{ .construct = .{ .constructor = "True" } });        }        if (std.mem.eql(u8, token, "false")) {            return PExpr.init(self.allocator, .{ .construct = .{ .constructor = "False" } });        }        if (std.mem.eql(u8, token, "nothing")) {            return PExpr.init(self.allocator, .{ .construct = .{ .constructor = "Unit" } });        }        if (token.len > 1 and token[0] == '$') {            return PExpr.init(self.allocator, .{ .var_ref = .{ .name = try self.dupeStr(token[1..]) } });        }        if (self.inEnv(token)) {            return PExpr.init(self.allocator, .{ .var_ref = .{ .name = try self.dupeStr(token) } });        }        if (self.defs.isDefined(token)) {            return PExpr.init(self.allocator, .{ .defined = .{ .name = try self.dupeStr(token) } });        }        if (self.types.hasConstructor(token)) {            if (self.types.constructorArity(token) == 0) {                return PExpr.init(self.allocator, .{ .construct = .{ .constructor = try self.dupeStr(token) } });            }        }        return ParseError.UnknownToken;    }    fn parseCompound(self: *Self) ParseError!*PExpr {        const head_token = self.peek() orelse return ParseError.UnexpectedEndOfInput;        if (isLambdaKeyword(head_token)) {            _ = self.advance();            return self.parseLambda();        }        if (std.mem.eql(u8, head_token, "if")) {            _ = self.advance();            return self.parseIf();        }        if (std.mem.eql(u8, head_token, "Y")) {            _ = self.advance();            return self.parseY();        }        if (std.mem.eql(u8, head_token, "case") or std.mem.eql(u8, head_token, "match")) {            _ = self.advance();            return self.parseCase();        }        if (std.mem.eql(u8, head_token, "let")) {            _ = self.advance();            return self.parseLet();        }        if (self.types.hasConstructor(head_token)) {            _ = self.advance();            return self.parseConstructor(head_token);        }        if (lookupPrim(head_token)) |head| {            _ = self.advance();            return self.parsePrimitive(head);        }        if (std.mem.eql(u8, head_token, "discrete")) {            _ = self.advance();            return self.parseDiscrete();        }        if (std.mem.eql(u8, head_token, "uniform")) {            _ = self.advance();            return self.parseUniform();        }        return self.parseApplication();    }    fn parseLambda(self: *Self) ParseError!*PExpr {        var arg_names: std.ArrayList(Symbol) = .empty;        defer arg_names.deinit(self.allocator);        const first = self.peek() orelse return ParseError.UnexpectedEndOfInput;        if (std.mem.eql(u8, first, "->")) {            _ = self.advance();            try self.pushEnv("_");            const body = try self.parseExpr();            self.popEnv();            try self.expect(")");            return PExpr.initWithArgs(self.allocator, .{ .abs = .{ .var_name = "_" } }, &.{body});        }        while (true) {            const token = self.peek() orelse return ParseError.UnexpectedEndOfInput;            if (std.mem.eql(u8, token, "->")) {                _ = self.advance();                break;            }            if (std.mem.eql(u8, token, ",")) {                _ = self.advance();                continue;            }            if (!isIdentifier(token)) return ParseError.InvalidIdentifier;            _ = self.advance();            const duped_name = try self.dupeStr(token);            try arg_names.append(self.allocator, duped_name);            try self.pushEnv(token);        }        const body = try self.parseExpr();        try self.expect(")");        for (arg_names.items) |_| {            self.popEnv();        }        var result = body;        var i = arg_names.items.len;        while (i > 0) {            i -= 1;            result = try PExpr.initWithArgs(self.allocator, .{ .abs = .{ .var_name = arg_names.items[i] } }, &.{result});        }        return result;    }    fn parseIf(self: *Self) ParseError!*PExpr {        const cond = try self.parseExpr();        const then_expr = try self.parseExpr();        const else_expr = try self.parseExpr();        try self.expect(")");        const branches = try self.allocator.alloc(CaseOfGuard, 2);        branches[0] = CaseOfGuard{ .constructor = "True", .args = &.{} };        branches[1] = CaseOfGuard{ .constructor = "False", .args = &.{} };        return PExpr.initWithArgs(            self.allocator,            .{ .case_of = .{ .branches = branches } },            &.{ cond, then_expr, else_expr },        );    }    fn parseY(self: *Self) ParseError!*PExpr {        const f = try self.parseExpr();        const next = self.peek();        if (next) |n| {            if (!std.mem.eql(u8, n, ")")) {                const x = try self.parseExpr();                try self.expect(")");                const y_expr = try PExpr.initWithArgs(self.allocator, .y_combinator, &.{f});                return PExpr.initWithArgs(self.allocator, .app, &.{ y_expr, x });            }        }        try self.expect(")");        return PExpr.initWithArgs(self.allocator, .y_combinator, &.{f});    }    fn parseCase(self: *Self) ParseError!*PExpr {        const scrutinee = try self.parseExpr();        const maybe_of = self.peek();        if (maybe_of) |token| {            if (std.mem.eql(u8, token, "of")) {                _ = self.advance();            }        }        var guards: std.ArrayList(CaseOfGuard) = .empty;        var branches: std.ArrayList(*PExpr) = .empty;        defer guards.deinit(self.allocator);        defer branches.deinit(self.allocator);        while (true) {            const token = self.peek() orelse return ParseError.UnexpectedEndOfInput;            if (std.mem.eql(u8, token, ")")) {                _ = self.advance();                break;            }            const constructor_tok = self.advance() orelse return ParseError.UnexpectedEndOfInput;            const constructor = try self.dupeStr(constructor_tok);            var args: std.ArrayList(Symbol) = .empty;            while (true) {                const arg_token = self.peek() orelse return ParseError.UnexpectedEndOfInput;                if (std.mem.eql(u8, arg_token, "=>")) {                    _ = self.advance();                    break;                }                _ = self.advance();                const duped_arg = try self.dupeStr(arg_token);                try args.append(self.allocator, duped_arg);                try self.pushEnv(arg_token);            }            for (guards.items) |g| {                if (std.mem.eql(u8, g.constructor, constructor)) {                    return ParseError.DuplicateConstructor;                }            }            const args_slice = try args.toOwnedSlice(self.allocator);            try guards.append(self.allocator, CaseOfGuard{ .constructor = constructor, .args = args_slice });            const body = try self.parseExpr();            try branches.append(self.allocator, body);            for (args_slice) |_| {                self.popEnv();            }            const sep = self.peek();            if (sep) |s| {                if (std.mem.eql(u8, s, "|")) {                    _ = self.advance();                }            }        }        const guards_slice = try guards.toOwnedSlice(self.allocator);        if (guards_slice.len == 0) {            return ParseError.InvalidExpression;        }        var all_args: std.ArrayList(*PExpr) = .empty;        defer all_args.deinit(self.allocator);        try all_args.append(self.allocator, scrutinee);        try all_args.appendSlice(self.allocator, branches.items);        return PExpr.initWithArgs(            self.allocator,            .{ .case_of = .{ .branches = guards_slice } },            try all_args.toOwnedSlice(self.allocator),        );    }    fn parseLet(self: *Self) ParseError!*PExpr {        const open = self.advance() orelse return ParseError.UnexpectedEndOfInput;        const close_token: []const u8 = if (std.mem.eql(u8, open, "[")) "]" else ")";        const Binding = struct { name: Symbol, val: *PExpr };        var bindings: std.ArrayList(Binding) = .empty;        defer bindings.deinit(self.allocator);        while (true) {            const token = self.peek() orelse return ParseError.UnexpectedEndOfInput;            if (std.mem.eql(u8, token, close_token)) {                _ = self.advance();                break;            }            if (std.mem.eql(u8, token, "(")) {                _ = self.advance();                const var_name_tok = self.advance() orelse return ParseError.UnexpectedEndOfInput;                const var_name = try self.dupeStr(var_name_tok);                const val = try self.parseExpr();                try self.expect(")");                try bindings.append(self.allocator, .{ .name = var_name, .val = val });                try self.pushEnv(var_name);            } else {                const var_name_tok = self.advance() orelse return ParseError.UnexpectedEndOfInput;                const var_name = try self.dupeStr(var_name_tok);                const val = try self.parseExpr();                try bindings.append(self.allocator, .{ .name = var_name, .val = val });                try self.pushEnv(var_name);            }        }        const body = try self.parseExpr();        try self.expect(")");        for (bindings.items) |_| {            self.popEnv();        }        var result = body;        var i = bindings.items.len;        while (i > 0) {            i -= 1;            const binding = bindings.items[i];            const abs_expr = try PExpr.initWithArgs(self.allocator, .{ .abs = .{ .var_name = binding.name } }, &.{result});            result = try PExpr.initWithArgs(self.allocator, .app, &.{ abs_expr, binding.val });        }        return result;    }    fn parseConstructor(self: *Self, constructor_tok: Symbol) ParseError!*PExpr {        const constructor = try self.dupeStr(constructor_tok);        var args: std.ArrayList(*PExpr) = .empty;        defer args.deinit(self.allocator);        while (true) {            const token = self.peek() orelse return ParseError.UnexpectedEndOfInput;            if (std.mem.eql(u8, token, ")")) {                _ = self.advance();                break;            }            const arg = try self.parseExpr();            try args.append(self.allocator, arg);        }        return PExpr.initWithArgs(            self.allocator,            .{ .construct = .{ .constructor = constructor } },            try args.toOwnedSlice(self.allocator),        );    }    fn parsePrimitive(self: *Self, head: Head) ParseError!*PExpr {        const arity = head.primArity() orelse 0;        var args: std.ArrayList(*PExpr) = .empty;        defer args.deinit(self.allocator);        for (0..arity) |_| {            const arg = try self.parseExpr();            try args.append(self.allocator, arg);        }        try self.expect(")");        return PExpr.initWithArgs(self.allocator, head, try args.toOwnedSlice(self.allocator));    }    fn parseApplication(self: *Self) ParseError!*PExpr {        const func = try self.parseExpr();        var args: std.ArrayList(*PExpr) = .empty;        defer args.deinit(self.allocator);        while (true) {            const token = self.peek() orelse return ParseError.UnexpectedEndOfInput;            if (std.mem.eql(u8, token, ")")) {                _ = self.advance();                break;            }            const arg = try self.parseExpr();            try args.append(self.allocator, arg);        }        if (args.items.len == 0) {            const unit = try PExpr.init(self.allocator, .{ .construct = .{ .constructor = "Unit" } });            try args.append(self.allocator, unit);        }        var result = func;        for (args.items) |arg| {            result = try PExpr.initWithArgs(self.allocator, .app, &.{ result, arg });        }        return result;    }    fn parseList(self: *Self) ParseError!*PExpr {        var items: std.ArrayList(*PExpr) = .empty;        defer items.deinit(self.allocator);        while (true) {            const token = self.peek() orelse return ParseError.UnexpectedEndOfInput;            if (std.mem.eql(u8, token, "]")) {                _ = self.advance();                break;            }            if (std.mem.eql(u8, token, ",")) {                _ = self.advance();                continue;            }            const item = try self.parseExpr();            try items.append(self.allocator, item);        }        var result = try PExpr.init(self.allocator, .{ .construct = .{ .constructor = "Nil" } });        var i = items.items.len;        while (i > 0) {            i -= 1;            result = try PExpr.initWithArgs(                self.allocator,                .{ .construct = .{ .constructor = "Cons" } },                &.{ items.items[i], result },            );        }        return result;    }    fn parseDiscrete(self: *Self) ParseError!*PExpr {        var options: std.ArrayList(*PExpr) = .empty;        var probs: std.ArrayList(f64) = .empty;        defer options.deinit(self.allocator);        defer probs.deinit(self.allocator);        while (true) {            const token = self.peek() orelse return ParseError.UnexpectedEndOfInput;            if (std.mem.eql(u8, token, ")")) {                _ = self.advance();                break;            }            try self.expect("(");            const expr = try self.parseExpr();            const prob_token = self.advance() orelse return ParseError.UnexpectedEndOfInput;            const prob = std.fmt.parseFloat(f64, prob_token) catch return ParseError.InvalidIdentifier;            try self.expect(")");            try options.append(self.allocator, expr);            try probs.append(self.allocator, prob);        }        if (options.items.len == 0) {            return ParseError.InvalidExpression;        }        var filtered_opts: std.ArrayList(*PExpr) = .empty;        var filtered_probs: std.ArrayList(f64) = .empty;        defer filtered_opts.deinit(self.allocator);        defer filtered_probs.deinit(self.allocator);        var total: f64 = 0.0;        for (options.items, probs.items) |opt, p| {            if (p > 0.0) {                try filtered_opts.append(self.allocator, opt);                try filtered_probs.append(self.allocator, p);                total += p;            }        }        if (filtered_opts.items.len == 0) {            if (!builtin.is_test) {                log.warn("discrete: all probabilities are zero or negative", .{});            }            return ParseError.InvalidExpression;        }        if (@abs(total - 1.0) > 1e-5) {            if (!builtin.is_test) {                log.warn("discrete: probabilities sum to {d:.6} (must be 1.0)", .{total});            }            return ParseError.InvalidExpression;        }        return self.buildDiscrete(filtered_opts.items, filtered_probs.items);    }    fn parseUniform(self: *Self) ParseError!*PExpr {        var options: std.ArrayList(*PExpr) = .empty;        defer options.deinit(self.allocator);        while (true) {            const token = self.peek() orelse return ParseError.UnexpectedEndOfInput;            if (std.mem.eql(u8, token, ")")) {                _ = self.advance();                break;            }            const expr = try self.parseExpr();            try options.append(self.allocator, expr);        }        if (options.items.len == 0) {            if (!builtin.is_test) {                log.warn("uniform: requires at least one option", .{});            }            return ParseError.InvalidExpression;        }        const n = options.items.len;        const probs = try self.allocator.alloc(f64, n);        defer self.allocator.free(probs);        for (probs) |*p| {            p.* = 1.0 / @as(f64, @floatFromInt(n));        }        return self.buildDiscrete(options.items, probs);    }    fn buildDiscrete(self: *Self, options: []*PExpr, probs: []f64) ParseError!*PExpr {        const n = options.len;        if (n == 0) return ParseError.InvalidExpression;        if (n == 1) return options[0];        if (n == 2) {            const flip_arg = try PExpr.init(self.allocator, .{ .const_native = .{ .float = probs[1] } });            const flip_expr = try PExpr.initWithArgs(self.allocator, .flip, &.{flip_arg});            const branches = try self.allocator.alloc(CaseOfGuard, 2);            branches[0] = CaseOfGuard{ .constructor = "True", .args = &.{} };            branches[1] = CaseOfGuard{ .constructor = "False", .args = &.{} };            return PExpr.initWithArgs(                self.allocator,                .{ .case_of = .{ .branches = branches } },                &.{ flip_expr, options[1], options[0] },            );        }        const num_bits = std.math.log2_int_ceil(usize, n);        const padded_len = @as(usize, 1) << @intCast(num_bits);        const padded_probs = try self.allocator.alloc(f64, padded_len);        defer self.allocator.free(padded_probs);        for (padded_probs, 0..) |*p, i| {            p.* = if (i < n) probs[i] else 0.0;        }        return self.buildDiscreteRecursive(options, padded_probs, num_bits, 0, padded_len);    }    fn buildDiscreteRecursive(        self: *Self,        options: []*PExpr,        probs: []f64,        bit_idx: usize,        start: usize,        end: usize,    ) ParseError!*PExpr {        if (bit_idx == 0) {            return if (start < options.len) options[start] else options[0];        }        const mid = (start + end) / 2;        var denom: f64 = 0;        for (probs[start..end]) |p| denom += p;        if (denom == 0) {            return self.buildDiscreteRecursive(options, probs, bit_idx - 1, start, mid);        }        var right_sum: f64 = 0;        for (probs[mid..end]) |p| right_sum += p;        const p = right_sum / denom;        const left = try self.buildDiscreteRecursive(options, probs, bit_idx - 1, mid, end);        const right = try self.buildDiscreteRecursive(options, probs, bit_idx - 1, start, mid);        const flip_arg = try PExpr.init(self.allocator, .{ .const_native = .{ .float = p } });        const flip_expr = try PExpr.initWithArgs(self.allocator, .flip, &.{flip_arg});        const branches = try self.allocator.alloc(CaseOfGuard, 2);        branches[0] = CaseOfGuard{ .constructor = "True", .args = &.{} };        branches[1] = CaseOfGuard{ .constructor = "False", .args = &.{} };        return PExpr.initWithArgs(            self.allocator,            .{ .case_of = .{ .branches = branches } },            &.{ flip_expr, left, right },        );    }    fn constToExpr(self: *Self, val: i64) ParseError!*PExpr {        var result = try PExpr.init(self.allocator, .{ .construct = .{ .constructor = "O" } });        var i: i64 = 0;        while (i < val) : (i += 1) {            result = try PExpr.initWithArgs(                self.allocator,                .{ .construct = .{ .constructor = "S" } },                &.{result},            );        }        return result;    }};fn lookupPrim(name: []const u8) ?Head {    const prims = .{        .{ "Y", Head.y_combinator },        .{ "flip", Head.flip },        .{ "factor", Head.factor },        .{ "native_eq", Head.native_eq },        .{ "get_args", Head.get_args },        .{ "get_constructor", Head.get_constructor },        .{ "pbool", Head.pbool },        .{ "get_config", Head.get_config },        .{ "mk_int", Head.mk_int },        .{ "mk_int_weighted", Head.mk_int_weighted },        .{ "int_dist_eq", Head.int_dist_eq },        .{ "print", Head.print_op },        .{ "/.", Head.f_div },        .{ "*.", Head.f_mul },        .{ "+.", Head.f_add },        .{ "-.", Head.f_sub },        .{ "error", Head.error_op },    };    inline for (prims) |prim| {        if (std.mem.eql(u8, name, prim[0])) {            return prim[1];        }    }    return null;}fn isLambdaKeyword(token: []const u8) bool {    return std.mem.eql(u8, token, "lam") or        std.mem.eql(u8, token, "lambda") or        std.mem.eql(u8, token, "λ") or        std.mem.eql(u8, token, "fn");}fn isIdentifier(token: []const u8) bool {    if (token.len == 0) return false;    const first = token[0];    if (!std.ascii.isAlphabetic(first) and first != '_') return false;    for (token[1..]) |c| {        if (!std.ascii.isAlphanumeric(c) and c != '_') return false;    }    return true;}fn isInteger(token: []const u8) bool {    if (token.len == 0) return false;    for (token) |c| {        if (!std.ascii.isDigit(c)) return false;    }    return true;}fn isFloat(token: []const u8) bool {    if (token.len == 0) return false;    var has_dot = false;    const start: usize = if (token[0] == '-') 1 else 0;    for (token[start..]) |c| {        if (c == '.') {            if (has_dot) return false;            has_dot = true;        } else if (!std.ascii.isDigit(c)) {            return false;        }    }    return has_dot;}pub fn parseExpr(    allocator: Allocator,    source: []const u8,    types: *const TypeRegistry,    defs: *const Definitions,) !*PExpr {    const tokens = try tokenize(allocator, source);    defer freeTokens(allocator, tokens);    var parser = Parser.init(allocator, tokens, types, defs);    defer parser.deinit();    const expr = try parser.parseExpr();    if (parser.pos < parser.tokens.len) {        expr.deinit(allocator);        return ParseError.UnexpectedEndOfInput;    }    return expr;}test "tokenize basic" {    const allocator = std.testing.allocator;    const tokens = try tokenize(allocator, "(λ x -> x)");    defer freeTokens(allocator, tokens);    try std.testing.expectEqual(@as(usize, 6), tokens.len);    try std.testing.expectEqualStrings("(", tokens[0]);    try std.testing.expectEqualStrings("λ", tokens[1]);    try std.testing.expectEqualStrings("x", tokens[2]);    try std.testing.expectEqualStrings("->", tokens[3]);    try std.testing.expectEqualStrings("x", tokens[4]);    try std.testing.expectEqualStrings(")", tokens[5]);}test "tokenize with comment" {    const allocator = std.testing.allocator;    const tokens = try tokenize(allocator, "(x) ;; this is a comment\n(y)");    defer freeTokens(allocator, tokens);    try std.testing.expectEqual(@as(usize, 6), tokens.len);    try std.testing.expectEqualStrings("(", tokens[0]);    try std.testing.expectEqualStrings("x", tokens[1]);    try std.testing.expectEqualStrings(")", tokens[2]);    try std.testing.expectEqualStrings("(", tokens[3]);    try std.testing.expectEqualStrings("y", tokens[4]);    try std.testing.expectEqualStrings(")", tokens[5]);}test "tokenize adjacent syntax" {    const allocator = std.testing.allocator;    const tokens = try tokenize(allocator, "a->b=>c|λ[x,y] ;; drop\n`z~q");    defer freeTokens(allocator, tokens);    const expected = [_][]const u8{        "a", "->", "b", "=>", "c", "|",        "λ",        "[", "x",  ",", "y",  "]", "`",        "z", "~",  "q",    };    try std.testing.expectEqual(@as(usize, expected.len), tokens.len);    for (expected, 0..) |token, i| {        try std.testing.expectEqualStrings(token, tokens[i]);    }}test "parse identity lambda" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "(λ x -> x)", &types, &defs);    try std.testing.expect(expr.head == .abs);    try std.testing.expectEqualStrings("x", expr.head.abs.var_name);}test "parse nat literal" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "3", &types, &defs);    try std.testing.expect(expr.head == .construct);    try std.testing.expectEqualStrings("S", expr.head.construct.constructor);    try std.testing.expectEqual(@as(?i64, 3), expr.maybeConst());}test "parse if expression" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "(if true 1 0)", &types, &defs);    try std.testing.expect(expr.head == .case_of);    try std.testing.expectEqual(@as(usize, 2), expr.head.case_of.branches.len);}test "parse application" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "((λ x -> x) 42)", &types, &defs);    try std.testing.expect(expr.head == .app);}test "parse list literal" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "[1, 2, 3]", &types, &defs);    try std.testing.expect(expr.head == .construct);    try std.testing.expectEqualStrings("Cons", expr.head.construct.constructor);}test "parse let expression" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "(let [x 1] x)", &types, &defs);    try std.testing.expect(expr.head == .app);}test "parse flip primitive" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "(flip 0.5)", &types, &defs);    try std.testing.expect(expr.head == .flip);    try std.testing.expectEqual(@as(usize, 1), expr.args.len);}test "parse factor primitive" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "(factor 0.2)", &types, &defs);    try std.testing.expect(expr.head == .factor);    try std.testing.expectEqual(@as(usize, 1), expr.args.len);}test "parse case expression" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const expr = try parseExpr(allocator, "(case true of True => 1 | False => 0)", &types, &defs);    try std.testing.expect(expr.head == .case_of);    try std.testing.expectEqual(@as(usize, 2), expr.head.case_of.branches.len);    try std.testing.expectEqualStrings("True", expr.head.case_of.branches[0].constructor);    try std.testing.expectEqualStrings("False", expr.head.case_of.branches[1].constructor);}test "empty uniform rejects" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const result = parseExpr(allocator, "(uniform)", &types, &defs);    try std.testing.expectError(ParseError.InvalidExpression, result);}test "empty discrete rejects" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const result = parseExpr(allocator, "(discrete)", &types, &defs);    try std.testing.expectError(ParseError.InvalidExpression, result);}test "discrete rejects probabilities not summing to 1" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const result = parseExpr(allocator, "(discrete (True 0.3) (False 0.4))", &types, &defs);    try std.testing.expectError(ParseError.InvalidExpression, result);}test "discrete filters zero probabilities" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const result = parseExpr(allocator, "(discrete (True 0.6) (False 0.0) (True 0.4))", &types, &defs);    try std.testing.expect(result != error.InvalidExpression);}test "discrete accepts valid distribution" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const result = try parseExpr(allocator, "(discrete (True 0.3) (False 0.7))", &types, &defs);    try std.testing.expect(result.head == .case_of);}test "empty case rejects" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var types = try TypeRegistry.initWithDefaults(allocator);    var defs = Definitions.init(allocator);    const result = parseExpr(allocator, "(case true of )", &types, &defs);    try std.testing.expectError(ParseError.InvalidExpression, result);}

Source: lib/pluck/src/root.zig:18

zig
pub const pexpr = @import("pexpr.zig");

Complete caller list for pexpr.Definitions.deinit

45 direct callers.

Complete caller list for pexpr.Definitions.init

69 direct callers.

Complete caller list for pexpr.PExpr.deinit

15 direct callers.

Complete caller list for pexpr.PExpr.init

38 direct callers.

Complete caller list for pexpr.PExpr.initWithArgs

43 direct callers.

Complete caller list for pexpr.Parser.parseExpr

16 direct callers.

Complete call list for pexpr.Parser.parseExpr

12 direct calls.

Complete caller list for pexpr.Parser.peek

11 direct callers.

Complete caller list for pexpr.TypeRegistry.initWithDefaults

33 direct callers.

Complete caller list for pexpr.freeTokens

7 direct callers.

Complete caller list for pexpr.parseExpr

33 direct callers.

Complete caller list for pexpr.tokenize

7 direct callers.

Audit

Definitions49
Public names49
Members64
Version26.7.0
Revisiondaab053ee433