diff --git a/compiler/cl-embed/examples/calculator.rs b/compiler/cl-embed/examples/calculator.rs index 194c72c..3fc4db1 100644 --- a/compiler/cl-embed/examples/calculator.rs +++ b/compiler/cl-embed/examples/calculator.rs @@ -1,25 +1,27 @@ +//! Demonstrates the cl_embed library + use cl_embed::*; use repline::{Response, prebaked}; fn main() -> Result<(), repline::Error> { + let mut env = Environment::new(); + + if let Err(e) = conlang_include!("calculator/expression.cl")(&mut env) { + panic!("{e}") + } + prebaked::read_and("", "calc >", " ? >", |line| { - calc(line).map_err(Into::into) + env.bind("line", line); + + let res = conlang! { + + let (expr, rest) = parse(line.chars(), Power::None); + execute(expr) + + }(&mut env)?; + + println!("{res}"); + + Ok(Response::Accept) }) } - -fn calc(line: &str) -> Result { - let mut env = Environment::new(); - env.bind("line", line); - - let res = conlang!( - mod expression; - use expression::{eval, parse}; - - let (expr, rest) = parse(line.chars(), 0); - eval(expr) - )(&mut env)?; - - println!("{res}"); - - Ok(Response::Accept) -} diff --git a/compiler/cl-embed/src/lib.rs b/compiler/cl-embed/src/lib.rs index 5312290..9062758 100644 --- a/compiler/cl-embed/src/lib.rs +++ b/compiler/cl-embed/src/lib.rs @@ -7,7 +7,7 @@ pub use cl_interpret::{convalue::ConValue as Value, env::Environment}; -use cl_ast::{Block, Module, ast_visitor::Fold}; +use cl_ast::{Block, File, Module, ast_visitor::Fold}; use cl_interpret::{convalue::ConValue, interpret::Interpret}; use cl_lexer::Lexer; use cl_parser::{Parser, error::Error as ParseError, inliner::ModuleInliner}; @@ -40,7 +40,7 @@ use std::{path::Path, sync::OnceLock}; /// # Ok(()) /// # } /// ``` -pub macro conlang ( +pub macro conlang( $($t:tt)* ) {{ // Parse once @@ -49,11 +49,13 @@ pub macro conlang ( |env: &mut Environment| -> Result { FN.get_or_init(|| { // TODO: embed the full module tree at compile time - let path = AsRef::::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"),"/../../", file!())).with_extension(""); + let path = + AsRef::::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"), "/../../", file!())) + .with_extension(""); let mut mi = ModuleInliner::new(path); let code = mi.fold_block( Parser::new( - concat!(file!(), ":", line!(), ":"), + concat!(file!(), ":", line!(), ":", column!()), Lexer::new(stringify!({ $($t)* })), ) .parse::()?, @@ -75,6 +77,60 @@ pub macro conlang ( } }} +pub macro conlang_include{ + ($path:literal, $name:ident) => { + |env: &mut Environment| -> Result { + // TODO: embed the full module tree at compile time + let path = AsRef::::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"), "/../../", file!())) + .with_file_name(concat!($path)); + let mut mi = ModuleInliner::new(path); + let code = mi.fold_module(Module { + name: stringify!($name).into(), + file: Some( + Parser::new( + concat!(file!(), ":", line!(), ":", column!()), + Lexer::new(include_str!($path)), + ) + .parse()?, + ), + }); + if let Some((ie, pe)) = mi.into_errs() { + for (file, err) in ie { + eprintln!("{}: {err}", file.display()); + } + for (file, err) in pe { + eprintln!("{}: {err}", file.display()); + } + } + code.interpret(env).map_err(Into::into) + } + }, + ($path:literal) => { + |env: &mut Environment| -> Result { + // TODO: embed the full module tree at compile time + let path = AsRef::::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"), "/../../", file!())) + .with_file_name(concat!($path)); + let mut mi = ModuleInliner::new(path); + let code = mi.fold_file( + Parser::new( + concat!(file!(), ":", line!(), ":", column!()), + Lexer::new(include_str!($path)), + ) + .parse()?, + ); + if let Some((ie, pe)) = mi.into_errs() { + for (file, err) in ie { + eprintln!("{}: {err}", file.display()); + } + for (file, err) in pe { + eprintln!("{}: {err}", file.display()); + } + } + code.interpret(env).map_err(Into::into) + } + } +} + #[derive(Clone, Debug)] pub enum EvalError { Parse(cl_parser::error::Error), diff --git a/sample-code/calculator.cl b/sample-code/calculator.cl index 7ec329c..69a3000 100644 --- a/sample-code/calculator.cl +++ b/sample-code/calculator.cl @@ -1,37 +1,43 @@ #!/usr/bin/env -S conlang-run //! A simple five-function pn calculator -// TODO: enum constructors in the interpreter -struct Atom(f64); -struct Op(char, [Expr]); - enum Expr { Atom(f64), Op(char, [Expr]), } -// Evaluates an expression -fn eval(expr: Expr) -> isize { +/// executes an expression +fn execute(expr: Expr) -> f64 { match expr { - Atom(value) => value, - Op('*', [lhs, rhs]) => eval(lhs) * eval(rhs), - Op('/', [lhs, rhs]) => eval(lhs) / eval(rhs), - Op('%', [lhs, rhs]) => eval(lhs) % eval(rhs), - Op('+', [lhs, rhs]) => eval(lhs) + eval(rhs), - Op('-', [lhs, rhs]) => eval(lhs) - eval(rhs), - Op('-', [lhs]) => - eval(lhs), - Op(other, ..rest) => { - panic("ERROR: Unknown operator: " + other) - }, + 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 => { - println(other); - panic("ERROR: Unknown operation ^") + 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]) { @@ -42,9 +48,11 @@ fn parse(line: [char], power: i32) -> (Expr, [char]) { let (lhs, line) = match line { ['0'..='9', ..] => number(line), - [op, ..rest] => { - parse(rest, pre_bp(op)).map(|lhs| Op(op, [lhs])) - }, + ['(', ..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"), }; @@ -53,7 +61,7 @@ fn parse(line: [char], power: i32) -> (Expr, [char]) { if before < power { break; }; - (lhs, line) = parse(rest, after).map(|rhs| Op(op, [lhs, rhs])); + (lhs, line) = parse(rest, after).map(|rhs| Expr::Op(op, [lhs, rhs])); }; (lhs, line) } @@ -61,35 +69,49 @@ fn parse(line: [char], power: i32) -> (Expr, [char]) { fn number(line: [char]) -> (Expr, [char]) { let value = 0.0; while (let [first, ..rest] = line) && (let '0'..='9' = first) { - value = value * 10.0 + (first as f64 - '0' as f64); - line = rest; - }; - (Atom(value), line) + (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 { - '*' => 2, - '/' => 2, - '%' => 2, - '+' => 1, - '-' => 1, - _ => -1, + '*' => 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 { - '-' => 9, - _ => -1, + '-' => Power::Unary, + _ => panic("Unknown unary operator: " + op), }) } + +fn my_eval(input: str) { + let (expr, rest) = input.chars().parse(0); + println(expr, " ", rest); + + execute(expr) +}