#!/usr/bin/env -S conlang-run //! A simple five-function pn calculator enum Expr { Atom(f64), Op(char, [Expr]), } /// executes an expression fn execute(expr: Expr) -> f64 { match expr { Expr::Atom(value) => value, Expr::Op('*', [lhs, rhs]) => execute(lhs) * execute(rhs), Expr::Op('/', [lhs, rhs]) => execute(lhs) / execute(rhs), Expr::Op('%', [lhs, rhs]) => execute(lhs) % execute(rhs), Expr::Op('+', [lhs, rhs]) => execute(lhs) + execute(rhs), Expr::Op('-', [lhs, rhs]) => execute(lhs) - execute(rhs), Expr::Op('-', [lhs]) => - execute(lhs), other => { panic("Unknown operation: " + fmt(other)) } } } /// Pretty-prints an expression fn fmt_expr(expr: Expr) -> str { match expr { Expr::Atom(value) => fmt(value), Expr::Op(operator, [lhs, rhs]) => fmt('(', fmt_expr(lhs), ' ', operator, ' ', fmt_expr(rhs), ')'), Expr::Op(operator, [rhs]) => fmt(operator, fmt_expr(rhs)), _ => println("Unexpected expr: ", expr), } } fn print_expr(expr: Expr) { println(fmt_expr(expr)) } /// Parses expressions fn parse(line: [char], power: i32) -> (Expr, [char]) { fn map((expr, line): (Expr, [char]), f: fn(Expr) -> Expr) -> (Expr, [char]) { (f(expr), line) } line = space(line); let (lhs, line) = match line { ['0'..='9', ..] => number(line), ['(', ..rest] => match parse(rest, Power::None) { (expr, [')', ..rest]) => (expr, rest), (expr, rest) => panic(fmt("Expected ')', got ", expr, ", ", rest)), }, [op, ..rest] => parse(rest, pre_bp(op)).map(|lhs| Expr::Op(op, [lhs])), _ => panic("Unexpected end of input"), }; while let [op, ..rest] = space(line) { let (before, after) = inf_bp(op); if before < power { break; }; (lhs, line) = parse(rest, after).map(|rhs| Expr::Op(op, [lhs, rhs])); }; (lhs, line) } fn number(line: [char]) -> (Expr, [char]) { let value = 0.0; while (let [first, ..rest] = line) && (let '0'..='9' = first) { (value, line) = (value * 10.0 + (first as f64 - '0' as f64), rest) } else (Expr::Atom(value), line) } fn space(line: [char]) -> [char] { match line { [' ', ..rest] => space(rest), ['\n', ..rest] => space(rest), line => line } } enum Power { None, Factor, Term, Exponent, Unary, } fn inf_bp(op: char) -> (i32, i32) { (|x| (2 * x, 2 * x + 1))( match op { '*' => Power::Term, '/' => Power::Term, '%' => Power::Term, '+' => Power::Factor, '-' => Power::Factor, _ => panic("Unknown operation: " + op), }) } fn pre_bp(op: char) -> i32 { (|x| 2 * x + 1)( match op { '-' => Power::Unary, _ => panic("Unknown unary operator: " + op), }) } fn my_eval(input: str) { let (expr, rest) = input.chars().parse(0); println(expr, " ", rest); execute(expr) }