split cli

This commit is contained in:
Michael Zhang 2024-12-02 04:12:03 -06:00
parent 4af71fb0ee
commit 61d0adb25b
6 changed files with 590 additions and 430 deletions

120
Cargo.lock generated
View file

@ -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"

View file

@ -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
View file

@ -0,0 +1 @@

View file

@ -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
View 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);
}
}
}

View file

@ -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(())
}