From 327e5d2b9e901d3df6af0d1fe518b164a2363e6f Mon Sep 17 00:00:00 2001 From: Kai Schmidt Date: Tue, 19 Nov 2024 14:06:23 -0800 Subject: [PATCH] add inline code macros --- site/text/experimental.md | 19 +++ src/ast.rs | 6 +- src/compile/modifier.rs | 302 ++++++++++++++++++++------------------ src/error.rs | 4 +- src/format.rs | 12 +- src/lsp.rs | 31 ++-- src/parse.rs | 7 +- 7 files changed, 224 insertions(+), 157 deletions(-) diff --git a/site/text/experimental.md b/site/text/experimental.md index db3320a33..eda4f0e58 100644 --- a/site/text/experimental.md +++ b/site/text/experimental.md @@ -14,6 +14,25 @@ StdDev ← √(^0^1^0)‼(÷⧻⟜/+|×.-). StdDev [1 2 3 4] ``` +An inline code macro can be specified by putting a `^` between the `)` and the first `!`. + +```uiua +# Experimental! +(⇌)^‼(⊂1|⊂2) [] +``` + +```uiua +# Experimental! +($"_ ← 5"⊢)^!X +X +``` + +```uiua +# Experimental! +(repr⋅⊢)^!+ +(repr⋅⊢)^!⊓+¯ +``` + ## [derivative](/docs/derivative) and [integral](/docs/integral) These modifiers transform a mathematical expression. diff --git a/src/ast.rs b/src/ast.rs index c6e365c82..2ee4f9748 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -256,6 +256,8 @@ pub struct Comments { pub struct InlineMacro { /// The function pub func: Sp, + /// The span of a `^` that makes this an array macro + pub caret_span: Option, /// The identifier, which consists of only exclamation marks pub ident: Sp, } @@ -414,7 +416,7 @@ impl fmt::Debug for Word { Word::SemanticComment(comment) => write!(f, "{comment}"), Word::OutputComment { i, n, .. } => write!(f, "output_comment({i}/{n})"), Word::Subscript(sub) => sub.fmt(f), - Word::InlineMacro(InlineMacro { ident, func }) => { + Word::InlineMacro(InlineMacro { ident, func, .. }) => { write!(f, "func_macro({:?}{}))", func.value, ident.value) } } @@ -589,7 +591,7 @@ impl fmt::Debug for Modifier { match self { Modifier::Primitive(prim) => prim.fmt(f), Modifier::Ref(refer) => write!(f, "ref({refer:?})"), - Modifier::Macro(mac) => write!(f, "macro({:?})", mac.func), + Modifier::Macro(mac) => write!(f, "macro({:?}{})", mac.func, mac.ident), } } } diff --git a/src/compile/modifier.rs b/src/compile/modifier.rs index 537d225a5..b2e7abdef 100644 --- a/src/compile/modifier.rs +++ b/src/compile/modifier.rs @@ -875,7 +875,7 @@ impl Compiler { )); } }; - self.quote(&code, &"quote".into(), &modified.modifier.span)? + self.quote(&code, Some("quote".into()), &modified.modifier.span)? } Sig => { let (sn, _) = self.monadic_modifier_op(modified)?; @@ -933,17 +933,24 @@ impl Compiler { self.code_meta .inline_macros .insert(mac.func.span, ident_modifier_args(&mac.ident.value)); - // Expand - self.expand_index_macro(None, &mut words, operands, span.clone(), true)?; - // Compile - let node = self.suppress_diagnostics(|comp| comp.words(words))?; - // Add - let sig = self.sig_of(&node, &span)?; - let func = self - .asm - .add_function(FunctionId::Macro(None, span.clone()), sig, node); - let span = self.add_span(span); - Ok(Node::Call(func, span)) + Ok(if mac.caret_span.is_some() { + let root = self.words_sig(words)?; + let code_mac = CodeMacro { + root, + names: Default::default(), + }; + self.code_macro(None, span, operands, code_mac)? + } else { + // Expand + self.expand_index_macro(None, &mut words, operands, span.clone(), true)?; + // Compile + let node = self.suppress_diagnostics(|comp| comp.words(words))?; + // Add + let sig = self.sig_of(&node, &span)?; + let func = (self.asm).add_function(FunctionId::Macro(None, span.clone()), sig, node); + let span = self.add_span(span); + Node::Call(func, span) + }) } fn modifier_ref( &mut self, @@ -1035,133 +1042,7 @@ impl Compiler { } } else if let Some(mac) = self.code_macros.get(&local.index).cloned() { // Code macros - let full_span = (modifier_span.clone()).merge(operands.last().unwrap().span.clone()); - - // Collect operands as strings - let mut operands: Vec> = (operands.into_iter()) - .filter(|w| w.value.is_code()) - .collect(); - if operands.len() == 1 { - let operand = operands.remove(0); - operands = match operand.value { - Word::Pack(pack) => pack - .branches - .into_iter() - .map(|b| b.map(Word::Func)) - .collect(), - word => vec![operand.span.sp(word)], - }; - } - let op_sigs = if mac.root.sig.args == 2 { - // If the macro function has 2 arguments, we pass the signatures - // of the operands as well - let mut sig_data: EcoVec = EcoVec::with_capacity(operands.len() * 2); - // Track the length of the instructions and spans so - // they can be discarded after signatures are calculated - for op in &operands { - let sn = self.word_sig(op.clone()).map_err(|e| { - let message = format!( - "This error occurred while compiling a macro operand. \ - This was attempted because the macro function's \ - signature is {}.", - Signature::new(2, 1) - ); - e.with_info([(message, None)]) - })?; - sig_data.extend_from_slice(&[sn.sig.args as u8, sn.sig.outputs as u8]); - } - // Discard unnecessary instructions and spans - Some(Array::::new([operands.len(), 2], sig_data)) - } else { - None - }; - let formatted: Array = operands - .iter() - .map(|w| { - let mut formatted = format_word(w, &self.asm.inputs); - if let Word::Func(_) = &w.value { - if formatted.starts_with('(') && formatted.ends_with(')') { - formatted = formatted[1..formatted.len() - 1].to_string(); - } - } - Boxed(formatted.trim().into()) - }) - .collect(); - - let mut code = String::new(); - (|| -> UiuaResult { - if let Some(index) = self.node_unbound_index(&mac.root.node) { - let name = self.scope.names.iter().find_map(|(name, local)| { - if local.index == index { - Some(name) - } else { - None - } - }); - let message = if let Some(name) = name { - format!("{} references runtime binding `{}`", r.name.value, name) - } else { - format!("{} references runtime binding", r.name.value) - }; - return Err(self.error(modifier_span.clone(), message)); - } - - let span = self.add_span(modifier_span.clone()); - let env = &mut self.macro_env; - swap(&mut env.asm, &mut self.asm); - env.rt.call_stack.last_mut().unwrap().call_span = span; - - // Run the macro function - if let Some(sigs) = op_sigs { - env.push(sigs); - } - env.push(formatted); - - #[cfg(feature = "native_sys")] - let enabled = - crate::sys::native::set_output_enabled(self.pre_eval_mode != PreEvalMode::Lsp); - - let res = (|| -> UiuaResult { - env.exec(mac.root)?; - - let val = env.pop("macro result")?; - - // Parse the macro output - if let Ok(s) = val.as_string(env, "") { - code = s; - } else { - for row in val.into_rows() { - let s = row.as_string(env, "Code macro output rows must be strings")?; - if code.chars().last().is_some_and(|c| !c.is_whitespace()) { - code.push(' '); - } - code.push_str(&s); - } - } - Ok(()) - })(); - - if let Err(e) = res { - self.errors.push(e); - } - - #[cfg(feature = "native_sys")] - crate::sys::native::set_output_enabled(enabled); - - swap(&mut env.asm, &mut self.asm); - Ok(()) - })() - .map_err(|e| e.trace_macro(r.name.value.clone(), modifier_span.clone()))?; - - // Quote - self.code_meta - .macro_expansions - .insert(full_span, (Some(r.name.value.clone()), code.clone())); - self.suppress_diagnostics(|comp| { - comp.temp_scope(mac.names, None, |comp| { - comp.quote(&code, &r.name.value, &modifier_span) - }) - })? + self.code_macro(Some(r.name.value), modifier_span, operands, mac)? } else if let Some(m) = (self.asm.bindings.get(local.index)).and_then(|binfo| match &binfo.kind { BindingKind::Module(m) => Some(m), @@ -1182,6 +1063,143 @@ impl Compiler { self.comptime_depth -= 1; Ok(node) } + fn code_macro( + &mut self, + mac_name: Option, + modifier_span: CodeSpan, + operands: Vec>, + mac: CodeMacro, + ) -> UiuaResult { + let full_span = (modifier_span.clone()).merge(operands.last().unwrap().span.clone()); + // Collect operands as strings + let mut operands: Vec> = (operands.into_iter()) + .filter(|w| w.value.is_code()) + .collect(); + if operands.len() == 1 { + let operand = operands.remove(0); + operands = match operand.value { + Word::Pack(pack) => pack + .branches + .into_iter() + .map(|b| b.map(Word::Func)) + .collect(), + word => vec![operand.span.sp(word)], + }; + } + let op_sigs = if mac.root.sig.args == 2 { + // If the macro function has 2 arguments, we pass the signatures + // of the operands as well + let mut sig_data: EcoVec = EcoVec::with_capacity(operands.len() * 2); + // Track the length of the instructions and spans so + // they can be discarded after signatures are calculated + for op in &operands { + let sn = self.word_sig(op.clone()).map_err(|e| { + let message = format!( + "This error occurred while compiling a macro operand. \ + This was attempted because the macro function's \ + signature is {}.", + Signature::new(2, 1) + ); + e.with_info([(message, None)]) + })?; + sig_data.extend_from_slice(&[sn.sig.args as u8, sn.sig.outputs as u8]); + } + // Discard unnecessary instructions and spans + Some(Array::::new([operands.len(), 2], sig_data)) + } else { + None + }; + let formatted: Array = operands + .iter() + .map(|w| { + let mut formatted = format_word(w, &self.asm.inputs); + if let Word::Func(_) = &w.value { + if formatted.starts_with('(') && formatted.ends_with(')') { + formatted = formatted[1..formatted.len() - 1].to_string(); + } + } + Boxed(formatted.trim().into()) + }) + .collect(); + + let mut code = String::new(); + (|| -> UiuaResult { + if let Some(index) = self.node_unbound_index(&mac.root.node) { + let name = self.scope.names.iter().find_map(|(name, local)| { + if local.index == index { + Some(name) + } else { + None + } + }); + let message = match (&mac_name, name) { + (Some(mac_name), Some(name)) => { + format!("{} references runtime binding `{}`", mac_name, name) + } + (Some(mac_name), None) => format!("{} references runtime binding", mac_name), + (None, Some(name)) => format!("macro references runtime binding `{}`", name), + (None, None) => "macro references runtime binding".into(), + }; + return Err(self.error(modifier_span.clone(), message)); + } + + let span = self.add_span(modifier_span.clone()); + let env = &mut self.macro_env; + swap(&mut env.asm, &mut self.asm); + env.rt.call_stack.last_mut().unwrap().call_span = span; + + // Run the macro function + if let Some(sigs) = op_sigs { + env.push(sigs); + } + env.push(formatted); + + #[cfg(feature = "native_sys")] + let enabled = + crate::sys::native::set_output_enabled(self.pre_eval_mode != PreEvalMode::Lsp); + + let res = (|| -> UiuaResult { + env.exec(mac.root)?; + + let val = env.pop("macro result")?; + + // Parse the macro output + if let Ok(s) = val.as_string(env, "") { + code = s; + } else { + for row in val.into_rows() { + let s = row.as_string(env, "Code macro output rows must be strings")?; + if code.chars().last().is_some_and(|c| !c.is_whitespace()) { + code.push(' '); + } + code.push_str(&s); + } + } + Ok(()) + })(); + + if let Err(e) = res { + self.errors.push(e); + } + + #[cfg(feature = "native_sys")] + crate::sys::native::set_output_enabled(enabled); + + swap(&mut env.asm, &mut self.asm); + Ok(()) + })() + .map_err(|e| e.trace_macro(mac_name.clone(), modifier_span.clone()))?; + + // Quote + self.code_meta + .macro_expansions + .insert(full_span, (mac_name.clone(), code.clone())); + self.suppress_diagnostics(|comp| { + comp.temp_scope(mac.names, None, |comp| { + comp.quote(&code, mac_name, &modifier_span) + }) + }) + } fn node_unbound_index(&self, node: &Node) -> Option { match node { Node::Run(nodes) => nodes.iter().find_map(|node| self.node_unbound_index(node)), @@ -1246,7 +1264,7 @@ impl Compiler { words.retain(|word| !matches!(word.value, Word::Placeholder(_))); error.map_or(Ok(()), Err) } - fn quote(&mut self, code: &str, name: &Ident, span: &CodeSpan) -> UiuaResult { + fn quote(&mut self, code: &str, name: Option, span: &CodeSpan) -> UiuaResult { let (items, errors, _) = parse( code, InputSrc::Macro(span.clone().into()), @@ -1255,7 +1273,7 @@ impl Compiler { if !errors.is_empty() { return Err(UiuaErrorKind::Parse(errors, self.asm.inputs.clone().into()) .error() - .trace_macro(name.clone(), span.clone())); + .trace_macro(name, span.clone())); } let root_node_len = self.asm.root.len(); @@ -1268,7 +1286,7 @@ impl Compiler { } let res = self .items(items, true) - .map_err(|e| e.trace_macro(name.clone(), span.clone())); + .map_err(|e| e.trace_macro(name, span.clone())); self.comptime_depth -= 1; self.pre_eval_mode = pre_eval_mod; // Extract generated root node diff --git a/src/error.rs b/src/error.rs index eef2b6120..d2705b4ae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -154,9 +154,9 @@ impl UiuaError { self.trace.push(frame); self } - pub(crate) fn trace_macro(mut self, name: Ident, span: CodeSpan) -> Self { + pub(crate) fn trace_macro(mut self, name: Option, span: CodeSpan) -> Self { let frame = TraceFrame { - id: Some(FunctionId::Macro(Some(name), span.clone())), + id: Some(FunctionId::Macro(name, span.clone())), span: Span::Code(span), }; self.trace.push(frame); diff --git a/src/format.rs b/src/format.rs index f423ae569..059fbc3f4 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1278,8 +1278,15 @@ impl<'a> Formatter<'a> { } self.push(&word.span, &s); } - Word::InlineMacro(InlineMacro { ident, func }) => { + Word::InlineMacro(InlineMacro { + func, + caret_span, + ident, + }) => { self.func(&func.value, depth); + if let Some(span) = caret_span { + self.push(span, "^"); + } self.push(&ident.span, &ident.value); } } @@ -1360,6 +1367,9 @@ impl<'a> Formatter<'a> { Modifier::Ref(r) => self.format_ref(r), Modifier::Macro(mac) => { self.func(&mac.func.value, depth); + if let Some(span) = &mac.caret_span { + self.push(span, "^"); + } self.push(&mac.ident.span, &mac.ident.value); } } diff --git a/src/lsp.rs b/src/lsp.rs index 3252d98b8..a71d6ad0c 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -652,8 +652,12 @@ impl Spanner { Modifier::Ref(r) => spans.extend(self.ref_spans(r)), Modifier::Macro(mac) => { spans.extend(self.func_spans(&mac.func.value, &mac.func.span)); - let ident_span = (mac.ident.span.clone()) - .sp(SpanKind::MacroDelim(ident_modifier_args(&mac.ident.value))); + let mac_delim_kind = + SpanKind::MacroDelim(ident_modifier_args(&mac.ident.value)); + if let Some(span) = &mac.caret_span { + spans.push(span.clone().sp(mac_delim_kind.clone())); + } + let ident_span = (mac.ident.span.clone()).sp(mac_delim_kind); spans.push(ident_span); } } @@ -690,9 +694,12 @@ impl Spanner { } Modifier::Macro(mac) => { spans.extend(self.func_spans(&mac.func.value, &mac.func.span)); - let ident_span = (mac.ident.span.clone()).sp(SpanKind::MacroDelim( - ident_modifier_args(&mac.ident.value), - )); + let mac_delim_kind = + SpanKind::MacroDelim(ident_modifier_args(&mac.ident.value)); + if let Some(span) = &mac.caret_span { + spans.push(span.clone().sp(mac_delim_kind.clone())); + } + let ident_span = (mac.ident.span.clone()).sp(mac_delim_kind); spans.push(ident_span); spans.push(sub.n.clone().map(|n| SpanKind::Subscript(None, n))); } @@ -709,11 +716,17 @@ impl Spanner { spans.push(sub.n.clone().map(|n| SpanKind::Subscript(None, n))); } }, - Word::InlineMacro(InlineMacro { ident, func }) => { - let ident_span = (ident.span.clone()) - .sp(SpanKind::MacroDelim(ident_modifier_args(&ident.value))); - spans.push(ident_span); + Word::InlineMacro(InlineMacro { + ident, + caret_span, + func, + }) => { spans.extend(self.func_spans(&func.value, &func.span)); + let mac_delim_kind = SpanKind::MacroDelim(ident_modifier_args(&ident.value)); + if let Some(span) = caret_span { + spans.push(span.clone().sp(mac_delim_kind.clone())); + } + spans.push(ident.span.clone().sp(mac_delim_kind)); } } } diff --git a/src/parse.rs b/src/parse.rs index 8e69a63e2..a9e3cf0ab 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1252,13 +1252,18 @@ impl<'i> Parser<'i> { closed: end.value, }; let reset = self.index; + let caret_span = self.exact(Caret.into()); if let Some(ident) = self .ident() .filter(|ident| ident.value.chars().all(|c| "!‼".contains(c))) { let func = outer_span.clone().sp(func); outer_span = outer_span.merge(ident.span.clone()); - outer_span.sp(Word::InlineMacro(InlineMacro { ident, func })) + outer_span.sp(Word::InlineMacro(InlineMacro { + func, + caret_span, + ident, + })) } else { self.index = reset; outer_span.sp(Word::Func(func))