cl_embed: Goodbye, cl_embed. You will be missed.
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
members = [
|
||||
"compiler/cl-repl",
|
||||
"compiler/cl-typeck",
|
||||
"compiler/cl-embed",
|
||||
"compiler/cl-interpret",
|
||||
"compiler/cl-structures",
|
||||
"compiler/cl-token",
|
||||
@@ -14,7 +13,7 @@ resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
repository = "https://git.soft.fish/j/Conlang"
|
||||
version = "0.0.10"
|
||||
version = "0.0.11"
|
||||
authors = ["John Breaux <j@soft.fish>"]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
[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 = { version = "*", registry = "soft-fish" }
|
||||
@@ -1,27 +0,0 @@
|
||||
//! 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| {
|
||||
env.bind("line", line);
|
||||
|
||||
let res = conlang! {
|
||||
|
||||
let (expr, rest) = parse(line.chars(), Power::None);
|
||||
execute(expr)
|
||||
|
||||
}(&mut env)?;
|
||||
|
||||
println!("{res}");
|
||||
|
||||
Ok(Response::Accept)
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../../../sample-code/calculator.cl
|
||||
@@ -1,182 +0,0 @@
|
||||
//! 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, 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};
|
||||
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!(), ":", column!()),
|
||||
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)
|
||||
}
|
||||
}}
|
||||
|
||||
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)]
|
||||
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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user