cl_embed/
lib.rs

1//! Embed Conlang code into your Rust project!
2//!
3//! # This crate is experimental, and has no guarantees of stability.
4#![feature(decl_macro)]
5#![cfg_attr(test, feature(assert_matches))]
6#![allow(unused_imports)]
7
8pub use cl_interpret::{convalue::ConValue as Value, env::Environment};
9
10use cl_ast::{Block, Module, ast_visitor::Fold};
11use cl_interpret::{convalue::ConValue, interpret::Interpret};
12use cl_lexer::Lexer;
13use cl_parser::{Parser, error::Error as ParseError, inliner::ModuleInliner};
14use std::{path::Path, sync::OnceLock};
15
16/// Constructs a function which evaluates a Conlang Block
17///
18/// # Examples
19///
20/// Bind and use a variable
21/// ```rust
22/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
23/// use cl_embed::{conlang, Environment, Value};
24///
25/// let mut env = Environment::new();
26///
27/// // Bind a variable named `message` to "Hello, world!"
28/// env.bind("message", "Hello, World!");
29///
30/// let print_hello = conlang!{
31///     println(message);
32/// };
33///
34/// // Run the function
35/// let ret = print_hello(&mut env)?;
36///
37/// // `println` returns Empty
38/// assert!(matches!(ret, Value::Empty));
39///
40/// # Ok(())
41/// # }
42/// ```
43pub macro conlang (
44    $($t:tt)*
45) {{
46    // Parse once
47    static FN: OnceLock<Result<Block, ParseError>> = OnceLock::new();
48
49    |env: &mut Environment| -> Result<ConValue, EvalError> {
50        FN.get_or_init(|| {
51            // TODO: embed the full module tree at compile time
52            let path = AsRef::<Path>::as_ref(&concat!(env!("CARGO_MANIFEST_DIR"),"/../../", file!())).with_extension("");
53            let mut mi = ModuleInliner::new(path);
54            let code = mi.fold_block(
55                Parser::new(
56                    concat!(file!(), ":", line!(), ":"),
57                    Lexer::new(stringify!({ $($t)* })),
58                )
59                .parse::<Block>()?,
60            );
61            if let Some((ie, pe)) = mi.into_errs() {
62                for (file, err) in ie {
63                    eprintln!("{}: {err}", file.display());
64                }
65                for (file, err) in pe {
66                    eprintln!("{}: {err}", file.display());
67                }
68            }
69            Ok(code)
70        })
71        .as_ref()
72        .map_err(Clone::clone)?
73        .interpret(env)
74        .map_err(Into::into)
75    }
76}}
77
78#[derive(Clone, Debug)]
79pub enum EvalError {
80    Parse(cl_parser::error::Error),
81    Interpret(cl_interpret::error::Error),
82}
83
84impl From<cl_parser::error::Error> for EvalError {
85    fn from(value: cl_parser::error::Error) -> Self {
86        Self::Parse(value)
87    }
88}
89impl From<cl_interpret::error::Error> for EvalError {
90    fn from(value: cl_interpret::error::Error) -> Self {
91        Self::Interpret(value)
92    }
93}
94impl std::error::Error for EvalError {}
95impl std::fmt::Display for EvalError {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            EvalError::Parse(error) => error.fmt(f),
99            EvalError::Interpret(error) => error.fmt(f),
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use std::assert_matches::assert_matches;
107
108    use super::*;
109
110    #[test]
111    fn it_works() -> Result<(), EvalError> {
112        let mut env = Environment::new();
113
114        let result = conlang! {
115            fn add(left, right) -> isize {
116                left + right
117            }
118
119            add(2, 2)
120        }(&mut env);
121
122        assert_matches!(result, Ok(Value::Int(4)));
123
124        Ok(())
125    }
126}