#!/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()