add block expressions

This commit is contained in:
Jim 2025-10-21 08:34:21 -04:00
parent c5b045ebcb
commit 3c184a1ac8
Signed by: jim
GPG key ID: 3236D2F059A7C0AC
8 changed files with 129 additions and 54 deletions

View file

@ -33,3 +33,33 @@ Found 2 outliers among 100 measurements (2.00%)
1 (1.00%) high mild
1 (1.00%) high severe
```
- adding block expression to avoid deep recursion (22.8 MB/s parsing, also enabled running long programs without stack overflow lol)
```
Fibonacci/Parse/programs/medium.jang
time: [9.3838 ms 9.3884 ms 9.3929 ms]
change: [31.880% 31.709% 31.544%] (p = 0.00 < 0.05)
Performance has improved.
Found 10 outliers among 100 measurements (10.00%)
5 (5.00%) low severe
5 (5.00%) high mild
Fibonacci/Interpret/programs/medium.jang
time: [11.324 ms 11.329 ms 11.334 ms]
change: [27.811% 27.695% 27.582%] (p = 0.00 < 0.05)
Performance has improved.
Found 4 outliers among 100 measurements (4.00%)
3 (3.00%) high mild
1 (1.00%) high severe
Fibonacci/Parse/programs/test.jang
time: [3.0235 µs 3.2381 µs 3.4928 µs]
change: [23.214% 13.984% 5.8612%] (p = 0.00 < 0.05)
Performance has improved.
Fibonacci/Interpret/programs/test.jang
time: [2.5598 µs 2.7237 µs 2.8978 µs]
change: [59.249% 57.809% 55.973%] (p = 0.00 < 0.05)
Performance has improved.
Found 19 outliers among 100 measurements (19.00%)
4 (4.00%) low mild
7 (7.00%) high mild
8 (8.00%) high severe
```

View file

@ -4,8 +4,14 @@ use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use jang::*;
fn bench_parsers(c: &mut Criterion) {
let mut group = c.benchmark_group("Fibonacci");
for input_path in ["programs/medium.jang", "programs/test.jang"].iter() {
let mut group = c.benchmark_group("jang");
for input_path in [
"programs/long.jang",
"programs/medium.jang",
"programs/test.jang",
]
.iter()
{
let src = std::fs::read_to_string(input_path).unwrap();
group.bench_with_input(BenchmarkId::new("Parse", input_path), &src, |b, src| {
@ -17,7 +23,8 @@ fn bench_parsers(c: &mut Criterion) {
return;
};
let mut vars = HashMap::new();
_ = eval(&ast, &mut vars);
let mut scope = Vec::new();
_ = eval(&ast, &mut vars, &mut scope);
})
});
}

View file

@ -18,24 +18,32 @@ pub enum IdentValue<'src> {
pub fn eval<'src>(
expr: &Expr<'src>,
vars: &mut HashMap<&'src str, Vec<IdentValue<'src>>>,
scope_vars: &mut Vec<&'src str>,
) -> Result<f64, String> {
fn bind_var<'src>(
vars: &mut HashMap<&'src str, Vec<IdentValue<'src>>>,
scope_vars: &mut Vec<&'src str>,
name: &'src str,
val: IdentValue<'src>,
) {
vars.entry(name).or_insert(Vec::new()).push(val);
scope_vars.push(name);
}
fn unbind_var<'src>(vars: &mut HashMap<&'src str, Vec<IdentValue<'src>>>, name: &'src str) {
vars.get_mut(name).unwrap().pop();
fn drop_scope<'src>(
vars: &mut HashMap<&'src str, Vec<IdentValue<'src>>>,
scope_vars: Vec<&'src str>,
) {
for var in scope_vars.into_iter() {
vars.get_mut(var).unwrap().pop();
}
}
match expr {
Expr::Num(x) => Ok(*x),
Expr::Neg(a) => Ok(-eval(a, vars)?),
Expr::Add(a, b) => Ok(eval(a, vars)? + eval(b, vars)?),
Expr::Sub(a, b) => Ok(eval(a, vars)? - eval(b, vars)?),
Expr::Mul(a, b) => Ok(eval(a, vars)? * eval(b, vars)?),
Expr::Div(a, b) => Ok(eval(a, vars)? / eval(b, vars)?),
Expr::Neg(a) => Ok(-eval(a, vars, scope_vars)?),
Expr::Add(a, b) => Ok(eval(a, vars, scope_vars)? + eval(b, vars, scope_vars)?),
Expr::Sub(a, b) => Ok(eval(a, vars, scope_vars)? - eval(b, vars, scope_vars)?),
Expr::Mul(a, b) => Ok(eval(a, vars, scope_vars)? * eval(b, vars, scope_vars)?),
Expr::Div(a, b) => Ok(eval(a, vars, scope_vars)? / eval(b, vars, scope_vars)?),
Expr::Var(name) => {
let ident_value = vars.get(name).and_then(|v| v.last());
match ident_value {
@ -44,12 +52,10 @@ pub fn eval<'src>(
None => Err(format!("Undefined variable `{}`", name)),
}
}
Expr::Let { name, rhs, then } => {
let val = eval(rhs, vars)?;
bind_var(vars, name, IdentValue::Var(val));
let output = eval(then, vars);
unbind_var(vars, name);
output
Expr::Let { name, rhs } => {
let val = eval(rhs, vars, scope_vars)?;
bind_var(vars, scope_vars, name, IdentValue::Var(val));
Ok(val)
}
Expr::Call(name, args) => {
let ident_value = vars.get(name).and_then(|v| v.last()).cloned();
@ -66,15 +72,19 @@ pub fn eval<'src>(
let binds = params
.iter()
.zip(args)
.map(|(param, arg)| (param, eval(arg, vars)))
.map(|(param, arg)| (param, eval(arg, vars, scope_vars)))
.collect::<Vec<_>>();
let mut param_scope = Vec::with_capacity(binds.len());
for (param, bind) in binds {
bind_var(vars, param, IdentValue::Var(bind.unwrap()));
}
let return_value = eval(&body, vars);
for param in params.iter() {
unbind_var(vars, param);
bind_var(
vars,
&mut param_scope,
param,
IdentValue::Var(bind.unwrap()),
);
}
let return_value = eval(&body, vars, scope_vars);
drop_scope(vars, param_scope);
return_value
}
}
@ -84,12 +94,7 @@ pub fn eval<'src>(
None => Err(format!("Undefined variable `{}`", name)),
}
}
Expr::Fn {
name,
args,
body,
then,
} => {
Expr::Fn { name, args, body } => {
vars.entry(name)
.or_insert(Vec::new())
.push(IdentValue::Func {
@ -97,11 +102,25 @@ pub fn eval<'src>(
params: args.clone(),
body: body.clone(),
});
let output = eval(then, vars);
vars.get_mut(name).unwrap().pop();
output
// todo - return value
Ok(0.0)
}
Expr::Block(exprs) => {
// todo - unbind vars after block
let mut return_value = Err(String::new());
let mut scope = Vec::new();
for expr in exprs.iter() {
return_value = eval(expr, vars, &mut scope);
}
drop_scope(vars, scope);
return_value.map_err(|err| {
if err.is_empty() {
"block must return a value".to_string()
} else {
err
}
})
}
Expr::Block(_) => panic!("unimplemented"),
}
}
@ -135,7 +154,7 @@ pub enum Token<'src> {
Comma,
#[token("=")]
Equals,
#[regex("[0-9]", |lex| lex.slice().parse::<u64>().unwrap())]
#[regex("[0-9]+", |lex| lex.slice().parse::<u64>().unwrap())]
Number(u64),
#[regex("[_a-zA-Z][_a-zA-Z0-9]*", |lex| lex.slice())]
Ident(&'src str),
@ -157,13 +176,11 @@ pub enum Expr<'src> {
Let {
name: &'src str,
rhs: Box<Expr<'src>>,
then: Box<Expr<'src>>,
},
Fn {
name: &'src str,
args: Vec<&'src str>,
body: Rc<Expr<'src>>,
then: Box<Expr<'src>>,
},
}
@ -194,12 +211,23 @@ pub fn parse<'src, const LOG: bool>(
}
(t, s)
});
create_parser().parse(token_stream).into_result()
}
pub fn create_parser<'src, I: ValueInput<'src, Token = Token<'src>, Span = SimpleSpan>>()
-> impl Parser<'src, I, Expr<'src>, ParseError<'src>> {
// decl => fn | let | block
// block => { stmt_list }
// stmt_list = decl1; decl2; decl3|expr
// let => let ident = expr; decl
// fn => fn f x y = expr; decl
// expr => sum | block
// sum => product +/- product
// product => unary * / unary
// unary => -atom
// atom => (expr) | call | ident
// call => ident(arg1,arg2)
let ident = any()
.filter(|tok| matches!(tok, Token::Ident(_)))
.map(|tok| match tok {
@ -213,6 +241,8 @@ pub fn create_parser<'src, I: ValueInput<'src, Token = Token<'src>, Span = Simpl
_ => unreachable!("alredy filtered on number"),
});
let mut block = Recursive::declare();
let expr = recursive(|expr| {
let call = ident
.then(
@ -253,21 +283,18 @@ pub fn create_parser<'src, I: ValueInput<'src, Token = Token<'src>, Span = Simpl
|lhs, (op, rhs)| op(Box::new(lhs), Box::new(rhs)),
);
sum
block.clone().or(sum)
});
let decl = recursive(|decl| {
let decl = recursive(|_decl| {
// ex: let x = 5*2
let decl_let = just(Token::KWLet)
.ignore_then(ident)
.then_ignore(just(Token::Equals))
.then(expr.clone())
.then_ignore(just(Token::Semicolon))
.then(decl.clone())
.map(|((name, rhs), then)| Expr::Let {
.map(|(name, rhs)| Expr::Let {
name,
rhs: Box::new(rhs),
then: Box::new(then),
});
// ex: fn f x y = x + y
@ -277,24 +304,24 @@ pub fn create_parser<'src, I: ValueInput<'src, Token = Token<'src>, Span = Simpl
.then(ident.repeated().collect::<Vec<_>>())
.then_ignore(just(Token::Equals))
.then(expr.clone())
.then_ignore(just(Token::Semicolon))
.then(decl.clone())
.map(|(((name, params), body), then)| Expr::Fn {
.map(|((name, params), body)| Expr::Fn {
name,
args: params,
body: Rc::new(body),
then: Box::new(then),
});
// let decl_block = decl
// .repeated()
// .collect::<Vec<_>>()
// .delimited_by(just(Token::LCurly), just(Token::RCurly))
// .map(Expr::Block);
// must parse 'let' keyword first so it doesn't become an identifier
decl_fn.or(decl_let).or(expr)
decl_fn.or(decl_let).or(expr.clone())
});
block.define(
decl.clone()
.separated_by(just(Token::Semicolon))
.allow_trailing()
.collect::<Vec<_>>()
.delimited_by(just(Token::LCurly), just(Token::RCurly))
.map(Expr::Block),
);
decl
}

4
programs/blocks.jang Normal file
View file

@ -0,0 +1,4 @@
let x = {
let y = 10;
5 + y
}

View file

@ -1,3 +1,4 @@
{
let five = 5;
let five = five + 3;
fn sum x y = x + y;
@ -999999,3 +1000000,4 @@ fn sum x y = x + y;
fn mul x y = x*y;
let six = sum(1,sum(mul(2,2),3));
six
}

View file

@ -1,3 +1,4 @@
{
let five = 5;
let five = five + 3;
fn sum x y = x + y;
@ -9999,3 +10000,4 @@ fn sum x y = x + y;
fn mul x y = x*y;
let six = sum(1,sum(mul(2,2),3));
six
}

View file

@ -1,3 +1,4 @@
{
let five = 5;
let five = five + 3;
fn sum x y = x + y;
@ -904,3 +905,4 @@ fn sum x y = x + y;
fn mul x y = x*y;
let six = sum(1,sum(mul(2,2),3));
six
}

View file

@ -11,7 +11,8 @@ fn main() {
println!("parsed:\n{:?}\n---", parse_result);
if let Ok(ast) = parse_result {
let mut vars = HashMap::new();
let output = eval(&ast, &mut vars);
let mut scope = Vec::new();
let output = eval(&ast, &mut vars, &mut scope);
println!("{:?}", output);
}
}