split cli
This commit is contained in:
parent
4af71fb0ee
commit
61d0adb25b
6 changed files with 590 additions and 430 deletions
120
Cargo.lock
generated
120
Cargo.lock
generated
|
@ -11,6 +11,55 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.93"
|
||||
|
@ -22,6 +71,7 @@ name = "aoclang"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"codespan-reporting",
|
||||
"derive_more",
|
||||
"lalrpop",
|
||||
|
@ -80,6 +130,46 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
|
@ -90,6 +180,12 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.16"
|
||||
|
@ -183,6 +279,12 @@ version = "0.15.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
|
@ -202,6 +304,12 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
|
@ -458,6 +566,12 @@ dependencies = [
|
|||
"precomputed-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.90"
|
||||
|
@ -512,6 +626,12 @@ version = "0.2.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
|
|
@ -5,6 +5,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1.0.93"
|
||||
clap = { version = "4.5.21", features = ["derive"] }
|
||||
codespan-reporting = "0.11.1"
|
||||
derive_more = { version = "1.0.0", features = ["debug"] }
|
||||
lalrpop-util = { version = "0.22.0", features = ["lexer"] }
|
||||
|
|
1
src/compiler.rs
Normal file
1
src/compiler.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -6,13 +6,13 @@ pub Program = StmtSemi*;
|
|||
|
||||
StmtSemi: Stmt = <s:Stmt> ";" => s;
|
||||
|
||||
Stmt: Stmt = {
|
||||
pub Stmt: Stmt = {
|
||||
"input" <n:Ident> "=" <s:String> => Stmt::Input(n, s),
|
||||
"let" <n:Ident> "=" <e:Expr> => Stmt::Let(n, e),
|
||||
"print" "(" <e:Expr> ")" => Stmt::Print(e),
|
||||
};
|
||||
|
||||
Expr: Expr = {
|
||||
pub Expr: Expr = {
|
||||
#[precedence(level = "0")]
|
||||
<i:Ident> => Expr::Ident(i),
|
||||
<n:Num> => Expr::Int(n),
|
||||
|
|
399
src/interpreter.rs
Normal file
399
src/interpreter.rs
Normal file
|
@ -0,0 +1,399 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use once_cell::unsync::OnceCell;
|
||||
|
||||
use crate::ast::{Expr, Op, Stmt, Value};
|
||||
|
||||
thread_local! {
|
||||
static PARENT: OnceCell<PathBuf> = OnceCell::new();
|
||||
}
|
||||
|
||||
pub fn run(path: PathBuf, program: Vec<Stmt>) -> Result<()> {
|
||||
let parent = path.parent().unwrap();
|
||||
PARENT.with(|f| {
|
||||
f.get_or_init(|| parent.to_path_buf());
|
||||
});
|
||||
|
||||
let mut ctx = HashMap::new();
|
||||
insert_builtins(&mut ctx);
|
||||
|
||||
for item in program {
|
||||
eval_stmt(&mut ctx, &item)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eval_stmt(ctx: &mut HashMap<String, Value>, stmt: &Stmt) -> Result<()> {
|
||||
match stmt {
|
||||
Stmt::Input(n, p) => {
|
||||
let path = PARENT.with(|f| f.get().unwrap().join(p).to_path_buf());
|
||||
let data = fs::read_to_string(&path)
|
||||
.with_context(|| format!("could not read {}", path.display()))?;
|
||||
ctx.insert(n.to_string(), Value::String(data));
|
||||
}
|
||||
|
||||
Stmt::Let(n, expr) => {
|
||||
let val = eval_expr(ctx, &expr)?;
|
||||
ctx.insert(n.to_string(), val);
|
||||
}
|
||||
Stmt::Print(expr) => {
|
||||
let val = eval_expr(ctx, &expr)?;
|
||||
println!("{val:?}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eval_expr(ctx: &mut HashMap<String, Value>, expr: &Expr) -> Result<Value> {
|
||||
match expr {
|
||||
Expr::Ident(n) => match ctx.get(n) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => bail!("{}:{}: unknown name {n}", file!(), line!()),
|
||||
},
|
||||
Expr::Call(expr, args) if matches!(**expr, Expr::Field(_, _)) => {
|
||||
if let Expr::Field(expr, name) = &**expr {
|
||||
let mut args1 = vec![(**expr).clone()];
|
||||
args1.extend(args.iter().cloned());
|
||||
let args = args1;
|
||||
Ok(eval_expr(
|
||||
ctx,
|
||||
&Expr::Call(Box::new(Expr::Ident(name.to_owned())), args),
|
||||
)?)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
Expr::Field(expr, n) => todo!(),
|
||||
Expr::Call(expr, args) => {
|
||||
let v = eval_expr(ctx, expr)?;
|
||||
match v {
|
||||
Value::Function(env, params, body) => {
|
||||
let mapping = params.iter().zip(args.iter()).collect::<Vec<_>>();
|
||||
todo!()
|
||||
}
|
||||
Value::Builtin(func) => {
|
||||
let mut args2 = Vec::new();
|
||||
for arg in args {
|
||||
args2.push(eval_expr(ctx, arg)?);
|
||||
}
|
||||
func(args2)
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
Expr::Lambda(vec, expr) => {
|
||||
let mut free_vars = HashSet::new();
|
||||
let mut known_vars =
|
||||
vec.iter().map(|s| s.to_owned()).collect::<HashSet<_>>();
|
||||
free_variables_expr_rec(expr, &mut known_vars, &mut free_vars);
|
||||
|
||||
let mut env = HashMap::new();
|
||||
for var in free_vars {
|
||||
let v = match ctx.get(&var) {
|
||||
Some(v) => v,
|
||||
None => bail!("{}:{}: unknown name {var}", file!(), line!()),
|
||||
};
|
||||
env.insert(var, v.clone());
|
||||
}
|
||||
|
||||
Ok(Value::Function(env, vec.clone(), (**expr).clone()))
|
||||
}
|
||||
Expr::Block(vec, expr) => {
|
||||
for stmt in vec {
|
||||
eval_stmt(ctx, stmt)?;
|
||||
}
|
||||
|
||||
if let Some(expr) = expr {
|
||||
Ok(eval_expr(ctx, expr)?)
|
||||
} else {
|
||||
Ok(Value::Unit)
|
||||
}
|
||||
}
|
||||
Expr::Index(expr, idx) => {
|
||||
let idx = match eval_expr(ctx, idx)? {
|
||||
Value::Int(v) => v,
|
||||
_ => bail!("not a number: {idx:?}"),
|
||||
};
|
||||
|
||||
let arr = match eval_expr(ctx, expr)? {
|
||||
Value::Array(a) => a,
|
||||
_ => bail!("not a list"),
|
||||
};
|
||||
|
||||
Ok(arr[idx as usize].clone())
|
||||
}
|
||||
Expr::Int(n) => Ok(Value::Int(*n)),
|
||||
Expr::Array(vec) => {
|
||||
let mut arr = Vec::new();
|
||||
for v in vec {
|
||||
arr.push(eval_expr(ctx, v)?);
|
||||
}
|
||||
Ok(Value::Array(arr))
|
||||
}
|
||||
Expr::BinOp(left, op, right) => {
|
||||
let left = eval_expr(ctx, left)?;
|
||||
let right = eval_expr(ctx, right)?;
|
||||
match op {
|
||||
Op::Sub => match (&left, &right) {
|
||||
(Value::Int(left), Value::Int(right)) => Ok(Value::Int(left - right)),
|
||||
_ => bail!("both {left:?} , {right:?} must be ints"),
|
||||
},
|
||||
Op::Mul => match (&left, &right) {
|
||||
(Value::Int(left), Value::Int(right)) => Ok(Value::Int(left * right)),
|
||||
_ => bail!("both {left:?} , {right:?} must be ints"),
|
||||
},
|
||||
Op::EqEq => match (&left, &right) {
|
||||
(Value::Int(left), Value::Int(right)) => {
|
||||
Ok(Value::Bool(left == right))
|
||||
}
|
||||
_ => bail!("both {left:?} , {right:?} must be the same"),
|
||||
},
|
||||
}
|
||||
}
|
||||
Expr::Bool(b) => Ok(Value::Bool(*b)),
|
||||
Expr::IfThenElse(cond, t, f) => {
|
||||
let cond = match eval_expr(ctx, cond)? {
|
||||
Value::Bool(b) => b,
|
||||
_ => bail!("not a bool"),
|
||||
};
|
||||
|
||||
if cond {
|
||||
Ok(eval_expr(ctx, t)?)
|
||||
} else {
|
||||
Ok(eval_expr(ctx, f)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_builtins(ctx: &mut HashMap<String, Value>) {
|
||||
fn splitlines(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "splitlines takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::String(s) => Ok(Value::Array(
|
||||
s.lines().map(|s| Value::String(s.to_owned())).collect(),
|
||||
)),
|
||||
_ => bail!("splitlines takes a string, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("splitlines".to_owned(), Value::Builtin(splitlines));
|
||||
|
||||
fn splitwhitespace(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "splitwhitespace takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::String(s) => Ok(Value::Array(
|
||||
s.split_whitespace()
|
||||
.map(|s| Value::String(s.to_owned()))
|
||||
.collect(),
|
||||
)),
|
||||
_ => bail!("splitwhitespace takes a string, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert(
|
||||
"splitwhitespace".to_owned(),
|
||||
Value::Builtin(splitwhitespace),
|
||||
);
|
||||
|
||||
fn map(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 2, "map takes 2 arguments");
|
||||
let list = &args[0];
|
||||
let func = &args[1];
|
||||
match (list, func) {
|
||||
(Value::Array(a), Value::Function(env, s, f)) => {
|
||||
let mut res = Vec::new();
|
||||
let mut ctx = HashMap::new();
|
||||
insert_builtins(&mut ctx);
|
||||
for (k, v) in env {
|
||||
ctx.insert(k.to_owned(), v.clone());
|
||||
}
|
||||
|
||||
for val in a {
|
||||
ctx.insert(s[0].to_owned(), val.clone());
|
||||
res.push(eval_expr(&mut ctx, f)?);
|
||||
}
|
||||
Ok(Value::Array(res))
|
||||
// todo!(" s={s:?}, f={f:?}")
|
||||
}
|
||||
_ => bail!("wrong types"),
|
||||
}
|
||||
}
|
||||
ctx.insert("map".to_owned(), Value::Builtin(map));
|
||||
|
||||
fn parseint(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "parseint takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::String(s) => Ok(Value::Int(s.parse::<i64>()?)),
|
||||
_ => bail!("parseint takes a string, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("parseint".to_owned(), Value::Builtin(parseint));
|
||||
|
||||
fn transpose(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "transpose takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::Array(s) => {
|
||||
let mut res = Vec::new();
|
||||
let mut first = true;
|
||||
for row in s {
|
||||
let row = match row {
|
||||
Value::Array(v) => v,
|
||||
_ => bail!("transpose takes an array of arrays"),
|
||||
};
|
||||
|
||||
if first {
|
||||
for _ in row {
|
||||
res.push(vec![]);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
ensure!(
|
||||
row.len() == res.len(),
|
||||
"non-uniform number of elements per row, expected {}, got {}",
|
||||
res.len(),
|
||||
row.len()
|
||||
);
|
||||
for (i, e) in row.iter().enumerate() {
|
||||
res[i].push(e.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Array(res.into_iter().map(Value::Array).collect()))
|
||||
}
|
||||
_ => bail!("transpose takes an array of arrays, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("transpose".to_owned(), Value::Builtin(transpose));
|
||||
|
||||
fn sort(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "sort takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::Array(a) => {
|
||||
let mut arr = Vec::new();
|
||||
for v in a {
|
||||
match v {
|
||||
Value::Int(n) => arr.push(*n),
|
||||
_ => bail!("not a number"),
|
||||
}
|
||||
}
|
||||
arr.sort();
|
||||
Ok(Value::Array(arr.into_iter().map(Value::Int).collect()))
|
||||
}
|
||||
_ => bail!("sort takes an array, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("sort".to_owned(), Value::Builtin(sort));
|
||||
|
||||
fn abs(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "abs takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::Int(n) => Ok(Value::Int(n.abs())),
|
||||
_ => bail!("abs takes an array, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("abs".to_owned(), Value::Builtin(abs));
|
||||
|
||||
fn sum(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "sum takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::Array(a) => {
|
||||
let mut sum = 0;
|
||||
for v in a {
|
||||
match v {
|
||||
Value::Int(n) => sum += *n,
|
||||
_ => bail!("not a number"),
|
||||
}
|
||||
}
|
||||
Ok(Value::Int(sum))
|
||||
}
|
||||
_ => bail!("sum takes an array, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("sum".to_owned(), Value::Builtin(sum));
|
||||
}
|
||||
|
||||
fn free_variables_stmt_rec(
|
||||
st: &Stmt,
|
||||
ctx: &mut HashSet<String>,
|
||||
s: &mut HashSet<String>,
|
||||
) {
|
||||
match st {
|
||||
Stmt::Input(_, _) => {}
|
||||
Stmt::Let(n, expr) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
ctx.insert(n.to_owned());
|
||||
}
|
||||
Stmt::Print(expr) => free_variables_expr_rec(expr, ctx, s),
|
||||
}
|
||||
}
|
||||
|
||||
fn free_variables_expr_rec(
|
||||
e: &Expr,
|
||||
ctx: &mut HashSet<String>,
|
||||
s: &mut HashSet<String>,
|
||||
) {
|
||||
match e {
|
||||
Expr::Block(vec, expr) => {
|
||||
for v in vec {
|
||||
free_variables_stmt_rec(v, ctx, s);
|
||||
}
|
||||
if let Some(expr) = expr {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
}
|
||||
}
|
||||
Expr::Array(vec) => {
|
||||
for v in vec {
|
||||
free_variables_expr_rec(v, ctx, s);
|
||||
}
|
||||
}
|
||||
Expr::Bool(_) => {}
|
||||
Expr::Int(_) => {}
|
||||
Expr::Ident(n) => {
|
||||
if !ctx.contains(n) {
|
||||
s.insert(n.to_owned());
|
||||
}
|
||||
}
|
||||
Expr::IfThenElse(expr, expr1, expr2) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
free_variables_expr_rec(expr1, ctx, s);
|
||||
free_variables_expr_rec(expr2, ctx, s);
|
||||
}
|
||||
Expr::BinOp(expr, _, expr1) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
free_variables_expr_rec(expr1, ctx, s);
|
||||
}
|
||||
Expr::Field(expr, _) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
}
|
||||
Expr::Call(expr, vec) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
for v in vec {
|
||||
free_variables_expr_rec(v, ctx, s);
|
||||
}
|
||||
}
|
||||
Expr::Index(expr, expr1) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
free_variables_expr_rec(expr1, ctx, s);
|
||||
}
|
||||
Expr::Lambda(vec, expr) => {
|
||||
let mut new_set = ctx.clone();
|
||||
for v in vec {
|
||||
new_set.insert(v.to_owned());
|
||||
}
|
||||
free_variables_expr_rec(expr, &mut new_set, s);
|
||||
}
|
||||
}
|
||||
}
|
495
src/main.rs
495
src/main.rs
|
@ -1,18 +1,11 @@
|
|||
mod ast;
|
||||
mod compiler;
|
||||
mod interpreter;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
env,
|
||||
fmt::format,
|
||||
fs,
|
||||
hash::Hash,
|
||||
os::unix::thread,
|
||||
path::PathBuf,
|
||||
process::exit,
|
||||
};
|
||||
use std::{fs, path::PathBuf, process::exit};
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use ast::Op;
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use codespan_reporting::{
|
||||
diagnostic::{Diagnostic, Label},
|
||||
files::SimpleFiles,
|
||||
|
@ -21,435 +14,81 @@ use codespan_reporting::{
|
|||
termcolor::{ColorChoice, StandardStream},
|
||||
},
|
||||
};
|
||||
use interpreter::run;
|
||||
use lalrpop_util::{lalrpop_mod, ParseError};
|
||||
use once_cell::unsync::{Lazy, OnceCell};
|
||||
|
||||
use crate::ast::{Expr, Stmt, Value};
|
||||
|
||||
lalrpop_mod!(grammar);
|
||||
|
||||
thread_local! {
|
||||
static PARENT: OnceCell<PathBuf> = OnceCell::new();
|
||||
#[derive(Debug, Parser)]
|
||||
struct Opt {
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Command {
|
||||
Compile {
|
||||
/// The file to compile
|
||||
path: PathBuf,
|
||||
},
|
||||
Run {
|
||||
/// The file to interpret
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
let path_str = &args[1];
|
||||
let path = PathBuf::from(path_str);
|
||||
let parent = path.parent().unwrap();
|
||||
PARENT.with(|f| {
|
||||
f.get_or_init(|| parent.to_path_buf());
|
||||
});
|
||||
let source = fs::read_to_string(&path)?;
|
||||
let opt = Opt::parse();
|
||||
|
||||
let parser = grammar::ProgramParser::new();
|
||||
let tree = match parser.parse(&source) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let mut files = SimpleFiles::new();
|
||||
let file_id = files.add(path_str, &source);
|
||||
let diagnostic = match e {
|
||||
ParseError::InvalidToken { location } => Diagnostic::error()
|
||||
.with_message("invalid token")
|
||||
.with_labels(vec![Label::primary(file_id, location..location)]),
|
||||
ParseError::UnrecognizedEof { location, expected } => todo!(),
|
||||
ParseError::UnrecognizedToken { token, expected } => {
|
||||
Diagnostic::error()
|
||||
.with_message(format!(
|
||||
"unrecognized token {}, expecting [{}]",
|
||||
token.1,
|
||||
expected.join(", ")
|
||||
))
|
||||
.with_labels(vec![Label::primary(file_id, token.0..token.2)])
|
||||
}
|
||||
ParseError::ExtraToken { token } => Diagnostic::error()
|
||||
.with_message(format!("extra token {}", token.1))
|
||||
.with_labels(vec![Label::primary(file_id, token.0..token.2)]),
|
||||
ParseError::User { error } => Diagnostic::error().with_message(error),
|
||||
};
|
||||
let writer = StandardStream::stderr(ColorChoice::Always);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
term::emit(&mut writer.lock(), &config, &files, &diagnostic)?;
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
match opt.command {
|
||||
Command::Compile { path } => todo!(),
|
||||
Command::Run { path } => {
|
||||
let source = fs::read_to_string(&path)?;
|
||||
|
||||
let mut ctx = HashMap::new();
|
||||
|
||||
insert_builtins(&mut ctx);
|
||||
|
||||
for item in tree {
|
||||
eval_stmt(&mut ctx, &item)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eval_stmt(ctx: &mut HashMap<String, Value>, stmt: &Stmt) -> Result<()> {
|
||||
match stmt {
|
||||
Stmt::Input(n, p) => {
|
||||
let path = PARENT.with(|f| f.get().unwrap().join(p).to_path_buf());
|
||||
let data = fs::read_to_string(&path)
|
||||
.with_context(|| format!("could not read {}", path.display()))?;
|
||||
ctx.insert(n.to_string(), Value::String(data));
|
||||
}
|
||||
|
||||
Stmt::Let(n, expr) => {
|
||||
let val = eval_expr(ctx, &expr)?;
|
||||
ctx.insert(n.to_string(), val);
|
||||
}
|
||||
Stmt::Print(expr) => {
|
||||
let val = eval_expr(ctx, &expr)?;
|
||||
println!("{val:?}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn eval_expr(ctx: &mut HashMap<String, Value>, expr: &Expr) -> Result<Value> {
|
||||
match expr {
|
||||
Expr::Ident(n) => match ctx.get(n) {
|
||||
Some(v) => Ok(v.clone()),
|
||||
None => bail!("{}:{}: unknown name {n}", file!(), line!()),
|
||||
},
|
||||
Expr::Call(expr, args) if matches!(**expr, Expr::Field(_, _)) => {
|
||||
if let Expr::Field(expr, name) = &**expr {
|
||||
let mut args1 = vec![(**expr).clone()];
|
||||
args1.extend(args.iter().cloned());
|
||||
let args = args1;
|
||||
Ok(eval_expr(
|
||||
ctx,
|
||||
&Expr::Call(Box::new(Expr::Ident(name.to_owned())), args),
|
||||
)?)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
Expr::Field(expr, n) => todo!(),
|
||||
Expr::Call(expr, args) => {
|
||||
let v = eval_expr(ctx, expr)?;
|
||||
match v {
|
||||
Value::Function(env, params, body) => {
|
||||
let mapping = params.iter().zip(args.iter()).collect::<Vec<_>>();
|
||||
todo!()
|
||||
}
|
||||
Value::Builtin(func) => {
|
||||
let mut args2 = Vec::new();
|
||||
for arg in args {
|
||||
args2.push(eval_expr(ctx, arg)?);
|
||||
}
|
||||
func(args2)
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
Expr::Lambda(vec, expr) => {
|
||||
let mut free_vars = HashSet::new();
|
||||
let mut known_vars =
|
||||
vec.iter().map(|s| s.to_owned()).collect::<HashSet<_>>();
|
||||
free_variables_expr_rec(expr, &mut known_vars, &mut free_vars);
|
||||
|
||||
let mut env = HashMap::new();
|
||||
for var in free_vars {
|
||||
let v = match ctx.get(&var) {
|
||||
Some(v) => v,
|
||||
None => bail!("{}:{}: unknown name {var}", file!(), line!()),
|
||||
};
|
||||
env.insert(var, v.clone());
|
||||
}
|
||||
|
||||
Ok(Value::Function(env, vec.clone(), (**expr).clone()))
|
||||
}
|
||||
Expr::Block(vec, expr) => {
|
||||
for stmt in vec {
|
||||
eval_stmt(ctx, stmt)?;
|
||||
}
|
||||
|
||||
if let Some(expr) = expr {
|
||||
Ok(eval_expr(ctx, expr)?)
|
||||
} else {
|
||||
Ok(Value::Unit)
|
||||
}
|
||||
}
|
||||
Expr::Index(expr, idx) => {
|
||||
let idx = match eval_expr(ctx, idx)? {
|
||||
Value::Int(v) => v,
|
||||
_ => bail!("not a number: {idx:?}"),
|
||||
};
|
||||
|
||||
let arr = match eval_expr(ctx, expr)? {
|
||||
Value::Array(a) => a,
|
||||
_ => bail!("not a list"),
|
||||
};
|
||||
|
||||
Ok(arr[idx as usize].clone())
|
||||
}
|
||||
Expr::Int(n) => Ok(Value::Int(*n)),
|
||||
Expr::Array(vec) => {
|
||||
let mut arr = Vec::new();
|
||||
for v in vec {
|
||||
arr.push(eval_expr(ctx, v)?);
|
||||
}
|
||||
Ok(Value::Array(arr))
|
||||
}
|
||||
Expr::BinOp(left, op, right) => {
|
||||
let left = eval_expr(ctx, left)?;
|
||||
let right = eval_expr(ctx, right)?;
|
||||
match op {
|
||||
Op::Sub => match (&left, &right) {
|
||||
(Value::Int(left), Value::Int(right)) => Ok(Value::Int(left - right)),
|
||||
_ => bail!("both {left:?} , {right:?} must be ints"),
|
||||
},
|
||||
Op::Mul => match (&left, &right) {
|
||||
(Value::Int(left), Value::Int(right)) => Ok(Value::Int(left * right)),
|
||||
_ => bail!("both {left:?} , {right:?} must be ints"),
|
||||
},
|
||||
Op::EqEq => match (&left, &right) {
|
||||
(Value::Int(left), Value::Int(right)) => {
|
||||
Ok(Value::Bool(left == right))
|
||||
}
|
||||
_ => bail!("both {left:?} , {right:?} must be the same"),
|
||||
},
|
||||
}
|
||||
}
|
||||
Expr::Bool(b) => Ok(Value::Bool(*b)),
|
||||
Expr::IfThenElse(cond, t, f) => {
|
||||
let cond = match eval_expr(ctx, cond)? {
|
||||
Value::Bool(b) => b,
|
||||
_ => bail!("not a bool"),
|
||||
};
|
||||
|
||||
if cond {
|
||||
Ok(eval_expr(ctx, t)?)
|
||||
} else {
|
||||
Ok(eval_expr(ctx, f)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_builtins(ctx: &mut HashMap<String, Value>) {
|
||||
fn splitlines(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "splitlines takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::String(s) => Ok(Value::Array(
|
||||
s.lines().map(|s| Value::String(s.to_owned())).collect(),
|
||||
)),
|
||||
_ => bail!("splitlines takes a string, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("splitlines".to_owned(), Value::Builtin(splitlines));
|
||||
|
||||
fn splitwhitespace(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "splitwhitespace takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::String(s) => Ok(Value::Array(
|
||||
s.split_whitespace()
|
||||
.map(|s| Value::String(s.to_owned()))
|
||||
.collect(),
|
||||
)),
|
||||
_ => bail!("splitwhitespace takes a string, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert(
|
||||
"splitwhitespace".to_owned(),
|
||||
Value::Builtin(splitwhitespace),
|
||||
);
|
||||
|
||||
fn map(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 2, "map takes 2 arguments");
|
||||
let list = &args[0];
|
||||
let func = &args[1];
|
||||
match (list, func) {
|
||||
(Value::Array(a), Value::Function(env, s, f)) => {
|
||||
let mut res = Vec::new();
|
||||
let mut ctx = HashMap::new();
|
||||
insert_builtins(&mut ctx);
|
||||
for (k, v) in env {
|
||||
ctx.insert(k.to_owned(), v.clone());
|
||||
}
|
||||
|
||||
for val in a {
|
||||
ctx.insert(s[0].to_owned(), val.clone());
|
||||
res.push(eval_expr(&mut ctx, f)?);
|
||||
}
|
||||
Ok(Value::Array(res))
|
||||
// todo!(" s={s:?}, f={f:?}")
|
||||
}
|
||||
_ => bail!("wrong types"),
|
||||
}
|
||||
}
|
||||
ctx.insert("map".to_owned(), Value::Builtin(map));
|
||||
|
||||
fn parseint(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "parseint takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::String(s) => Ok(Value::Int(s.parse::<i64>()?)),
|
||||
_ => bail!("parseint takes a string, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("parseint".to_owned(), Value::Builtin(parseint));
|
||||
|
||||
fn transpose(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "transpose takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::Array(s) => {
|
||||
let mut res = Vec::new();
|
||||
let mut first = true;
|
||||
for row in s {
|
||||
let row = match row {
|
||||
Value::Array(v) => v,
|
||||
_ => bail!("transpose takes an array of arrays"),
|
||||
};
|
||||
|
||||
if first {
|
||||
for _ in row {
|
||||
res.push(vec![]);
|
||||
let parser = grammar::ProgramParser::new();
|
||||
let tree = match parser.parse(&source) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let mut files = SimpleFiles::new();
|
||||
let file_id = files.add(path.display().to_string(), &source);
|
||||
let diagnostic = match e {
|
||||
ParseError::InvalidToken { location } => Diagnostic::error()
|
||||
.with_message("invalid token")
|
||||
.with_labels(vec![Label::primary(file_id, location..location)]),
|
||||
ParseError::UnrecognizedEof { location, expected } => {
|
||||
Diagnostic::error()
|
||||
.with_message(format!(
|
||||
"unrecognized eof, expected [{}]",
|
||||
expected.join(", ")
|
||||
))
|
||||
.with_labels(vec![Label::primary(file_id, location..location)])
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
ensure!(
|
||||
row.len() == res.len(),
|
||||
"non-uniform number of elements per row, expected {}, got {}",
|
||||
res.len(),
|
||||
row.len()
|
||||
);
|
||||
for (i, e) in row.iter().enumerate() {
|
||||
res[i].push(e.clone());
|
||||
}
|
||||
ParseError::UnrecognizedToken { token, expected } => {
|
||||
Diagnostic::error()
|
||||
.with_message(format!(
|
||||
"unrecognized token {}, expecting [{}]",
|
||||
token.1,
|
||||
expected.join(", ")
|
||||
))
|
||||
.with_labels(vec![Label::primary(file_id, token.0..token.2)])
|
||||
}
|
||||
ParseError::ExtraToken { token } => Diagnostic::error()
|
||||
.with_message(format!("extra token {}", token.1))
|
||||
.with_labels(vec![Label::primary(file_id, token.0..token.2)]),
|
||||
ParseError::User { error } => {
|
||||
Diagnostic::error().with_message(error)
|
||||
}
|
||||
};
|
||||
let writer = StandardStream::stderr(ColorChoice::Always);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
term::emit(&mut writer.lock(), &config, &files, &diagnostic)?;
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Value::Array(res.into_iter().map(Value::Array).collect()))
|
||||
}
|
||||
_ => bail!("transpose takes an array of arrays, got {:?} instead", arg),
|
||||
run(path, tree)?;
|
||||
}
|
||||
}
|
||||
ctx.insert("transpose".to_owned(), Value::Builtin(transpose));
|
||||
|
||||
fn sort(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "sort takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::Array(a) => {
|
||||
let mut arr = Vec::new();
|
||||
for v in a {
|
||||
match v {
|
||||
Value::Int(n) => arr.push(*n),
|
||||
_ => bail!("not a number"),
|
||||
}
|
||||
}
|
||||
arr.sort();
|
||||
Ok(Value::Array(arr.into_iter().map(Value::Int).collect()))
|
||||
}
|
||||
_ => bail!("sort takes an array, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("sort".to_owned(), Value::Builtin(sort));
|
||||
|
||||
fn abs(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "abs takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::Int(n) => Ok(Value::Int(n.abs())),
|
||||
_ => bail!("abs takes an array, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("abs".to_owned(), Value::Builtin(abs));
|
||||
|
||||
fn sum(args: Vec<Value>) -> Result<Value> {
|
||||
ensure!(args.len() == 1, "sum takes one argument");
|
||||
let arg = &args[0];
|
||||
match arg {
|
||||
Value::Array(a) => {
|
||||
let mut sum = 0;
|
||||
for v in a {
|
||||
match v {
|
||||
Value::Int(n) => sum += *n,
|
||||
_ => bail!("not a number"),
|
||||
}
|
||||
}
|
||||
Ok(Value::Int(sum))
|
||||
}
|
||||
_ => bail!("sum takes an array, got {:?} instead", arg),
|
||||
}
|
||||
}
|
||||
ctx.insert("sum".to_owned(), Value::Builtin(sum));
|
||||
}
|
||||
|
||||
fn free_variables_stmt_rec(
|
||||
st: &Stmt,
|
||||
ctx: &mut HashSet<String>,
|
||||
s: &mut HashSet<String>,
|
||||
) {
|
||||
match st {
|
||||
Stmt::Input(_, _) => {}
|
||||
Stmt::Let(n, expr) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
ctx.insert(n.to_owned());
|
||||
}
|
||||
Stmt::Print(expr) => free_variables_expr_rec(expr, ctx, s),
|
||||
}
|
||||
}
|
||||
|
||||
fn free_variables_expr_rec(
|
||||
e: &Expr,
|
||||
ctx: &mut HashSet<String>,
|
||||
s: &mut HashSet<String>,
|
||||
) {
|
||||
match e {
|
||||
Expr::Block(vec, expr) => {
|
||||
for v in vec {
|
||||
free_variables_stmt_rec(v, ctx, s);
|
||||
}
|
||||
if let Some(expr) = expr {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
}
|
||||
}
|
||||
Expr::Array(vec) => {
|
||||
for v in vec {
|
||||
free_variables_expr_rec(v, ctx, s);
|
||||
}
|
||||
}
|
||||
Expr::Bool(_) => {}
|
||||
Expr::Int(_) => {}
|
||||
Expr::Ident(n) => {
|
||||
if !ctx.contains(n) {
|
||||
s.insert(n.to_owned());
|
||||
}
|
||||
}
|
||||
Expr::IfThenElse(expr, expr1, expr2) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
free_variables_expr_rec(expr1, ctx, s);
|
||||
free_variables_expr_rec(expr2, ctx, s);
|
||||
}
|
||||
Expr::BinOp(expr, _, expr1) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
free_variables_expr_rec(expr1, ctx, s);
|
||||
}
|
||||
Expr::Field(expr, _) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
}
|
||||
Expr::Call(expr, vec) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
for v in vec {
|
||||
free_variables_expr_rec(v, ctx, s);
|
||||
}
|
||||
}
|
||||
Expr::Index(expr, expr1) => {
|
||||
free_variables_expr_rec(expr, ctx, s);
|
||||
free_variables_expr_rec(expr1, ctx, s);
|
||||
}
|
||||
Expr::Lambda(vec, expr) => {
|
||||
let mut new_set = ctx.clone();
|
||||
for v in vec {
|
||||
new_set.insert(v.to_owned());
|
||||
}
|
||||
free_variables_expr_rec(expr, &mut new_set, s);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue