Skip to content

Commit

Permalink
placeholders now require signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
kaikalii committed Oct 22, 2023
1 parent 6059844 commit 1c5e63a
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 53 deletions.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 10 additions & 6 deletions site/src/tutorial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ fn TutorialControlFlow() -> impl IntoView {
view! {
<Title text="Control Flow - Uiua Docs"/>
<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):
Expand Down Expand Up @@ -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/>
Expand Down
4 changes: 2 additions & 2 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub enum Word {
Ocean(Vec<Sp<Primitive>>),
Primitive(Primitive),
Modified(Box<Modified>),
Placeholder,
Placeholder(Signature),
Comment(String),
Spaces,
}
Expand Down Expand Up @@ -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),
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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)?,
Expand Down
16 changes: 10 additions & 6 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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);
}
Expand Down
14 changes: 7 additions & 7 deletions src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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('.');
Expand Down Expand Up @@ -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();
}
Expand All @@ -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();
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
Expand Down
12 changes: 9 additions & 3 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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}>"),
Expand Down Expand Up @@ -405,6 +409,7 @@ pub enum FunctionId {
Primitive(Primitive),
Constant,
Main,
Temp,
}

impl PartialEq<&str> for FunctionId {
Expand Down Expand Up @@ -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"),
}
}
}
2 changes: 1 addition & 1 deletion src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 16 additions & 13 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
38 changes: 25 additions & 13 deletions src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 1c5e63a

Please sign in to comment.