cl-embed: Calculator example update!
This commit is contained in:
parent
148ef34a01
commit
e165e029dc
@ -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)
|
|
||||||
}
|
|
||||||
|
@ -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),
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user