cl-embed: Calculator example update!

This commit is contained in:
John 2025-07-18 05:26:39 -04:00
parent 148ef34a01
commit e165e029dc
3 changed files with 136 additions and 56 deletions

View File

@ -1,25 +1,27 @@
//! Demonstrates the cl_embed library
use cl_embed::*; use cl_embed::*;
use repline::{Response, prebaked}; use repline::{Response, prebaked};
fn main() -> Result<(), repline::Error> { 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| { 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<Response, EvalError> {
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)
}

View File

@ -7,7 +7,7 @@
pub use cl_interpret::{convalue::ConValue as Value, env::Environment}; 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_interpret::{convalue::ConValue, interpret::Interpret};
use cl_lexer::Lexer; use cl_lexer::Lexer;
use cl_parser::{Parser, error::Error as ParseError, inliner::ModuleInliner}; use cl_parser::{Parser, error::Error as ParseError, inliner::ModuleInliner};
@ -40,7 +40,7 @@ use std::{path::Path, sync::OnceLock};
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub macro conlang ( pub macro conlang(
$($t:tt)* $($t:tt)*
) {{ ) {{
// Parse once // Parse once
@ -49,11 +49,13 @@ pub macro conlang (
|env: &mut Environment| -> Result<ConValue, EvalError> { |env: &mut Environment| -> Result<ConValue, EvalError> {
FN.get_or_init(|| { FN.get_or_init(|| {
// TODO: embed the full module tree at compile time // TODO: embed the full module tree at compile time
let path = AsRef::<Path>::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"),"/../../", file!())).with_extension(""); let path =
AsRef::<Path>::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"), "/../../", file!()))
.with_extension("");
let mut mi = ModuleInliner::new(path); let mut mi = ModuleInliner::new(path);
let code = mi.fold_block( let code = mi.fold_block(
Parser::new( Parser::new(
concat!(file!(), ":", line!(), ":"), concat!(file!(), ":", line!(), ":", column!()),
Lexer::new(stringify!({ $($t)* })), Lexer::new(stringify!({ $($t)* })),
) )
.parse::<Block>()?, .parse::<Block>()?,
@ -75,6 +77,60 @@ pub macro conlang (
} }
}} }}
pub macro conlang_include{
($path:literal, $name:ident) => {
|env: &mut Environment| -> Result<ConValue, EvalError> {
// TODO: embed the full module tree at compile time
let path = AsRef::<Path>::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<ConValue, EvalError> {
// TODO: embed the full module tree at compile time
let path = AsRef::<Path>::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)] #[derive(Clone, Debug)]
pub enum EvalError { pub enum EvalError {
Parse(cl_parser::error::Error), Parse(cl_parser::error::Error),

View File

@ -1,37 +1,43 @@
#!/usr/bin/env -S conlang-run #!/usr/bin/env -S conlang-run
//! A simple five-function pn calculator //! A simple five-function pn calculator
// TODO: enum constructors in the interpreter
struct Atom(f64);
struct Op(char, [Expr]);
enum Expr { enum Expr {
Atom(f64), Atom(f64),
Op(char, [Expr]), Op(char, [Expr]),
} }
// Evaluates an expression /// executes an expression
fn eval(expr: Expr) -> isize { fn execute(expr: Expr) -> f64 {
match expr { match expr {
Atom(value) => value, Expr::Atom(value) => value,
Op('*', [lhs, rhs]) => eval(lhs) * eval(rhs), Expr::Op('*', [lhs, rhs]) => execute(lhs) * execute(rhs),
Op('/', [lhs, rhs]) => eval(lhs) / eval(rhs), Expr::Op('/', [lhs, rhs]) => execute(lhs) / execute(rhs),
Op('%', [lhs, rhs]) => eval(lhs) % eval(rhs), Expr::Op('%', [lhs, rhs]) => execute(lhs) % execute(rhs),
Op('+', [lhs, rhs]) => eval(lhs) + eval(rhs), Expr::Op('+', [lhs, rhs]) => execute(lhs) + execute(rhs),
Op('-', [lhs, rhs]) => eval(lhs) - eval(rhs), Expr::Op('-', [lhs, rhs]) => execute(lhs) - execute(rhs),
Op('-', [lhs]) => - eval(lhs), Expr::Op('-', [lhs]) => - execute(lhs),
Op(other, ..rest) => {
panic("ERROR: Unknown operator: " + other)
},
other => { other => {
println(other); panic("Unknown operation: " + fmt(other))
panic("ERROR: Unknown operation ^")
} }
} }
} }
/// 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 /// Parses expressions
fn parse(line: [char], power: i32) -> (Expr, [char]) { fn parse(line: [char], power: i32) -> (Expr, [char]) {
fn map((expr, line): (Expr, [char]), f: fn(Expr) -> Expr) -> (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 { let (lhs, line) = match line {
['0'..='9', ..] => number(line), ['0'..='9', ..] => number(line),
[op, ..rest] => { ['(', ..rest] => match parse(rest, Power::None) {
parse(rest, pre_bp(op)).map(|lhs| Op(op, [lhs])) (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"), _ => panic("Unexpected end of input"),
}; };
@ -53,7 +61,7 @@ fn parse(line: [char], power: i32) -> (Expr, [char]) {
if before < power { if before < power {
break; 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) (lhs, line)
} }
@ -61,35 +69,49 @@ fn parse(line: [char], power: i32) -> (Expr, [char]) {
fn number(line: [char]) -> (Expr, [char]) { fn number(line: [char]) -> (Expr, [char]) {
let value = 0.0; let value = 0.0;
while (let [first, ..rest] = line) && (let '0'..='9' = first) { while (let [first, ..rest] = line) && (let '0'..='9' = first) {
value = value * 10.0 + (first as f64 - '0' as f64); (value, line) = (value * 10.0 + (first as f64 - '0' as f64), rest)
line = rest; } else (Expr::Atom(value), line)
};
(Atom(value), line)
} }
fn space(line: [char]) -> [char] { fn space(line: [char]) -> [char] {
match line { match line {
[' ', ..rest] => space(rest), [' ', ..rest] => space(rest),
['\n', ..rest] => space(rest),
line => line line => line
} }
} }
enum Power {
None,
Factor,
Term,
Exponent,
Unary,
}
fn inf_bp(op: char) -> (i32, i32) { fn inf_bp(op: char) -> (i32, i32) {
(|x| (2 * x, 2 * x + 1))( (|x| (2 * x, 2 * x + 1))(
match op { match op {
'*' => 2, '*' => Power::Term,
'/' => 2, '/' => Power::Term,
'%' => 2, '%' => Power::Term,
'+' => 1, '+' => Power::Factor,
'-' => 1, '-' => Power::Factor,
_ => -1, _ => panic("Unknown operation: " + op),
}) })
} }
fn pre_bp(op: char) -> i32 { fn pre_bp(op: char) -> i32 {
(|x| 2 * x + 1)( (|x| 2 * x + 1)(
match op { match op {
'-' => 9, '-' => Power::Unary,
_ => -1, _ => panic("Unknown unary operator: " + op),
}) })
} }
fn my_eval(input: str) {
let (expr, rest) = input.chars().parse(0);
println(expr, " ", rest);
execute(expr)
}