diff --git a/site/primitives.json b/site/primitives.json index 9fb505e1c..0981ba5e6 100644 --- a/site/primitives.json +++ b/site/primitives.json @@ -339,6 +339,14 @@ "class": "DyadicPervasive", "description": "Add values" }, + "anti": { + "glyph": "˘", + "outputs": 1, + "modifier_args": 1, + "class": "InversionModifier", + "description": "Invert the behavior of a function, but keep its signature", + "experimental": true + }, "assert": { "glyph": "⍤", "args": 2, @@ -957,6 +965,13 @@ "class": "Stack", "description": "Duplicate the second-to-top value to the top of the stack" }, + "pad": { + "args": 2, + "outputs": 1, + "class": "DyadicArray", + "description": "Pad an array", + "experimental": true + }, "parse": { "glyph": "⋕", "args": 1, diff --git a/src/algorithm/invert.rs b/src/algorithm/invert.rs index bcce45931..2649fccca 100644 --- a/src/algorithm/invert.rs +++ b/src/algorithm/invert.rs @@ -646,6 +646,7 @@ pub(crate) fn under_instrs( &UnderPatternFn(under_push_temp_pattern, "push temp"), &UnderPatternFn(under_copy_temp_pattern, "copy temp"), &UnderPatternFn(under_un_pattern, "un"), + &UnderPatternFn(under_anti_pattern, "anti"), &UnderPatternFn(under_from_inverse_pattern, "from inverse"), // These must come last! ]; @@ -691,7 +692,7 @@ fn resolve_uns(instrs: EcoVec, comp: &mut Compiler) -> Option bool { instrs.iter().any(|instr| match instr { Instr::PushFunc(f) => contains_un(f.instrs(asm), asm), - Instr::Prim(Primitive::Un, _) => true, + Instr::Prim(Primitive::Un | Primitive::Anti, _) => true, _ => false, }) } @@ -712,6 +713,24 @@ fn resolve_uns(instrs: EcoVec, comp: &mut Compiler) -> Option + { + let Some(Instr::Prim(Primitive::Anti, span)) = instrs.next() else { + unreachable!() + }; + let mut instrs = f.instrs(&comp.asm).to_vec(); + if f.signature().args >= 2 { + instrs.insert(0, Instr::copy_inline(span)); + instrs.push(Instr::pop_inline(1, span)); + } + let mut inverse = invert_instrs(&instrs, comp)?; + if f.signature().args >= 2 { + inverse.push(Instr::Prim(Primitive::Pop, span)); + } + resolved.extend(inverse); + } Instr::PushFunc(f) => { let instrs = f.instrs(&comp.asm); if !contains_un(instrs, &comp.asm) { @@ -783,7 +802,10 @@ invert_pat!( invert_pat!( invert_un_pattern, - (Instr::PushFunc(f), Instr::Prim(Primitive::Un, _)), + ( + Instr::PushFunc(f), + Instr::Prim(Primitive::Un | Primitive::Anti, _) + ), |input, comp| (input, EcoVec::from(f.instrs(&comp.asm))) ); @@ -814,6 +836,42 @@ fn under_un_pattern<'a>( )) } +fn under_anti_pattern<'a>( + input: &'a [Instr], + g_sig: Signature, + comp: &mut Compiler, +) -> Option<(&'a [Instr], Under)> { + let [Instr::PushFunc(f), Instr::Prim(Primitive::Anti, span), input @ ..] = input else { + return None; + }; + let mut instrs = EcoVec::from(f.instrs(&comp.asm)); + if f.signature().args >= 2 { + instrs.insert(0, Instr::copy_inline(*span)); + instrs.push(Instr::pop_inline(1, *span)); + } + let mut befores = invert_instrs(&instrs, comp)?; + if f.signature().args >= 2 { + befores.push(Instr::Prim(Primitive::Pop, *span)); + } + if let [Instr::PushFunc(_), Instr::PushFunc(_), Instr::Prim(Primitive::SetInverse, _)] = + befores.as_slice() + { + if let Some(under) = under_instrs(&befores, g_sig, comp) { + return Some((input, under)); + } + } + let (befores, mut afters) = if let Some((befores, afters)) = under_instrs(&befores, g_sig, comp) + { + (befores, afters) + } else { + (befores, instrs) + }; + if f.signature().args >= 2 { + afters.push(Instr::Prim(Primitive::Pop, *span)); + } + Some((input, (befores, afters))) +} + fn under_call_pattern<'a>( input: &'a [Instr], g_sig: Signature, diff --git a/src/check.rs b/src/check.rs index 9e038fa3a..ef53e4186 100644 --- a/src/check.rs +++ b/src/check.rs @@ -416,6 +416,10 @@ impl<'a> VirtualEnv<'a> { let sig = self.pop_func()?.signature(); self.handle_args_outputs(sig.outputs, sig.args)?; } + Anti => { + let sig = self.pop_func()?.signature(); + self.handle_args_outputs(sig.args, sig.outputs)?; + } Fold => { let f = self.pop_func()?; self.handle_sig(f.signature())?; diff --git a/src/compile/modifier.rs b/src/compile/modifier.rs index e8289db39..1563dd195 100644 --- a/src/compile/modifier.rs +++ b/src/compile/modifier.rs @@ -596,6 +596,17 @@ impl Compiler { } }}; } + /// Modify instructions for `on`, and return the new signature + fn on(instrs: &mut EcoVec, sig: Signature, span: usize) -> Signature { + let save = if sig.args == 0 { + Instr::push_inline(1, span) + } else { + Instr::copy_inline(span) + }; + instrs.insert(0, save); + instrs.push(Instr::pop_inline(1, span)); + Signature::new(sig.args.max(1), sig.outputs + 1) + } match prim { Dip | Gap | On | By | With | Off | Above | Below => { // Compile operands @@ -634,18 +645,7 @@ impl Compiler { instrs.insert(0, Instr::Prim(Pop, span)); Signature::new(sig.args + 1, sig.outputs) } - On => { - instrs.insert( - 0, - if sig.args == 0 { - Instr::push_inline(1, span) - } else { - Instr::copy_inline(span) - }, - ); - instrs.push(Instr::pop_inline(1, span)); - Signature::new(sig.args.max(1), sig.outputs + 1) - } + On => on(instrs, sig, span), By => { if sig.args > 0 { let mut i = 0; @@ -912,19 +912,55 @@ impl Compiler { } } Un if !self.in_inverse => { - let mut operands = modified.code_operands().cloned(); - let f = operands.next().unwrap(); + let f = modified.code_operands().next().unwrap().clone(); let span = f.span.clone(); self.in_inverse = !self.in_inverse; let f_res = self.compile_operand_word(f); self.in_inverse = !self.in_inverse; - let (new_func, _) = f_res?; + let (mut new_func, _) = f_res?; self.add_span(span.clone()); if let Some(inverted) = invert_instrs(&new_func.instrs, self) { let sig = self.sig_of(&inverted, &span)?; - finish!(inverted, sig); + new_func.instrs = inverted; + finish!(new_func, sig); + } else { + return Err(self.fatal_error(span, "No inverse found")); + } + } + Anti if !self.in_inverse => { + let f = modified.code_operands().next().unwrap().clone(); + let span = f.span.clone(); + + self.in_inverse = !self.in_inverse; + let f_res = self.compile_operand_word(f); + self.in_inverse = !self.in_inverse; + let (mut new_func, f_sig) = f_res?; + let spandex = self.add_span(span.clone()); + if f_sig.args >= 2 { + on(&mut new_func.instrs, f_sig, spandex); + } else { + self.emit_diagnostic( + format!( + "Prefer {} over {} for functions \ + with fewer than 2 arguments", + Primitive::Un.format(), + Primitive::Anti.format() + ), + DiagnosticKind::Style, + span.clone(), + ); + }; + + if let Some(inverted) = invert_instrs(&new_func.instrs, self) { + let mut sig = self.sig_of(&inverted, &span)?; + new_func.instrs = inverted; + if f_sig.args >= 2 { + new_func.instrs.push(Instr::Prim(Primitive::Pop, spandex)); + sig.outputs -= 1; + } + finish!(new_func, sig); } else { return Err(self.fatal_error(span, "No inverse found")); } diff --git a/src/primitive/defs.rs b/src/primitive/defs.rs index 684028d35..96792031c 100644 --- a/src/primitive/defs.rs +++ b/src/primitive/defs.rs @@ -2214,6 +2214,30 @@ primitive!( /// A function's [un]-inverse can be set with [setinv]. /// For more about inverses, see the [Inverse Tutorial](/tutorial/inverses). ([1], Un, InversionModifier, ("un", '°')), + /// Invert the behavior of a function, but keep its signature + /// + /// [un] has a guaruntee that the inverted function will have a signature that is the inverse of original function's signature. For dyadic functions, if we want the inverse to *also* be dyadic, then we have to do some workarounds. We can either include the first argument in the inverted function, or we can use [on]. + /// For example, here are two ways to invert [rotate]. + /// ex: °(↻1) [1 2 3] + /// : ◌°⟜↻ 1 [1 2 3] + /// The first way requires the first argument to be a constant, which is not always applicable. The second way works but it is a bit verbose. + /// [anti] does the [pop][un][on] for you. + /// ex: # Experimental! + /// : ˘↻ 1 [1 2 3] + /// This simplifies some interesting inverses. + /// ex: # Experimental! + /// : ˘+ 1 5 + /// ex: # Experimental! + /// : ⬚@-˘⊏ [0 2 5] "abc" + /// ex: # Experimental! + /// : ⬚@-˘⊡ [1_2 3_4] "xy" + /// ex: # Experimental! + /// : ˘⍥(+1) 3 10 + /// ex: # Experimental! + /// : ˘⊂ 1 [1 2 3] + /// ex! # Experimental! + /// : ˘⊂ 1 [2 3 4] + ([1], Anti, InversionModifier, ("anti", '˘')), /// Set the [un]-compatible inverse of a function /// /// The first function is the uninverted function, and the second function is the inverse. diff --git a/src/primitive/mod.rs b/src/primitive/mod.rs index 7b16c96b8..f2d217c79 100644 --- a/src/primitive/mod.rs +++ b/src/primitive/mod.rs @@ -451,7 +451,7 @@ impl Primitive { use SysOp::*; matches!( self, - (Off | Backward | Above) + (Anti | Off | Backward | Above) | (Tuples | Choose | Permute | Pad) | Struct | (Last | Sort | Chunks | Base | Coordinate | Fft | Case | Layout) @@ -979,6 +979,7 @@ impl Primitive { | Primitive::Above | Primitive::Below | Primitive::Un + | Primitive::Anti | Primitive::Under | Primitive::Content | Primitive::Both diff --git a/todo.md b/todo.md index da9c424b1..c2ff674f9 100644 --- a/todo.md +++ b/todo.md @@ -11,6 +11,7 @@ - Recursive stack macros - `pad` function - Label and format string glyphs? + - Inversion errors - `setinv/setund` unification - Custom `on/by` inverses - Document on and by inverses