Skip to content

Commit

Permalink
add anti modifier
Browse files Browse the repository at this point in the history
  • Loading branch information
kaikalii committed Sep 22, 2024
1 parent 09330c5 commit 3c30060
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 19 deletions.
15 changes: 15 additions & 0 deletions site/primitives.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
62 changes: 60 additions & 2 deletions src/algorithm/invert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!
];

Expand Down Expand Up @@ -691,7 +692,7 @@ fn resolve_uns(instrs: EcoVec<Instr>, comp: &mut Compiler) -> Option<EcoVec<Inst
fn contains_un(instrs: &[Instr], asm: &Assembly) -> 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,
})
}
Expand All @@ -712,6 +713,24 @@ fn resolve_uns(instrs: EcoVec<Instr>, comp: &mut Compiler) -> Option<EcoVec<Inst
let inverse = invert_instrs(&instrs, comp)?;
resolved.extend(inverse);
}
Instr::PushFunc(f)
if (instrs.peek())
.is_some_and(|instr| matches!(instr, Instr::Prim(Primitive::Anti, _))) =>
{
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) {
Expand Down Expand Up @@ -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)))
);

Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())?;
Expand Down
68 changes: 52 additions & 16 deletions src/compile/modifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,17 @@ impl Compiler {
}
}};
}
/// Modify instructions for `on`, and return the new signature
fn on(instrs: &mut EcoVec<Instr>, 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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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"));
}
Expand Down
24 changes: 24 additions & 0 deletions src/primitive/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/primitive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -979,6 +979,7 @@ impl Primitive {
| Primitive::Above
| Primitive::Below
| Primitive::Un
| Primitive::Anti
| Primitive::Under
| Primitive::Content
| Primitive::Both
Expand Down
1 change: 1 addition & 0 deletions todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3c30060

Please sign in to comment.