#!/usr/bin/env python3
"""
Bracket-only surface language for defect functions.

Alphabet:
  ▷ ◁

No names. No numerals. No parentheses.

Parsing is arity-driven prefix application:
  - a function-defect consumes the next expression(s)
  - a plain defect literal is a value

Examples:
  ▷◁▷◁ ▷▷▷▷◁                     succ 4
  ▷◁▷◁▷◁ ▷▷◁ ▷▷▷◁                add 2 3
  ▷◁▷◁▷◁▷◁ ▷▷◁ ▷▷▷◁              mul 2 3
  ▷◁▷◁▷◁ ▷◁▷◁▷◁▷◁ ▷▷◁ ▷▷◁ ▷◁     add (mul 2 2) 1
"""

from __future__ import annotations

from typing import Sequence

from church import DefectFunction, add, mul, succ
from ontic_spinor import parse as parse_term


BUILTINS: dict[str, callable] = {
    str(succ().entity): succ,
    str(add().entity): add,
    str(mul().entity): mul,
}


def tokenize(text: str) -> list[str]:
    tokens: list[str] = []
    i = 0
    while i < len(text):
        ch = text[i]
        if ch.isspace():
            i += 1
            continue
        if ch in {"▷", "◁", "─"}:
            j = i
            while j < len(text) and text[j] in {"▷", "◁", "─"}:
                j += 1
            tokens.append(text[i:j])
            i = j
            continue
        raise ValueError(f"Unexpected character: {ch!r}")
    return tokens


def parse_value(tokens: Sequence[str], pos: int):
    if pos >= len(tokens):
        raise ValueError("Unexpected end of input")
    token = tokens[pos]
    try:
        factory = BUILTINS[token]
    except KeyError:
        return parse_term(token), pos + 1

    fn = factory()
    value = fn
    pos += 1
    for _ in range(fn.arity):
        arg, pos = parse_value(tokens, pos)
        if not isinstance(value, DefectFunction):
            raise ValueError(f"Too many arguments for defect: {value}")
        value = value.apply(arg)
    return value, pos


def run(source: str):
    tokens = tokenize(source)
    value, pos = parse_value(tokens, 0)
    if pos != len(tokens):
        raise ValueError(f"Unexpected trailing tokens: {tokens[pos:]}")
    return value


def demo() -> None:
    programs = [
        "▷◁▷◁ ▷▷▷▷◁",
        "▷◁▷◁▷◁ ▷▷◁ ▷▷▷◁",
        "▷◁▷◁▷◁▷◁ ▷▷◁ ▷▷▷◁",
        "▷◁▷◁▷◁ ▷◁▷◁▷◁▷◁ ▷▷◁ ▷▷◁ ▷◁",
    ]

    print("=" * 60)
    print("BRACKET-ONLY DEFECT LANGUAGE")
    print("=" * 60)
    print()
    for program in programs:
        print(f"  {program}  =>  {run(program)}")


if __name__ == "__main__":
    demo()
An unhandled error has occurred. Reload 🗙