Compare commits

...

4 Commits

Author SHA1 Message Date
bog 55239ba7c0 build environment (create build_dir/checksum.txt). 2024-03-08 12:21:56 +01:00
bog 8420c7cce5 file literal. 2024-03-08 04:27:03 +01:00
bog 586676b519 instruction executor. 2024-03-07 17:55:40 +01:00
bog b72614afaf expression evaluation. 2024-03-06 08:08:56 +01:00
15 changed files with 1397 additions and 31 deletions

285
Cargo.lock generated
View File

@ -2,6 +2,291 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "alchimake"
version = "0.1.0"
dependencies = [
"regex",
"sha256",
]
[[package]]
name = "async-trait"
version = "0.1.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cc"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cpufeatures"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha256"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0"
dependencies = [
"async-trait",
"bytes",
"hex",
"sha2",
"tokio",
]
[[package]]
name = "syn"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
"pin-project-lite",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"

View File

@ -6,3 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.10.3"
sha256 = "1.5.0"

63
src/app.rs Normal file
View File

@ -0,0 +1,63 @@
use std::path::PathBuf;
use crate::cli::parse;
use crate::core::conf::Conf;
use crate::core::build_env::BuildEnv;
pub struct App {
conf: Conf
}
impl App {
pub fn new() -> Self {
Self {
conf: Conf::new()
}
}
pub fn init(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let mut args = parse::Args::new();
let sys_args: Vec<String> = std::env::args().collect();
args.register(&vec!["-h", "--help"])
.description("show this help")
.callback(Box::new(|_, ctx| {
println!("{}", ctx.help);
std::process::exit(0);
}));
args.register(&vec!["-v", "--version"])
.description("show alchimake version")
.callback(Box::new(|_, _| {
println!("Alchimake v0.0.0");
println!("Copyright (C) 2024 bog");
println!("Licensed under the GPLv3+ (see LICENSE)");
std::process::exit(0);
}));
args.register(&vec!["-s", "--source"])
.description("set the source directory")
.callback(Box::new(|value, _| {
if let Some(val) = value {
self.conf.src_dir = PathBuf::from(val);
}
}));
args.register(&vec!["-b", "--build"])
.description("set the build directory")
.callback(Box::new(|value, _| {
if let Some(val) = value {
self.conf.build_dir = PathBuf::from(val);
}
}));
args.parse(&sys_args.iter().map(|v| v.as_str()).collect())?;
Ok(())
}
pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let mut benv = BuildEnv::new(&self.conf);
benv.init_build_dir()?;
Ok(())
}
}

View File

@ -23,19 +23,19 @@ pub struct Context {
pub help: String
}
type Callback = Box<dyn Fn(Option<String>, Context)>;
type Callback<'a> = Box<dyn FnMut(Option<String>, Context) + 'a>;
pub struct Arg {
pub struct Arg<'a> {
pub flags: Vec<String>,
pub description: Option<String>,
pub callback: Option<Callback>
pub callback: Option<Callback<'a>>
}
pub struct Args {
args: Vec<Arg>,
pub struct Args<'a> {
args: Vec<Arg<'a>>,
}
impl Args {
impl<'a> Args<'a> {
pub fn new() -> Self {
Self {
args: vec![]
@ -45,6 +45,7 @@ impl Args {
pub fn help(&self) -> String {
let mut res = String::from("Usage: alchimake [OPTION]...\n");
res += "OPTIONS:\n";
for arg in self.args.iter() {
if let Some(description) = &arg.description {
res += format!(
@ -67,7 +68,7 @@ impl Args {
for arg in self.args.iter_mut() {
for flag in arg.flags.iter() {
if &flag == cli_arg {
if let Some(ref callback) = arg.callback {
if let Some(ref mut callback) = arg.callback {
let value = getval(&args, &flag)?;
callback(value.clone(), Context {
help: help.clone()
@ -99,9 +100,16 @@ impl Args {
}
pub fn callback(&mut self,
callback: Callback) -> &mut Self{
callback: Callback<'a>) -> &mut Self{
let idx = self.args.len() - 1;
self.args[idx].callback = Some(Box::new(callback));
self.args.sort_by_cached_key(|a| {
if let Some(val) = a.flags.get(0) {
val.clone()
} else {
String::from("")
}
});
self
}
}

131
src/core/build_env.rs Normal file
View File

@ -0,0 +1,131 @@
use std::error::Error;
use std::io::Read;
use std::path::{Path, PathBuf};
use crate::core::conf::Conf;
#[derive(Debug)]
struct FileInfo {
pub path: PathBuf,
pub sha: String
}
pub struct BuildEnv {
conf: Conf,
files: Vec<FileInfo>,
checksum_file_path: PathBuf
}
impl BuildEnv {
pub fn new(conf: &Conf) -> Self {
Self {
conf: conf.clone(),
files: vec![],
checksum_file_path: conf.build_dir.join("checksum.txt")
}
}
pub fn init_build_dir(&mut self) -> Result<(), Box<dyn Error>>{
if !self.conf.build_dir.as_path().exists() {
std::fs::create_dir(&self.conf.build_dir)?;
}
if !self.checksum_file_path.as_path().exists() {
std::fs::write(&self.checksum_file_path, b"")?;
}
self.load_checksum()?;
Ok(())
}
pub fn is_up_to_date(&mut self, file_path: &Path) -> Result<bool, Box<dyn Error>> {
let mut f = std::fs::File::open(file_path)?;
let mut content = String::from("");
f.read_to_string(&mut content)?;
let sha: String = sha256::digest(content);
if let Some(idx) = self.files.iter().position(|x| {
x.path.canonicalize().unwrap()
== file_path.canonicalize().unwrap()
}) {
return Ok(self.files.get(idx).unwrap().sha == sha);
}
Ok(false)
}
pub fn update(&mut self, file_path: &Path) -> Result <(), Box<dyn Error>>{
let mut f = std::fs::File::open(file_path)?;
let mut content = String::from("");
f.read_to_string(&mut content)?;
let sha = sha256::digest(content);
if let Some(idx) = self.files.iter().position(|x| {
x.path.canonicalize().unwrap()
== file_path.canonicalize().unwrap()
}) {
self.files.get_mut(idx).unwrap().sha = sha;
} else {
self.files.push(FileInfo {
path: PathBuf::from(file_path),
sha
});
}
self.save_checksum()?;
Ok(())
}
pub fn load_checksum(&mut self) -> Result<(), Box<dyn Error>>{
let mut file = std::fs::File::open(
self.checksum_file_path.as_path()
)?;
let mut content = String::from("");
file.read_to_string(&mut content)?;
self.files.clear();
for line in content.split('\n') {
if line.len() == 0 {
continue;
}
let info: Vec<String> = line.split(' ').map(
|s| s.to_string()
).collect();
let f = FileInfo {
path: PathBuf::from(info.get(0).unwrap()),
sha: info.get(1).unwrap().to_owned()
};
self.files.push(f);
}
Ok(())
}
pub fn save_checksum(&self) -> Result<(), Box<dyn Error>> {
let mut content = String::from("");
for file in self.files.iter() {
content += format!("{} {}",
file.path.canonicalize()?.to_str().unwrap(),
file.sha
).as_str();
}
std::fs::write(
self.checksum_file_path.as_path(),
content.as_bytes()
)?;
Ok(())
}
}
#[cfg(test)]
mod test {
}

15
src/core/cmd.rs Normal file
View File

@ -0,0 +1,15 @@
use crate::core::value::Value;
use crate::core::expr::Expr;
pub trait Command {
fn params(&self) -> Vec<Expr> { vec![] }
fn requires(&self) -> Vec<Expr> { vec![] }
fn provides(&self) -> Vec<Expr> { vec![] }
fn call(&mut self, args: Vec<Value>) -> Value;
fn clone_box(&self) -> Box<dyn Command>;
}
#[cfg(test)]
mod test {
use super::*;
}

18
src/core/conf.rs Normal file
View File

@ -0,0 +1,18 @@
use std::path::PathBuf;
#[derive(Debug)]
#[derive(Clone)]
pub struct Conf {
pub src_dir: PathBuf,
pub build_dir: PathBuf
}
impl Conf {
pub fn new() -> Self {
Self {
src_dir: PathBuf::from("."),
build_dir: PathBuf::from(".")
}
}
}

268
src/core/eval.rs Normal file
View File

@ -0,0 +1,268 @@
use regex::Regex;
use std::collections::HashMap;
use std::error::Error;
use crate::core::{
value::Value,
expr::Expr,
sym::Sym,
cmd::Command,
conf::Conf
};
pub struct Evaluator {
sym: Sym,
commands: HashMap<String, Box<dyn Command>>,
conf: Conf
}
impl Evaluator {
pub fn new(sym: Sym, conf: Conf) -> Self {
Self {
sym,
commands: HashMap::new(),
conf
}
}
pub fn run(&mut self, expr: &Expr) -> Result<Value, Box<dyn Error>> {
match expr {
Expr::File(rel_path) => {
fn replace_path(dir: &std::path::Path, prefix: &str, rel_path: &str)
-> Result<Value, Box<dyn Error>> {
let re = Regex::new(&format!("{}://([^)]*)", prefix)).unwrap();
if let Some(path) = re.captures(rel_path) {
Ok(Value::String(
dir.join(
path[1].to_string()
).to_str().unwrap().to_string(),
))
} else {
Err(String::from("wrong path").into())
}
}
if let Ok(res) = replace_path(&self.conf.src_dir, "src", rel_path) {
Ok(res)
} else if let Ok(res) = replace_path(&self.conf.build_dir, "build", rel_path) {
Ok(res)
} else {
Err(format!("wrong path {}", rel_path).into())
}
},
Expr::BuiltIn(value) => Ok(value.clone()),
Expr::Ident(value) => {
if let Some(val) = self.sym.get(value) {
Ok(val)
} else {
Err(format!("'{}' is not declared", value).into())
}
},
Expr::Call(value, args) => {
let mut val_args: Vec<Value> = vec![];
for arg in args {
val_args.push(self.run(arg)?);
}
let mut name = String::from("");
match self.sym.get(value) {
Some(Value::Command(n)) => {name = n;}
None => {
return Err(
format!(
"command '{}' is not declared",
value).into()
);
},
_ => {
return Err(
format!(
"'{}' is not a command",
value).into()
);
}
}
match self.commands.get_mut(&name) {
Some(cmd) => {
Ok(cmd.call(val_args))
},
None => Err(
format!(
"command '{}' is not declared",
value).into()
)
}
},
_ => Err(format!("unknown expr {:?}", expr).into())
}
}
pub fn new_command(&mut self, name: &str, cmd: Box<dyn Command>) {
self.commands.insert(name.to_string(), cmd);
}
pub fn clone_sym(&self) -> Sym {
self.sym.clone()
}
pub fn getsym<U>(&mut self, callback: U)
where U: Fn(&mut Sym) {
callback(&mut self.sym);
}
}
impl Clone for Evaluator {
fn clone(&self) -> Self {
let mut c = Evaluator::new(self.sym.clone(), self.conf.clone());
for cmd in self.commands.iter() {
c.commands.insert(cmd.0.to_string(), cmd.1.clone_box());
}
c
}
}
mod test {
use super::*;
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn test_eval_init<U>(expr: Expr, value: Value, init: U)
-> TestResult
where U: Fn(&mut Evaluator) {
let sym = Sym::new();
let mut conf = Conf::new();
conf.src_dir = std::path::PathBuf::from("/root/source");
conf.build_dir = std::path::PathBuf::from("/root/build");
let mut eval = Evaluator::new(sym, conf);
init(&mut eval);
let res = eval.run(&expr)?;
assert_eq!(res, value);
Ok(())
}
fn test_eval(expr: Expr, value: Value) -> TestResult {
test_eval_init(expr, value, |_| {})
}
#[test]
fn builtins() -> TestResult {
test_eval(
Expr::BuiltIn(Value::Int(42)),
Value::Int(42)
)?;
test_eval(
Expr::BuiltIn(Value::String("hello".to_string())),
Value::String("hello".to_string())
)?;
test_eval(
Expr::BuiltIn(Value::Symbol("hello".to_string())),
Value::Symbol("hello".to_string()),
)?;
test_eval(
Expr::BuiltIn(Value::Array(vec![
Value::Bool(true),
Value::Float(3.2),
])),
Value::Array(vec![
Value::Bool(true),
Value::Float(3.2),
]),
)?;
Ok(())
}
#[test]
fn ident() -> TestResult {
test_eval_init(
Expr::Ident(String::from("X")),
Value::Symbol(String::from("hello")),
|eval| {
eval.getsym(|s| {
s.assign("X", Value::Symbol("hello".to_string()));
})
}
)?;
Ok(())
}
#[test]
fn command() -> TestResult {
#[derive(Clone)]
struct MockedCmd {
}
impl Command for MockedCmd {
fn call(&mut self, args: Vec<Value>) -> Value {
if let Some(Value::Int(val)) = args.get(0) {
Value::Int(val * 2)
} else {
Value::Int(0)
}
}
fn clone_box(&self) -> Box<dyn Command> {
Box::new(self.clone())
}
}
test_eval_init(
Expr::Call(String::from("X"), vec![Expr::BuiltIn(Value::Int(12))]),
Value::Int(24),
|eval| {
eval.new_command("CMD", Box::new(MockedCmd {}));
eval.getsym(|s| {
s.assign("X", Value::Command(String::from("CMD")));
})
}
)?;
Ok(())
}
#[test]
fn files() -> TestResult {
test_eval(
Expr::File(String::from("build://")),
Value::String(String::from("/root/build/"))
)?;
test_eval(
Expr::File(String::from("build://hello/world.txt")),
Value::String(String::from("/root/build/hello/world.txt"))
)?;
test_eval(
Expr::File(String::from("build://../parent")),
Value::String(String::from("/root/build/../parent"))
)?;
test_eval(
Expr::File(String::from("src://")),
Value::String(String::from("/root/source/"))
)?;
test_eval(
Expr::File(String::from("src://hello/world.txt")),
Value::String(String::from("/root/source/hello/world.txt"))
)?;
test_eval(
Expr::File(String::from("src://../parent")),
Value::String(String::from("/root/source/../parent"))
)?;
Ok(())
}
}

452
src/core/exec.rs Normal file
View File

@ -0,0 +1,452 @@
use std::error::Error;
use crate::core::{
instr::Instr,
expr::Expr,
value::Value,
eval::Evaluator,
sym::Sym,
cmd::Command,
conf::Conf
};
#[derive(Clone)]
struct UserCmd {
pub pams: Vec<Expr>,
pub reqs: Vec<Expr>,
pub provs: Vec<Expr>,
pub body: Vec<Instr>,
pub eval: Evaluator
}
impl Command for UserCmd {
fn params(&self) -> Vec<Expr> {
self.pams.clone()
}
fn requires(&self) -> Vec<Expr> {
self.reqs.clone()
}
fn provides(&self) -> Vec<Expr> {
self.provs.clone()
}
fn call(&mut self, args: Vec<Value>) -> Value {
let mut exec = Executor::new(self.eval.clone());
let pams = self.params();
for (i, arg) in args.iter().enumerate() {
let Expr::Ident(ref name) = pams[i] else {
return Value::Nil;
};
exec.getsym(move |s| {
s.assign(name, arg.clone());
});
}
let mut result = Value::Nil;
for instr in self.body.iter() {
match exec.run(&instr) {
Ok(value) => { result = value; }
Err(err) => { println!("=> {:?}", err); }
}
}
result
}
fn clone_box(&self) -> Box<dyn Command> {
Box::new(self.clone())
}
}
pub struct Executor {
eval: Evaluator
}
impl Executor {
pub fn new(eval: Evaluator) -> Self {
Self {
eval
}
}
pub fn getsym<U>(&mut self, callback: U)
where U: Fn(&mut Sym) {
self.eval.getsym(|s| {
callback(s);
})
}
pub fn run(&mut self, instr: &Instr)
-> Result<Value, Box<dyn Error>> {
match instr {
Instr::Module(instrs) => {
let mut res: Value = Value::Nil;
for instr in instrs {
res = self.run(instr)?;
}
Ok(res)
},
// ignoring tasks
Instr::Task(_, _, _) => { Ok(Value::Nil) },
Instr::Expr(expr) => {
let value = self.eval.run(&expr)?;
Ok(value)
},
Instr::Assign(name, exprs) => {
let mut values: Vec<Value> = vec![];
for expr in exprs {
values.push(self.eval.run(expr)?);
}
self.eval.getsym(|s| {
if values.len() == 1 {
s.assign(name, values[0].clone());
} else {
s.assign(name, Value::Array(values.iter().cloned().collect()));
}
});
Ok(Value::Nil)
},
Instr::AssignIf(name, exprs) => {
let mut values: Vec<Value> = vec![];
for expr in exprs {
values.push(self.eval.run(expr)?);
}
self.eval.getsym(|s| {
if values.len() == 1 {
s.assign_if(name, values[0].clone());
} else {
s.assign_if(name, Value::Array(values.iter().cloned().collect()));
}
});
Ok(Value::Nil)
},
Instr::AssignArray(name, exprs) => {
let mut values: Vec<Value> = vec![];
for expr in exprs {
values.push(self.eval.run(expr)?);
}
self.eval.getsym(|s| {
if values.len() == 1 {
s.assign_array(name, values[0].clone());
} else {
for val in values.iter() {
s.assign_array(name, val.clone());
}
}
});
Ok(Value::Nil)
},
Instr::Command(name, params,
requires, provides, body) => {
let cmd = UserCmd {
pams: params.clone(),
reqs: requires.clone(),
provs: provides.clone(),
body: body.to_vec(),
eval: self.eval.clone()
};
self.eval.new_command(name, Box::new(cmd));
self.getsym(|s| {
s.assign(name, Value::Command(
name.clone()
));
});
Ok(Value::Nil)
},
_ => {
Err(format!(
"cannot execute unknown instruction: {:?}",
instr
).into())
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
type TestResult = Result<(), Box<dyn Error>>;
fn test_exec<T>(instr: Instr, oracle: Value, init: T)
-> TestResult
where T: Fn(&mut Executor) {
let sym = Sym::new();
let eval = Evaluator::new(sym, Conf::new());
let mut exec = Executor::new(eval);
init(&mut exec);
let value = exec.run(
&instr
)?;
assert_eq!(value, oracle);
Ok(())
}
#[test]
fn expr() -> TestResult {
test_exec(
Instr::Module(vec![
Instr::Expr(Expr::BuiltIn(Value::Int(74)))
]),
Value::Int(74),
|_| {}
)?;
test_exec(
Instr::Module(vec![
Instr::Expr(Expr::Ident(String::from("Y")))
]),
Value::Float(22.5),
|e| { e.getsym(|s|{
s.assign("Y", Value::Float(22.5));
}); }
)?;
Ok(())
}
#[test]
fn assign() -> TestResult {
// Assign
// ======
test_exec(
Instr::Module(vec![
Instr::Assign(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bim".to_string())),
Expr::BuiltIn(Value::Int(12)),
Expr::BuiltIn(Value::Float(3.4))
]
),
Instr::Expr(Expr::Ident(String::from("hello")))
]),
Value::Array(vec![
Value::String("bim".to_string()),
Value::Int(12),
Value::Float(3.4)
]),
|_| {}
)?;
test_exec(
Instr::Module(vec![
Instr::Assign(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bim".to_string()))
]
),
Instr::Assign(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bam".to_string()))
]
),
Instr::Expr(Expr::Ident(String::from("hello")))
]),
Value::String("bam".to_string()),
|_| {}
)?;
test_exec(
Instr::Module(vec![
Instr::Assign(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bim".to_string()))
]
),
Instr::Expr(Expr::Ident(String::from("hello")))
]),
Value::String("bim".to_string()),
|_| {}
)?;
// Assign If
// =========
test_exec(
Instr::Module(vec![
Instr::AssignIf(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bim".to_string()))
]
),
Instr::Expr(Expr::Ident(String::from("hello")))
]),
Value::String("bim".to_string()),
|_| {}
)?;
test_exec(
Instr::Module(vec![
Instr::AssignIf(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bim".to_string()))
]
),
Instr::AssignIf(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bam".to_string()))
]
),
Instr::Expr(Expr::Ident(String::from("hello")))
]),
Value::String("bim".to_string()),
|_| {}
)?;
// Assign Array
// ============
test_exec(
Instr::Module(vec![
Instr::Assign(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bim".to_string())),
]
),
Instr::AssignArray(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("Pizza".to_string()))
]
),
Instr::Expr(Expr::Ident(String::from("hello")))
]),
Value::Array(vec![
Value::String("bim".to_string()),
Value::String("Pizza".to_string()),
]),
|_| {}
)?;
test_exec(
Instr::Module(vec![
Instr::AssignArray(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bim".to_string())),
Expr::BuiltIn(Value::String("bam".to_string())),
Expr::BuiltIn(Value::String("boom".to_string()))
]
),
Instr::AssignArray(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("I".to_string())),
Expr::BuiltIn(Value::String("Love".to_string())),
Expr::BuiltIn(Value::String("Pizza".to_string()))
]
),
Instr::Expr(Expr::Ident(String::from("hello")))
]),
Value::Array(vec![
Value::String("bim".to_string()),
Value::String("bam".to_string()),
Value::String("boom".to_string()),
Value::String("I".to_string()),
Value::String("Love".to_string()),
Value::String("Pizza".to_string()),
]),
|_| {}
)?;
test_exec(
Instr::Module(vec![
Instr::AssignArray(
String::from("hello"),
vec![
Expr::BuiltIn(Value::String("bim".to_string()))
]
),
Instr::Expr(Expr::Ident(String::from("hello")))
]),
Value::Array(vec![Value::String("bim".to_string())]),
|_| {}
)?;
Ok(())
}
#[test]
fn command() -> TestResult {
#[derive(Clone)]
struct AddIntCmd {}
impl Command for AddIntCmd {
fn call(&mut self, args: Vec<Value>) -> Value {
let mut res: i32 = 0;
for arg in args {
if let Value::Int(val) = arg {
res += val;
}
}
Value::Int(res)
}
fn clone_box(&self) -> Box<dyn Command> {
Box::new(self.clone())
}
}
test_exec(
Instr::Module(vec![
Instr::Command(
String::from("sum"), // name
vec![
Expr::Ident(String::from("x")),
Expr::Ident(String::from("y")),
],// args
vec![],// requires
vec![],// provides
vec![
Instr::Expr(
Expr::Call(String::from("ADD"), vec![
Expr::Ident(String::from("x")),
Expr::Ident(String::from("y"))
])
)
]
),
Instr::Expr(Expr::Call(String::from("sum"), vec![
Expr::BuiltIn(Value::Int(32)),
Expr::BuiltIn(Value::Int(16)),
]))
]),
Value::Int(48),
|exec| {
exec.eval.new_command(
"ADD",
Box::new(AddIntCmd {})
);
exec.getsym(|s| {
s.assign("ADD", Value::Command(String::from("ADD")))
})
}
)?;
Ok(())
}
}

11
src/core/expr.rs Normal file
View File

@ -0,0 +1,11 @@
use crate::core::value::Value;
#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Clone)]
pub enum Expr {
BuiltIn(Value),
Ident(String),
Call(String, Vec<Expr>),
File(String)
}

14
src/core/instr.rs Normal file
View File

@ -0,0 +1,14 @@
use crate::core::expr::Expr;
#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Clone)]
pub enum Instr {
Module(Vec<Instr>),
Expr(Expr),
Assign(String, Vec<Expr>),
AssignIf(String, Vec<Expr>),
AssignArray(String, Vec<Expr>),
Command(String, Vec<Expr>, Vec<Expr>, Vec<Expr>, Vec<Instr>),
Task(String, Vec<Expr>, Vec<Instr>)
}

9
src/core/mod.rs Normal file
View File

@ -0,0 +1,9 @@
pub mod expr;
pub mod instr;
pub mod value;
pub mod eval;
pub mod sym;
pub mod cmd;
pub mod exec;
pub mod conf;
pub mod build_env;

92
src/core/sym.rs Normal file
View File

@ -0,0 +1,92 @@
use std::collections::HashMap;
use crate::core::value::Value;
#[derive(Clone, Debug)]
pub struct Sym {
table: HashMap<String, Value>
}
impl Sym {
pub fn new() -> Self {
Self {
table: HashMap::new()
}
}
pub fn get(&self, identifier: &str) -> Option<Value> {
self.table.get(identifier).cloned()
}
pub fn assign(&mut self, identifier: &str, value: Value) {
self.table.insert(identifier.to_string(), value);
}
pub fn assign_array(&mut self, identifier: &str, value: Value) {
match self.table.get(identifier) {
Some(Value::Array(arr)) => {
let mut new_arr: Vec<Value> = vec![];
new_arr.extend(arr.clone());
new_arr.push(value.clone());
self.table.insert(identifier.to_string(), Value::Array(new_arr));
},
Some(val) => {
let mut new_arr: Vec<Value> = vec![val.clone()];
new_arr.push(value.clone());
self.table.insert(identifier.to_string(), Value::Array(new_arr));
},
None => {
self.table.insert(identifier.to_string(), Value::Array(vec![
value.clone()
]));
}
}
}
pub fn assign_if(&mut self, identifier: &str, value: Value) {
if self.table.get(identifier).is_none() {
self.table.insert(identifier.to_string(), value);
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn assign() {
let mut sym = Sym::new();
assert!(sym.get("X").is_none());
sym.assign("X", Value::Int(34));
assert_eq!(sym.get("X"), Some(Value::Int(34)));
}
#[test]
fn assign_array() {
let mut sym = Sym::new();
assert!(sym.get("X").is_none());
sym.assign_array("X", Value::Float(6.28));
assert_eq!(sym.get("X"), Some(Value::Array(vec![
Value::Float(6.28),
])));
sym.assign_array("X", Value::Bool(false));
assert_eq!(sym.get("X"), Some(Value::Array(vec![
Value::Float(6.28),
Value::Bool(false)
])));
}
#[test]
fn assign_if() {
let mut sym = Sym::new();
assert!(sym.get("X").is_none());
sym.assign_if("X", Value::Float(6.28));
assert_eq!(sym.get("X"), Some(Value::Float(6.28)));
sym.assign_if("X", Value::Int(79));
assert_eq!(sym.get("X"), Some(Value::Float(6.28)));
}
}

15
src/core/value.rs Normal file
View File

@ -0,0 +1,15 @@
use crate::core::cmd::Command;
#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Clone)]
pub enum Value {
Nil,
Int(i32),
Float(f64),
Bool(bool),
String(String),
Symbol(String),
Array(Vec<Value>),
Command(String)
}

View File

@ -1,28 +1,11 @@
mod cli;
mod core;
mod app;
use crate::cli::parse;
use crate::app::App;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = parse::Args::new();
let sys_args: Vec<String> = std::env::args().collect();
args.register(&vec!["-h", "--help"])
.description("show this help")
.callback(Box::new(|_, ctx| {
println!("{}", ctx.help);
std::process::exit(0);
}));
args.register(&vec!["-v", "--version"])
.description("show alchimake version")
.callback(Box::new(|_, _| {
println!("Alchimake v0.0.0");
println!("Copyright (C) 2024 bog");
println!("Licensed under the GPLv3+ (see LICENSE)");
std::process::exit(0);
}));
args.parse(&sys_args.iter().map(|v| v.as_str()).collect())?;
Ok(())
let mut app = App::new();
app.init()?;
app.start()
}