From 1c5e63a8269b2cb06e39c0aed8c3af475f366fcd Mon Sep 17 00:00:00 2001 From: Kai Schmidt Date: Sat, 21 Oct 2023 21:52:36 -0700 Subject: [PATCH] placeholders now require signatures --- changelog.md | 4 ++++ site/src/tutorial.rs | 16 ++++++++++------ src/ast.rs | 4 ++-- src/check.rs | 10 ++++++++-- src/compile.rs | 16 ++++++++++------ src/format.rs | 14 +++++++------- src/function.rs | 12 +++++++++--- src/lsp.rs | 2 +- src/parse.rs | 29 ++++++++++++++++------------- src/run.rs | 38 +++++++++++++++++++++++++------------- 10 files changed, 92 insertions(+), 53 deletions(-) diff --git a/changelog.md b/changelog.md index b6b99a859..a93fb02ae 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,10 @@ Uiua is not yet stable. +## 0.0.22 - 2023-10-21 +### Language +- Custom modifier placeholders (`^`) must now be immediately followed by a signature. This reduces the number of signatures that have to be declared everywhere else. + ## 0.0.21 - 2023-10-21 ### Language - **Massive Change** - Functions are no longer first-class values. This has many implications: diff --git a/site/src/tutorial.rs b/site/src/tutorial.rs index c2a9488e9..0f9964bae 100644 --- a/site/src/tutorial.rs +++ b/site/src/tutorial.rs @@ -704,7 +704,7 @@ fn TutorialControlFlow() -> impl IntoView { view! { <h1>"Control Flow"</h1> - <p>"Uiua, and array languages in generall, require much less control flow than other programming languages. Most operations that would be loops in other languages are simply operations on arrays. Because boolean operations return numbers, a lot of checks that would be done with "<code>"if"</code>" statements in other languages become mathematical or indexing operations in array languages."</p> + <p>"Uiua, and array languages in general, require much less control flow than other programming languages. Most operations that would be loops in other languages are simply operations on arrays. Because boolean operations return numbers, a lot of checks that would be done with "<code>"if"</code>" statements in other languages become mathematical or indexing operations in array languages."</p> <p>"For example, if you wanted to split an array of numbers into an array of odds and an array of evens, you might do it like this in a language like Python:"</p> <code class="code-block">"\ def splitArray(array): @@ -862,23 +862,27 @@ fn TutorialCustomModifiers() -> impl IntoView { <p>"But what if you want to define functions that use other functions?"</p> <h2 id="placeholders-and-bangs">"Placeholders and "<code>"!"</code>"s"</h2> - <p>"Anywhere you can put a built-in or inline function, you can also put a "<code>"^"</code>". This is called a "<em>"placeholder"</em>"."</p> + <p>"Anywhere you can put a built-in or inline function, you can also put a "<code>"^"</code>". This is called a "<em>"placeholder"</em>". The "<code>"^"</code>" must be followed by a signature declaration, where the "<code>"^"</code>" replaces the "<code>"|"</code>"."</p> <p>"Any named function with "<code>"^"</code>"s in it becomes a modifier."</p> <p>"However, there is one additional requirement: custom modifiers must have names that end in as many "<code>"!"</code>"s as the number of functions they take."</p> <p>"Lets look at a simple example using "<Prim prim=Reduce/>". It reduces a function over the numbers up to the given range."</p> <Editor example="\ -ReduceRange! ← |1 /^+1⇡ +ReduceRange! ← /^2+1⇡ ReduceRange!+5 ReduceRange!×4"/> - <p>"Custom modifiers "<em>"must"</em>" have stack signatures declared."</p> + <p>"Here is another simple example which calls a function on a reversed version of each row of an array."</p> + <Editor example="\ +OnRev! ← ≡⍜⇌^1 +OnRev!(↘1) ↯3_4⇡12 +OnRev!(⊂π) ↯3_4⇡12"/> <p>"A custom modifier can take as many functions as you want."</p> <Editor example="\ -F!!! ← |2 ⊂/^⊃^^ +F!!! ← ⊂/^2⊃^2^2 F!!!+×⊂ [1 2 3][4 5 6]"/> <p>"Each "<code>"^"</code>" refers to a different function. If you want to use that function more than once in the modifier, you'll have to get creative."</p> <p>"Here, we reduce with the same function multiple times by using "<Prim prim=Repeat/>"."</p> <Editor example="\ -ReduceAll! ← |1 ⍥(|1 /^)⧻△. +ReduceAll! ← ⍥/^2⧻△. ReduceAll!+[1_2_3 4_5_6]"/> <br/> diff --git a/src/ast.rs b/src/ast.rs index 1211ec5a9..5aee8f5aa 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -38,7 +38,7 @@ pub enum Word { Ocean(Vec<Sp<Primitive>>), Primitive(Primitive), Modified(Box<Modified>), - Placeholder, + Placeholder(Signature), Comment(String), Spaces, } @@ -79,7 +79,7 @@ impl fmt::Debug for Word { Word::Modified(modified) => modified.fmt(f), Word::Spaces => write!(f, "' '"), Word::Comment(comment) => write!(f, "# {comment}"), - Word::Placeholder => write!(f, "^"), + Word::Placeholder(sig) => write!(f, "^{}.{}", sig.args, sig.outputs), } } } diff --git a/src/check.rs b/src/check.rs index bf5390ccd..058c3ee80 100644 --- a/src/check.rs +++ b/src/check.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, cmp::Ordering}; use crate::{ array::Array, - function::{Function, Instr, Signature}, + function::{Function, FunctionId, Instr, Signature}, primitive::Primitive, value::Value, }; @@ -105,7 +105,13 @@ impl<'a> VirtualEnv<'a> { self.handle_args_outputs(*count, 0)? } Instr::PushTempFunctions(_) | Instr::PopTempFunctions(_) => {} - Instr::GetTempFunction(_) => return Err("custom modifier".into()), + Instr::GetTempFunction { sig, .. } => { + self.function_stack.push(Cow::Owned(Function::new( + FunctionId::Temp, + Vec::new(), + *sig, + ))); + } Instr::PopTempInline { count, .. } | Instr::PopTempUnder { count, .. } | Instr::CopyTempInline { count, .. } => self.handle_args_outputs(0, *count)?, diff --git a/src/compile.rs b/src/compile.rs index 5efddf3b1..09744a268 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -452,10 +452,14 @@ impl Uiua { Word::Ocean(prims) => self.ocean(prims, call)?, Word::Primitive(p) => self.primitive(p, word.span, call)?, Word::Modified(m) => self.modified(*m, call)?, - Word::Placeholder => { - self.push_instr(Instr::GetTempFunction(0)); + Word::Placeholder(sig) => { + let span = self.add_span(word.span); + self.push_instr(Instr::GetTempFunction { + offset: 0, + sig, + span, + }); if call { - let span = self.add_span(word.span); self.push_instr(Instr::Call(span)); } } @@ -854,8 +858,8 @@ fn words_look_pervasive(words: &[Sp<Word>]) -> bool { fn increment_placeholders(instrs: &mut [Instr]) { let mut curr = 0; for instr in instrs { - if let Instr::GetTempFunction(i) = instr { - *i = curr; + if let Instr::GetTempFunction { offset, .. } = instr { + *offset = curr; curr += 1; } } @@ -865,7 +869,7 @@ fn count_temp_functions(instrs: &[Instr]) -> usize { let mut count = 0; for instr in instrs { match instr { - Instr::GetTempFunction(_) => count += 1, + Instr::GetTempFunction { .. } => count += 1, Instr::PushFunc(f) if matches!(f.id, FunctionId::Anonymous(_)) => { count += count_temp_functions(&f.instrs); } diff --git a/src/format.rs b/src/format.rs index a06a952b4..3ba01e38e 100644 --- a/src/format.rs +++ b/src/format.rs @@ -446,15 +446,15 @@ impl<'a> Formatter<'a> { self.output.push(' '); } if let Some(sig) = &binding.signature { - self.format_signature(sig.value, true); + self.format_signature('|', sig.value, true); } self.format_words(&binding.words, true, 0); } Item::ExtraNewlines(_) => {} } } - fn format_signature(&mut self, sig: Signature, trailing_space: bool) { - self.output.push('|'); + fn format_signature(&mut self, init_char: char, sig: Signature, trailing_space: bool) { + self.output.push(init_char); self.output.push_str(&sig.args.to_string()); if sig.outputs != 1 { self.output.push('.'); @@ -540,7 +540,7 @@ impl<'a> Formatter<'a> { Word::Func(func) => { self.output.push('('); if let Some(sig) = &func.signature { - self.format_signature(sig.value, func.lines.len() <= 1); + self.format_signature('|', sig.value, func.lines.len() <= 1); if func.lines.is_empty() { self.output.pop(); } @@ -555,7 +555,7 @@ impl<'a> Formatter<'a> { self.output.push('|'); } if let Some(sig) = &branch.value.signature { - self.format_signature(sig.value, branch.value.lines.len() <= 1); + self.format_signature('|', sig.value, branch.value.lines.len() <= 1); if branch.value.lines.is_empty() { self.output.pop(); } @@ -585,7 +585,7 @@ impl<'a> Formatter<'a> { ); self.format_words(&m.operands, true, depth); } - Word::Placeholder => self.push(&word.span, "^"), + Word::Placeholder(sig) => self.format_signature('^', *sig, false), Word::Spaces => self.push(&word.span, " "), Word::Comment(comment) => { let beginning_of_line = self @@ -736,7 +736,7 @@ fn word_is_multiline(word: &Word) -> bool { Word::Ocean(_) => false, Word::Primitive(_) => false, Word::Modified(m) => m.operands.iter().any(|word| word_is_multiline(&word.value)), - Word::Placeholder => false, + Word::Placeholder(_) => false, Word::Comment(_) => false, Word::Spaces => false, } diff --git a/src/function.rs b/src/function.rs index 8af0eea1e..8010b2f57 100644 --- a/src/function.rs +++ b/src/function.rs @@ -29,7 +29,11 @@ pub enum Instr { }, PushTempFunctions(usize), PopTempFunctions(usize), - GetTempFunction(usize), + GetTempFunction { + offset: usize, + sig: Signature, + span: usize, + }, Dynamic(DynamicFunction), PushTempUnder { count: usize, @@ -131,7 +135,7 @@ impl Hash for Instr { Instr::Switch { count, .. } => count.hash(state), Instr::PushTempFunctions(count) => count.hash(state), Instr::PopTempFunctions(count) => count.hash(state), - Instr::GetTempFunction(offset) => offset.hash(state), + Instr::GetTempFunction { offset, .. } => offset.hash(state), Instr::Dynamic(f) => f.id.hash(state), Instr::PushTempUnder { count, .. } => count.hash(state), Instr::PopTempUnder { count, .. } => count.hash(state), @@ -200,7 +204,7 @@ impl fmt::Display for Instr { Instr::Switch { count, .. } => write!(f, "<switch {count}>"), Instr::PushTempFunctions(count) => write!(f, "<push {count} functions>"), Instr::PopTempFunctions(count) => write!(f, "<pop {count} functions>"), - Instr::GetTempFunction(offset) => write!(f, "<get function at {offset}>"), + Instr::GetTempFunction { offset, .. } => write!(f, "<get function at {offset}>"), Instr::Dynamic(df) => write!(f, "{df:?}"), Instr::PushTempUnder { count, .. } => write!(f, "<push under {count}>"), Instr::PopTempUnder { count, .. } => write!(f, "<pop under {count}>"), @@ -405,6 +409,7 @@ pub enum FunctionId { Primitive(Primitive), Constant, Main, + Temp, } impl PartialEq<&str> for FunctionId { @@ -436,6 +441,7 @@ impl fmt::Display for FunctionId { FunctionId::Primitive(prim) => write!(f, "{prim}"), FunctionId::Constant => write!(f, "constant"), FunctionId::Main => write!(f, "main"), + FunctionId::Temp => write!(f, "temp"), } } } diff --git a/src/lsp.rs b/src/lsp.rs index 2bb1c1a84..383982709 100644 --- a/src/lsp.rs +++ b/src/lsp.rs @@ -109,7 +109,7 @@ fn words_spans(words: &[Sp<Word>]) -> Vec<Sp<SpanKind>> { } Word::Spaces => spans.push(word.span.clone().sp(SpanKind::Whitespace)), Word::Comment(_) => spans.push(word.span.clone().sp(SpanKind::Comment)), - Word::Placeholder => spans.push(word.span.clone().sp(SpanKind::Placeholder)), + Word::Placeholder(_) => spans.push(word.span.clone().sp(SpanKind::Placeholder)), } } spans diff --git a/src/parse.rs b/src/parse.rs index 7aa6fddfa..585477874 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -237,7 +237,7 @@ impl Parser { } self.try_spaces(); // Signature - let sig = self.try_signature(); + let sig = self.try_signature(Bar); // Words let words = self.try_words().unwrap_or_default(); match words.as_slice() { @@ -297,10 +297,17 @@ impl Parser { } Some(ident) } - fn try_signature(&mut self) -> Option<Sp<Signature>> { - let start = self.try_exact(Bar)?; + fn try_signature(&mut self, initial_token: AsciiToken) -> Option<Sp<Signature>> { + let start = self.try_exact(initial_token)?; self.try_spaces(); - let (args, outs) = if let Some(sn) = self.try_num() { + let (args, outs) = self.sig_inner(); + let end = self.prev_span(); + self.try_spaces(); + let span = start.merge(end); + Some(span.sp(Signature::new(args, outs))) + } + fn sig_inner(&mut self) -> (usize, usize) { + if let Some(sn) = self.try_num() { if let Some((a, o)) = sn.value.0.split_once('.') { let a = match a.parse() { Ok(a) => a, @@ -333,11 +340,7 @@ impl Parser { } else { self.errors.push(self.expected([Expectation::ArgOutCount])); (1usize, 1usize) - }; - let end = self.prev_span(); - self.try_spaces(); - let span = start.merge(end); - Some(span.sp(Signature::new(args, outs))) + } } fn try_words(&mut self) -> Option<Vec<Sp<Word>>> { let mut words: Vec<Sp<Word>> = Vec::new(); @@ -540,8 +543,8 @@ impl Parser { }) } fn try_placeholder(&mut self) -> Option<Sp<Word>> { - let span = self.try_exact(Caret)?; - Some(span.sp(Word::Placeholder)) + let sig = self.try_signature(Caret)?; + Some(sig.map(Word::Placeholder)) } fn try_term(&mut self) -> Option<Sp<Word>> { Some(if let Some(prim) = self.try_prim() { @@ -688,7 +691,7 @@ impl Parser { } fn func_contents(&mut self) -> FunctionContents { while self.try_exact(Newline).is_some() || self.try_spaces().is_some() {} - let signature = self.try_signature(); + let signature = self.try_signature(Bar); let lines = self.multiline_words(); let start = signature .as_ref() @@ -761,7 +764,7 @@ pub(crate) fn count_placeholders(words: &[Sp<Word>]) -> usize { let mut count = 0; for word in words { match &word.value { - Word::Placeholder => count += 1, + Word::Placeholder(_) => count += 1, Word::Strand(items) => count += count_placeholders(items), Word::Array(arr) => { for line in &arr.lines { diff --git a/src/run.rs b/src/run.rs index aa4bbe8f8..2e56e15cf 100644 --- a/src/run.rs +++ b/src/run.rs @@ -496,19 +496,31 @@ code: .truncate(self.temp_function_stack.len() - n); Ok(()) } - &Instr::GetTempFunction(i) => (|| { - let f = self - .temp_function_stack - .get(self.temp_function_stack.len() - 1 - i) - .ok_or_else(|| { - self.error( - "Error getting placeholder function. \ - This is a bug in the interpreter.", - ) - })?; - self.function_stack.push(f.clone()); - Ok(()) - })(), + &Instr::GetTempFunction { offset, sig, span } => { + self.push_span(span, None); + let res = (|| { + let f = self + .temp_function_stack + .get(self.temp_function_stack.len() - 1 - offset) + .ok_or_else(|| { + self.error( + "Error getting placeholder function. \ + This is a bug in the interpreter.", + ) + })?; + let f_sig = f.signature(); + if f_sig != sig { + return Err(self.error(format!( + "Function signature {f_sig} does not match \ + placeholder signature {sig}" + ))); + } + self.function_stack.push(f.clone()); + Ok(()) + })(); + self.pop_span(); + res + } Instr::Dynamic(df) => df.f.clone()(self), &Instr::PushTempUnder { count, span } => (|| { self.push_span(span, None);