cl-embed: Create an (unstable) API for embedding Conlang in your projects!
This commit is contained in:
parent
233e4dab4e
commit
6ce27c522f
@ -2,6 +2,7 @@
|
||||
members = [
|
||||
"compiler/cl-repl",
|
||||
"compiler/cl-typeck",
|
||||
"compiler/cl-embed",
|
||||
"compiler/cl-interpret",
|
||||
"compiler/cl-structures",
|
||||
"compiler/cl-token",
|
||||
|
18
compiler/cl-embed/Cargo.toml
Normal file
18
compiler/cl-embed/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "cl-embed"
|
||||
version = "0.1.0"
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cl-interpret = { path = "../cl-interpret" }
|
||||
cl-ast = { path = "../cl-ast" }
|
||||
cl-structures = { path = "../cl-structures" }
|
||||
cl-lexer = { path = "../cl-lexer" }
|
||||
cl-parser = { path = "../cl-parser" }
|
||||
|
||||
[dev-dependencies]
|
||||
repline = { path = "../../repline" }
|
126
compiler/cl-embed/src/lib.rs
Normal file
126
compiler/cl-embed/src/lib.rs
Normal file
@ -0,0 +1,126 @@
|
||||
//! Embed Conlang code into your Rust project!
|
||||
//!
|
||||
//! # This crate is experimental, and has no guarantees of stability.
|
||||
#![feature(decl_macro)]
|
||||
#![cfg_attr(test, feature(assert_matches))]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
pub use cl_interpret::{convalue::ConValue as Value, env::Environment};
|
||||
|
||||
use cl_ast::{Block, 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};
|
||||
use std::{path::Path, sync::OnceLock};
|
||||
|
||||
/// Constructs a function which evaluates a Conlang Block
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Bind and use a variable
|
||||
/// ```rust
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use cl_embed::{conlang, Environment, Value};
|
||||
///
|
||||
/// let mut env = Environment::new();
|
||||
///
|
||||
/// // Bind a variable named `message` to "Hello, world!"
|
||||
/// env.bind("message", "Hello, World!");
|
||||
///
|
||||
/// let print_hello = conlang!{
|
||||
/// println(message);
|
||||
/// };
|
||||
///
|
||||
/// // Run the function
|
||||
/// let ret = print_hello(&mut env)?;
|
||||
///
|
||||
/// // `println` returns Empty
|
||||
/// assert!(matches!(ret, Value::Empty));
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub macro conlang (
|
||||
$($t:tt)*
|
||||
) {{
|
||||
// Parse once
|
||||
static FN: OnceLock<Result<Block, ParseError>> = OnceLock::new();
|
||||
|
||||
|env: &mut Environment| -> Result<ConValue, EvalError> {
|
||||
FN.get_or_init(|| {
|
||||
// TODO: embed the full module tree at compile time
|
||||
let path = AsRef::<Path>::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!(), ":"),
|
||||
Lexer::new(stringify!({ $($t)* })),
|
||||
)
|
||||
.parse::<Block>()?,
|
||||
);
|
||||
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());
|
||||
}
|
||||
}
|
||||
Ok(code)
|
||||
})
|
||||
.as_ref()
|
||||
.map_err(Clone::clone)?
|
||||
.interpret(env)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EvalError {
|
||||
Parse(cl_parser::error::Error),
|
||||
Interpret(cl_interpret::error::Error),
|
||||
}
|
||||
|
||||
impl From<cl_parser::error::Error> for EvalError {
|
||||
fn from(value: cl_parser::error::Error) -> Self {
|
||||
Self::Parse(value)
|
||||
}
|
||||
}
|
||||
impl From<cl_interpret::error::Error> for EvalError {
|
||||
fn from(value: cl_interpret::error::Error) -> Self {
|
||||
Self::Interpret(value)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for EvalError {}
|
||||
impl std::fmt::Display for EvalError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EvalError::Parse(error) => error.fmt(f),
|
||||
EvalError::Interpret(error) => error.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() -> Result<(), EvalError> {
|
||||
let mut env = Environment::new();
|
||||
|
||||
let result = conlang! {
|
||||
fn add(left, right) -> isize {
|
||||
left + right
|
||||
}
|
||||
|
||||
add(2, 2)
|
||||
}(&mut env);
|
||||
|
||||
assert_matches!(result, Ok(Value::Int(4)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user