pipa-lang 1.0.0-alpha.1

A tiny template language
Documentation
// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
// SPDX-License-Identifier: MPL-2.0

mod abs;
mod add;
mod all;
mod any;
mod append;
mod assert;
mod capitalize;
mod ceil;
mod cmp;
mod description;
mod diminish;
mod div;
mod empty;
mod eq;
mod escape;
mod filter;
mod filter_by_key;
mod find;
mod find_by_key;
mod first;
mod floor;
mod format_base64;
mod format_bool;
mod format_date;
mod format_input;
mod format_json;
mod format_lower;
mod format_paragraphs;
mod format_syntax;
mod format_tag;
mod format_upper;
mod get;
mod group;
mod gt;
mod gte;
mod has;
mod hide;
mod highlight;
mod insert;
mod internal_debug;
mod internal_json;
mod internal_tracer;
mod intersection;
mod is_bytes;
mod is_list;
mod is_map;
mod join;
mod key;
mod keys;
mod label;
mod last;
mod length;
mod lt;
mod lte;
mod map;
mod math;
mod meta;
mod mul;
mod ne;
mod not;
mod omit;
mod order;
mod pattern;
mod percent_decode;
mod percent_encode;
mod pick;
mod placeholder;
mod prepend;
mod quote;
mod reduce;
mod rem;
mod repeat;
mod replace;
mod rest;
mod round;
mod safe;
mod set;
mod slice;
mod sort;
mod span;
mod split;
mod strip_end;
mod strip_start;
mod sub;
mod toggle;
mod trace;
mod trim;
mod truncate;
mod truncate_start;
mod union;
mod values;

use crate::lang::adapter::Adapter;
use crate::lang::args::eval_args;
use crate::lang::arity::assert_func_arity;
use crate::lang::error::Error;
use crate::lang::error::ErrorKind;
use crate::lang::eval::eval_context_getter;
use crate::lang::label::Label;
use crate::lang::machine::Machine;
use crate::lang::node::Node;
use crate::value::Value;
use either::Either;

pub(crate) fn call(
    adapter: &impl Adapter,
    machine: &mut Machine<'_>,
    stdin: Option<&Value>,
    node: Node,
    func: &[u8],
) -> Result<Either<Node, Value>, Error> {
    let node = match call_adapter_func(adapter, machine, stdin, node, func)? {
        Either::Right(value) => return Ok(Either::Right(value)),
        Either::Left(node) => node,
    };

    if func.starts_with(b"internal") {
        return call_internal_func(adapter, machine, stdin, node).map(Either::Right);
    }

    call_func(adapter, machine, stdin, node, func)
}

fn call_func(
    adapter: &impl Adapter,
    machine: &mut Machine<'_>,
    span: Option<&Value>,
    node: Node,
    func: &[u8],
) -> Result<Either<Node, Value>, Error> {
    Ok(Either::Right(match func {
        b"abs" => abs::abs(adapter, machine, span, node)?,
        b"add" => add::add(adapter, machine, span, node)?,
        b"all" => all::all(adapter, machine, span, node)?,
        b"any" => any::any(adapter, machine, span, node)?,
        b"append" => append::append(adapter, machine, span, node)?,
        b"assert" => assert::assert(adapter, machine, span, node)?,
        b"capitalize" => capitalize::capitalize(adapter, machine, span, node)?,
        b"ceil" => ceil::ceil(adapter, machine, span, node)?,
        b"description" => description::description(adapter, machine, span, node)?,
        b"diminish" => diminish::diminish(adapter, machine, span, node)?,
        b"div" => div::div(adapter, machine, span, node)?,
        b"empty" => empty::empty(adapter, machine, span, node)?,
        b"eq" => eq::eq(adapter, machine, span, node)?,
        b"escape" => escape::escape(adapter, machine, span, node)?,
        b"filter" => filter::filter(adapter, machine, span, node)?,
        b"filter-by-key" => filter_by_key::filter_by_key(adapter, machine, span, node)?,
        b"find" => find::find(adapter, machine, span, node)?,
        b"find-by-key" => find_by_key::find_by_key(adapter, machine, span, node)?,
        b"first" => first::first(adapter, machine, span, node)?,
        b"floor" => floor::floor(adapter, machine, span, node)?,
        b"format-base64" => format_base64::format_base64(adapter, machine, span, node)?,
        b"format-bool" => format_bool::format_bool(adapter, machine, span, node)?,
        b"format-date" => format_date::format_date(adapter, machine, span, node)?,
        b"format-input" => format_input::format_input(adapter, machine, span, node)?,
        b"format-json" => format_json::format_json(adapter, machine, span, node)?,
        b"format-lower" => format_lower::format_lower(adapter, machine, span, node)?,
        b"format-paragraphs" => format_paragraphs::format_paragraphs(adapter, machine, span, node)?,
        b"format-syntax" => format_syntax::format_syntax(adapter, machine, span, node)?,
        b"format-tag" => format_tag::format_tag(adapter, machine, span, node)?,
        b"format-upper" => format_upper::format_upper(adapter, machine, span, node)?,
        b"get" => get::get(adapter, machine, span, node)?,
        b"group" => group::group(adapter, machine, span, node)?,
        b"gt" => gt::gt(adapter, machine, span, node)?,
        b"gte" => gte::gte(adapter, machine, span, node)?,
        b"has" => has::has(adapter, machine, span, node)?,
        b"hide" => hide::hide(adapter, machine, span, node)?,
        b"highlight" => highlight::highlight(adapter, machine, span, node)?,
        b"insert" => insert::insert(adapter, machine, span, node)?,
        b"intersection" => intersection::intersection(adapter, machine, span, node)?,
        b"is-bytes" => is_bytes::is_bytes(adapter, machine, span, node)?,
        b"is-list" => is_list::is_list(adapter, machine, span, node)?,
        b"is-map" => is_map::is_map(adapter, machine, span, node)?,
        b"join" => join::join(adapter, machine, span, node)?,
        b"key" => key::key(adapter, machine, span, node)?,
        b"keys" => keys::keys(adapter, machine, span, node)?,
        b"label" => label::label(adapter, machine, span, node)?,
        b"last" => last::last(adapter, machine, span, node)?,
        b"length" => length::length(adapter, machine, span, node)?,
        b"lt" => lt::lt(adapter, machine, span, node)?,
        b"lte" => lte::lte(adapter, machine, span, node)?,
        b"map" => map::map(adapter, machine, span, node)?,
        b"meta" => meta::meta(adapter, machine, span, node)?,
        b"mul" => mul::mul(adapter, machine, span, node)?,
        b"ne" => ne::ne(adapter, machine, span, node)?,
        b"not" => not::not(adapter, machine, span, node)?,
        b"omit" => omit::omit(adapter, machine, span, node)?,
        b"order" => order::order(adapter, machine, span, node)?,
        b"pattern" => pattern::pattern(adapter, machine, span, node)?,
        b"percent-decode" => percent_decode::percent_decode(adapter, machine, span, node)?,
        b"percent-encode" => percent_encode::percent_encode(adapter, machine, span, node)?,
        b"pick" => pick::pick(adapter, machine, span, node)?,
        b"placeholder" => placeholder::placeholder(adapter, machine, span, node)?,
        b"prepend" => prepend::prepend(adapter, machine, span, node)?,
        b"quote" => quote::quote(adapter, machine, span, node)?,
        b"reduce" => reduce::reduce(adapter, machine, span, node)?,
        b"rem" => rem::rem(adapter, machine, span, node)?,
        b"repeat" => repeat::repeat(adapter, machine, span, node)?,
        b"replace" => replace::replace(adapter, machine, span, node)?,
        b"rest" => rest::rest(adapter, machine, span, node)?,
        b"round" => round::round(adapter, machine, span, node)?,
        b"safe" => safe::safe(adapter, machine, span, node)?,
        b"set" => set::set(adapter, machine, span, node)?,
        b"slice" => slice::slice(adapter, machine, span, node)?,
        b"sort" => sort::sort(adapter, machine, span, node)?,
        b"span" => span::span(adapter, machine, span, node)?,
        b"split" => split::split(adapter, machine, span, node)?,
        b"strip-end" => strip_end::strip_end(adapter, machine, span, node)?,
        b"strip-start" => strip_start::strip_start(adapter, machine, span, node)?,
        b"sub" => sub::sub(adapter, machine, span, node)?,
        b"toggle" => toggle::toggle(adapter, machine, span, node)?,
        b"trace" => trace::trace(adapter, machine, span, node)?,
        b"trim" => trim::trim(adapter, machine, span, node)?,
        b"truncate" => truncate::truncate(adapter, machine, span, node)?,
        b"truncate-start" => truncate_start::truncate_start(adapter, machine, span, node)?,
        b"union" => union::union(adapter, machine, span, node)?,
        b"values" => values::values(adapter, machine, span, node)?,
        _ => return Ok(Either::Left(node)),
    }))
}

fn call_internal_func(
    adapter: &impl Adapter,
    machine: &mut Machine<'_>,
    stdin: Option<&Value>,
    node: Node,
) -> Result<Value, Error> {
    let name = node
        .list()
        .first()
        .and_then(Node::as_label)
        .map(Label::as_name);

    match name {
        Some(n) if n == b"internal-debug" => {
            internal_debug::internal_debug(adapter, machine, stdin, node)
        }
        Some(n) if n == b"internal-tracer" => {
            internal_tracer::internal_tracer(adapter, machine, stdin, node)
        }
        Some(n) if n == b"internal-json" => {
            internal_json::internal_json(adapter, machine, stdin, node)
        }
        Some(n) if n.starts_with(b"internal") => {
            Ok(eval_context_getter(adapter, machine, stdin, node)?.unwrap_or_default())
        }
        _ => Err(Error {
            kind: ErrorKind::Func,
            source: adapter.template_source().map(String::from),
            span: machine.span,
            message: "unknown internal func".into(),
        }),
    }
}

fn call_adapter_func(
    adapter: &impl Adapter,
    machine: &mut Machine<'_>,
    stdin: Option<&Value>,
    node: Node,
    func: &[u8],
) -> Result<Either<Node, Value>, Error> {
    let Some(arity) = adapter.func_find(func) else {
        return Ok(Either::Left(node));
    };

    assert_func_arity(adapter, machine, stdin, &node, &arity)?;
    let args = eval_args(adapter, machine, stdin, node)?;

    adapter
        .func_call(func, args)
        .map(|v| Either::Right(v.unwrap_or_default()))
        .map_err(|e| Error {
            kind: ErrorKind::Func,
            source: adapter.template_source().map(String::from),
            span: machine.span,
            message: e,
        })
}