diff --git a/Cargo.lock b/Cargo.lock index d1293939..d88e30b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,7 +62,7 @@ dependencies = [ [[package]] name = "bend-lang" -version = "0.2.27" +version = "0.2.28" dependencies = [ "TSPL", "clap", diff --git a/Cargo.toml b/Cargo.toml index 79dfae58..a474068a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "bend-lang" description = "A high-level, massively parallel programming language" license = "Apache-2.0" -version = "0.2.27" +version = "0.2.28" edition = "2021" rust-version = "1.74" exclude = ["tests/snapshots/"] diff --git a/docs/syntax.md b/docs/syntax.md index 050213b9..7f469d83 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -142,6 +142,17 @@ The operations are: - Subtraction `-=` - Multiplication `*=` - Division `/=` +- Bit And `&=` +- Bit Or `|=` +- Bit Xor `^=` +- Mapper `@=` + +The mapper in-place operation applies a function and re-assigns the variable: + +```python +x = "hello" +x @= String/uppercase +``` ### Return diff --git a/src/fun/builtins.bend b/src/fun/builtins.bend index c7235f8b..f9ec3d4f 100644 --- a/src/fun/builtins.bend +++ b/src/fun/builtins.bend @@ -45,6 +45,19 @@ Map/set map key value = } } +Map/map (Map/Leaf) key f = Map/Leaf +Map/map (Map/Node value left right) key f = + switch _ = (== 0 key) { + 0: switch _ = (% key 2) { + 0: + (Map/Node value (Map/map left (/ key 2) f) right) + _: + (Map/Node value left (Map/map right (/ key 2) f)) + } + _: (Map/Node (f value) left right) + } + + # IO Impl type IO: diff --git a/src/imp/gen_map_get.rs b/src/imp/gen_map_get.rs index 024c76b2..723f279f 100644 --- a/src/imp/gen_map_get.rs +++ b/src/imp/gen_map_get.rs @@ -39,12 +39,23 @@ impl Stmt { *self = gen_get(self, substitutions); } } - Stmt::InPlace { op: _, var: _, val, nxt } => { + Stmt::InPlace { op: _, pat, val, nxt } => { + let key_substitutions = if let AssignPattern::MapSet(_, key) = &mut **pat { + key.substitute_map_gets(id) + } else { + Vec::new() + }; + nxt.gen_map_get(id); + let substitutions = val.substitute_map_gets(id); if !substitutions.is_empty() { *self = gen_get(self, substitutions); } + + if !key_substitutions.is_empty() { + *self = gen_get(self, key_substitutions); + } } Stmt::If { cond, then, otherwise, nxt } => { then.gen_map_get(id); diff --git a/src/imp/mod.rs b/src/imp/mod.rs index 2198d045..b5537f37 100644 --- a/src/imp/mod.rs +++ b/src/imp/mod.rs @@ -72,6 +72,7 @@ pub enum InPlaceOp { And, Or, Xor, + Map, } #[derive(Clone, Debug, Default)] @@ -85,7 +86,7 @@ pub enum Stmt { // {var} += {val} ";"? {nxt} InPlace { op: InPlaceOp, - var: Name, + pat: Box, val: Box, nxt: Box, }, @@ -215,6 +216,7 @@ impl InPlaceOp { InPlaceOp::And => Op::AND, InPlaceOp::Or => Op::OR, InPlaceOp::Xor => Op::XOR, + InPlaceOp::Map => unreachable!(), } } } diff --git a/src/imp/parser.rs b/src/imp/parser.rs index 646a278c..196eef60 100644 --- a/src/imp/parser.rs +++ b/src/imp/parser.rs @@ -464,16 +464,20 @@ impl<'a> PyParser<'a> { return Ok((stmt, nxt_indent)); } // In-place - if let AssignPattern::Var(name) = pat { - if let Some(op) = self.parse_in_place_op()? { - let val = self.parse_expr(true)?; - self.skip_trivia_inline(); - self.try_consume_exactly(";"); - self.consume_indent_exactly(*indent)?; - let (nxt, nxt_indent) = self.parse_statement(indent)?; - let stmt = Stmt::InPlace { op, var: name, val: Box::new(val), nxt: Box::new(nxt) }; - return Ok((stmt, nxt_indent)); - } + + match &pat { + AssignPattern::Var(..) => {} + AssignPattern::MapSet(..) => {} + _ => self.expected_spanned("Var or Map accessor", ini_idx, end_idx)?, + } + if let Some(op) = self.parse_in_place_op()? { + let val = self.parse_expr(true)?; + self.skip_trivia_inline(); + self.try_consume_exactly(";"); + self.consume_indent_exactly(*indent)?; + let (nxt, nxt_indent) = self.parse_statement(indent)?; + let stmt = Stmt::InPlace { op, pat: Box::new(pat), val: Box::new(val), nxt: Box::new(nxt) }; + return Ok((stmt, nxt_indent)); } self.expected_spanned("statement", ini_idx, end_idx) @@ -502,6 +506,9 @@ impl<'a> PyParser<'a> { } else if self.starts_with("^=") { self.consume("^=")?; Some(InPlaceOp::Xor) + } else if self.starts_with("@=") { + self.consume("@=")?; + Some(InPlaceOp::Map) } else { None }; diff --git a/src/imp/to_fun.rs b/src/imp/to_fun.rs index 010af672..003d284a 100644 --- a/src/imp/to_fun.rs +++ b/src/imp/to_fun.rs @@ -1,4 +1,4 @@ -use super::{AssignPattern, Definition, Expr, Stmt}; +use super::{AssignPattern, Definition, Expr, InPlaceOp, Stmt}; use crate::fun::{ self, builtins::{LCONS, LNIL}, @@ -93,24 +93,78 @@ impl Stmt { let val = val.to_fun(); StmtToFun::Assign(pat, val) } - Stmt::InPlace { op, var, val, nxt } => { + Stmt::InPlace { op, pat, val, nxt } => { let (nxt_pat, nxt) = match nxt.into_fun()? { StmtToFun::Return(term) => (None, term), StmtToFun::Assign(pat, term) => (Some(pat), term), }; - let term = fun::Term::Let { - pat: Box::new(fun::Pattern::Var(Some(var.clone()))), - val: Box::new(fun::Term::Oper { - opr: op.to_lang_op(), - fst: Box::new(fun::Term::Var { nam: var }), - snd: Box::new(val.to_fun()), - }), - nxt: Box::new(nxt), - }; - if let Some(pat) = nxt_pat { - StmtToFun::Assign(pat, term) - } else { - StmtToFun::Return(term) + + // if it is a mapper operation + if let InPlaceOp::Map = op { + let term = match &*pat { + AssignPattern::MapSet(map, key) => { + let rhs = fun::Term::call( + fun::Term::r#ref("Map/map"), + [fun::Term::Var { nam: map.clone() }, key.clone().to_fun(), val.clone().to_fun()], + ); + fun::Term::Let { + pat: Box::new(fun::Pattern::Var(Some(map.clone()))), + val: Box::new(rhs), + nxt: Box::new(nxt), + } + } + _ => { + let rhs = fun::Term::call(val.to_fun(), [pat.clone().into_fun().to_term()]); + fun::Term::Let { pat: Box::new(pat.into_fun()), val: Box::new(rhs), nxt: Box::new(nxt) } + } + }; + + if let Some(pat) = nxt_pat { + return Ok(StmtToFun::Assign(pat, term)); + } else { + return Ok(StmtToFun::Return(term)); + } + } + + // otherwise + match *pat { + AssignPattern::Var(var) => { + let term = fun::Term::Let { + pat: Box::new(fun::Pattern::Var(Some(var.clone()))), + val: Box::new(fun::Term::Oper { + opr: op.to_lang_op(), + fst: Box::new(fun::Term::Var { nam: var }), + snd: Box::new(val.to_fun()), + }), + nxt: Box::new(nxt), + }; + if let Some(pat) = nxt_pat { + StmtToFun::Assign(pat, term) + } else { + StmtToFun::Return(term) + } + } + AssignPattern::MapSet(map, key) => { + let temp = Name::new("%0"); + let partial = + Expr::Opr { op: op.to_lang_op(), lhs: Box::new(Expr::Var { nam: temp.clone() }), rhs: val }; + let map_fn = Expr::Lam { names: vec![(temp, false)], bod: Box::new(partial) }; + let map_term = fun::Term::call( + fun::Term::r#ref("Map/map"), + [fun::Term::Var { nam: map.clone() }, key.to_fun(), map_fn.to_fun()], + ); + let term = fun::Term::Let { + pat: Box::new(fun::Pattern::Var(Some(map))), + val: Box::new(map_term), + nxt: Box::new(nxt), + }; + if let Some(pat) = nxt_pat { + StmtToFun::Assign(pat, term) + } else { + StmtToFun::Return(term) + } + } + _ => unreachable!(), } } Stmt::If { cond, then, otherwise, nxt } => { diff --git a/tests/golden_tests/desugar_file/mapper_syntax.bend b/tests/golden_tests/desugar_file/mapper_syntax.bend new file mode 100644 index 00000000..5e90bc67 --- /dev/null +++ b/tests/golden_tests/desugar_file/mapper_syntax.bend @@ -0,0 +1,7 @@ +def main: + x = 1 + x @= lambda x: x + 1 + map = { 0: 3, 1: 4 } + map[1] += 1 + map[1] @= lambda x: x * 2 + return (x, map[1], map[0]) diff --git a/tests/golden_tests/run_file/mapper_syntax.bend b/tests/golden_tests/run_file/mapper_syntax.bend new file mode 100644 index 00000000..5e90bc67 --- /dev/null +++ b/tests/golden_tests/run_file/mapper_syntax.bend @@ -0,0 +1,7 @@ +def main: + x = 1 + x @= lambda x: x + 1 + map = { 0: 3, 1: 4 } + map[1] += 1 + map[1] @= lambda x: x * 2 + return (x, map[1], map[0]) diff --git a/tests/snapshots/desugar_file__mapper_syntax.bend.snap b/tests/snapshots/desugar_file__mapper_syntax.bend.snap new file mode 100644 index 00000000..5d6ea8c2 --- /dev/null +++ b/tests/snapshots/desugar_file__mapper_syntax.bend.snap @@ -0,0 +1,85 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/desugar_file/mapper_syntax.bend +--- +(Map/empty) = Map/Leaf + +(Map/get) = λa λb (a Map/get__C5 b) + +(Map/set) = λa λb λc (a Map/set__C10 c b) + +(Map/map) = λa λb λc (a Map/map__C5 b c) + +(main) = let (a, b) = main__C8; let (c, *) = (Map/get b 0); (main__C7, a, c) + +(Map/Node) = λa λb λc λd (d Map/Node/tag a b c) + +(Map/Leaf) = λa (a Map/Leaf/tag) + +(Map/Node/tag) = 0 + +(Map/Leaf/tag) = 1 + +(Map/get__C0) = λa λb λc λd let (e, f) = (Map/get c (/ a 2)); (e, (Map/Node b f d)) + +(Map/get__C1) = λ* λa λb λc λd let (e, f) = (Map/get d (/ a 2)); (e, (Map/Node b c f)) + +(Map/get__C2) = λa let {b c} = a; λd λe λf λ* (switch (% b 2) { 0: Map/get__C0; _: Map/get__C1; } c d e f) + +(Map/get__C3) = λ* λ* λa λ* λ* λb (a, b) + +(Map/get__C4) = λa let {b c} = a; λd let {e f} = d; λg let {h i} = g; λj let {k l} = j; (switch (== 0 k) { 0: Map/get__C2; _: Map/get__C3; } l b e h (Map/Node c f i)) + +(Map/get__C5) = λa switch a { 0: Map/get__C4; _: λ* λ* (*, Map/Leaf); } + +(Map/map__C0) = λa λb λc λd λe (Map/Node e (Map/map d (/ b 2) a) c) + +(Map/map__C1) = λ* λa λb λc λd λe (Map/Node e d (Map/map c (/ b 2) a)) + +(Map/map__C2) = λa λb let {c d} = b; λe λf λg (switch (% c 2) { 0: Map/map__C0; _: Map/map__C1; } a d e f g) + +(Map/map__C3) = λ* λa λ* λb λc λd (Map/Node (a d) c b) + +(Map/map__C4) = λa λb λc λd let {e f} = d; λg (switch (== 0 e) { 0: Map/map__C2; _: Map/map__C3; } g f c b a) + +(Map/map__C5) = λa switch a { 0: Map/map__C4; _: λ* λ* λ* Map/Leaf; } + +(Map/set__C0) = λa λb λc λd λe (Map/Node c (Map/set d (/ b 2) a) e) + +(Map/set__C1) = λ* λa λb λc λd λe (Map/Node c d (Map/set e (/ b 2) a)) + +(Map/set__C10) = λa switch a { 0: Map/set__C8; _: Map/set__C9; } + +(Map/set__C2) = λa λb let {c d} = b; λe λf λg (switch (% c 2) { 0: Map/set__C0; _: Map/set__C1; } a d e f g) + +(Map/set__C3) = λ* λa λ* λ* λb λc (Map/Node a b c) + +(Map/set__C4) = λa λb (Map/Node * (Map/set Map/Leaf (/ b 2) a) Map/Leaf) + +(Map/set__C5) = λ* λa λb (Map/Node * Map/Leaf (Map/set Map/Leaf (/ b 2) a)) + +(Map/set__C6) = λa λb let {c d} = b; (switch (% c 2) { 0: Map/set__C4; _: Map/set__C5; } a d) + +(Map/set__C7) = λ* λa λ* (Map/Node a Map/Leaf Map/Leaf) + +(Map/set__C8) = λa λb λc λd λe let {f g} = e; (switch (== 0 f) { 0: Map/set__C2; _: Map/set__C3; } d g a b c) + +(Map/set__C9) = λ* λa λb let {c d} = b; (switch (== 0 c) { 0: Map/set__C6; _: Map/set__C7; } a d) + +(main__C0) = (Map/set Map/empty 0 3) + +(main__C1) = λa (+ a 1) + +(main__C2) = (Map/set main__C0 1 4) + +(main__C3) = λa (* a 2) + +(main__C4) = (Map/map main__C2 1 main__C1) + +(main__C5) = (Map/map main__C4 1 main__C3) + +(main__C6) = λa (+ a 1) + +(main__C7) = (main__C6 1) + +(main__C8) = (Map/get main__C5 1) diff --git a/tests/snapshots/run_file__mapper_syntax.bend.snap b/tests/snapshots/run_file__mapper_syntax.bend.snap new file mode 100644 index 00000000..c1aa0101 --- /dev/null +++ b/tests/snapshots/run_file__mapper_syntax.bend.snap @@ -0,0 +1,9 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/run_file/mapper_syntax.bend +--- +NumScott: +((λa (+ a 1) 1), (10, 3)) + +Scott: +((λa (+ a 1) 1), (10, 3))