Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/domain/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ fn render_expression(expr: &Expression) -> String {
.collect();
format!("[{}]", items.join(", "))
}
Expression::Hole(_) => "?".to_string(),
Expression::Separator => String::new(),
}
}
Expand Down
24 changes: 23 additions & 1 deletion src/formatting/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ pub fn render_descriptive<'i>(descriptive: &'i Descriptive, renderer: &dyn Rende
render_fragments(&sub.fragments, renderer)
}

/// Render the document's metadata header, coloured, to a String.
pub fn render_header<'i>(metadata: &'i Metadata, renderer: &dyn Render) -> String {
let mut sub = Formatter::new(78);
sub.format_header(metadata);
render_fragments(&sub.fragments, renderer)
.trim_end()
.to_string()
}

/// Render a procedure or section title with its leading `#` marker.
pub fn render_title(title: &str, renderer: &dyn Render) -> String {
let mut sub = Formatter::new(78);
Expand Down Expand Up @@ -206,6 +215,16 @@ pub fn render_scope<'i>(scope: &'i Scope, renderer: &dyn Render) -> String {
.to_string()
}

/// Render a procedure's description: its prose paragraphs, blank-line
/// separated, exactly as written in the source.
pub fn render_description<'i>(paragraphs: &'i [Paragraph<'i>], renderer: &dyn Render) -> String {
let mut sub = Formatter::new(78);
sub.append_paragraphs(paragraphs);
render_fragments(&sub.fragments, renderer)
.trim_end()
.to_string()
}

/// Render step's without descending into nested subscopes.
pub fn render_step<'i>(scope: &'i Scope, renderer: &dyn Render) -> String {
let mut sub = Formatter::new(78);
Expand Down Expand Up @@ -711,7 +730,7 @@ impl<'i> Formatter<'i> {
self.add_fragment_reference(Syntax::Forma, forma.value)
}

fn append_paragraphs(&mut self, paragraphs: &'i Vec<Paragraph>) {
fn append_paragraphs(&mut self, paragraphs: &'i [Paragraph<'i>]) {
for (i, paragraph) in paragraphs
.iter()
.enumerate()
Expand Down Expand Up @@ -1152,6 +1171,9 @@ impl<'i> Formatter<'i> {
}
Expression::Pair(pair, _) => self.append_pair(pair),
Expression::List(elements, _) => self.append_list(elements),
Expression::Hole(_) => {
self.add_fragment_reference(Syntax::Variable, "?");
}
Expression::Separator => {}
}
}
Expand Down
35 changes: 35 additions & 0 deletions src/language/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,34 @@ pub enum Genus<'i> {
List(Forma<'i>),
}

impl<'i> Genus<'i> {
/// The number of distinct values this genus carries. As the `requires`
/// of a procedure's signature this is the procedure's arity.
pub fn cardinality(&self) -> usize {
match self {
Genus::Unit => 0,
Genus::Single(_) => 1,
Genus::List(_) => 1,
Genus::Tuple(formas) => formas.len(),
Genus::Naked(formas) => formas.len(),
}
}

pub fn formae(&self) -> Vec<&Forma<'i>> {
match self {
Genus::Unit => Vec::new(),
Genus::Single(forma) => vec![forma],
Genus::List(forma) => vec![forma],
Genus::Tuple(formas) => formas
.iter()
.collect(),
Genus::Naked(formas) => formas
.iter()
.collect(),
}
}
}

#[derive(Eq, Debug, PartialEq)]
pub struct Signature<'i> {
pub requires: Genus<'i>,
Expand Down Expand Up @@ -418,6 +446,7 @@ pub enum Expression<'i> {
Binding(Box<Expression<'i>>, Vec<Identifier<'i>>, Span),
Pair(Box<Pair<'i>>, Span),
List(Vec<Expression<'i>>, Span),
Hole(Span),
Separator,
}

Expand All @@ -441,6 +470,7 @@ impl PartialEq for Expression<'_> {
}
(Expression::Pair(a, _), Expression::Pair(b, _)) => a == b,
(Expression::List(a, _), Expression::List(b, _)) => a == b,
(Expression::Hole(_), Expression::Hole(_)) => true,
(Expression::Separator, Expression::Separator) => true,
_ => false,
}
Expand Down Expand Up @@ -506,6 +536,11 @@ pub(crate) fn validate_forma(input: &str, span: Span) -> Option<Forma<'_>> {
return None;
}

// wildcard, represents "any" type.
if input == "*" {
return Some(Forma { value: input, span });
}

let mut cs = input.chars();

if !cs
Expand Down
5 changes: 4 additions & 1 deletion src/linking/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ fn link_operation<'i>(
link_operation(item, library, problems);
}
}
Operation::Variable(_) | Operation::Number(_) | Operation::Multiline(_, _) => {}
Operation::Variable(_)
| Operation::Number(_)
| Operation::Multiline(_, _)
| Operation::Hole => {}
}
}

Expand Down
46 changes: 46 additions & 0 deletions src/parsing/checks/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,40 @@ fn signatures() {
);
}

#[test]
fn signature_wildcards() {
let mut input = Parser::new();
input.initialize("* -> *");
assert_eq!(
input.read_signature(),
Ok(Signature {
requires: Genus::Single(Forma::new("*")),
provides: Genus::Single(Forma::new("*"))
})
);

input.initialize("(A, *) -> [*]");
assert_eq!(
input.read_signature(),
Ok(Signature {
requires: Genus::Tuple(vec![Forma::new("A"), Forma::new("*")]),
provides: Genus::List(Forma::new("*"))
})
);
}

#[test]
fn hole_as_expression() {
// A bare `?` parses as a Hole outside argument position too — in a code
// block or as a binding value, not only inside an invocation's parens.
let mut input = Parser::new();
input.initialize("?");
assert_eq!(
input.read_expression(),
Ok(Expression::Hole(Span::default()))
);
}

#[test]
fn declaration_simple() {
let mut input = Parser::new();
Expand Down Expand Up @@ -477,6 +511,18 @@ fn reading_invocations() {
})
);

// A `?` argument is a deferred value: it parses to a Hole in parameter
// position, satisfying the callee's arity without naming a value.
input.initialize("<resume>(?)");
let result = input.read_invocation();
assert_eq!(
result,
Ok(Invocation {
target: Target::Local(Identifier::new("resume")),
parameters: Some(vec![Expression::Hole(Span::default())])
})
);

// We don't have real support for this yet, but syntactically we will
// support the idea of invoking a procedure at an external URL, so we
// have this case as a placeholder.
Expand Down
8 changes: 8 additions & 0 deletions src/parsing/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,10 @@ impl<'i> Parser<'i> {
return Err(ParsingError::InvalidForeach(Span::new(self.offset, 0)));
} else if content.starts_with('[') {
self.read_bracket_expression()
} else if content.starts_with('?') {
self.advance(1);
let span = self.span_since(start);
Ok(Expression::Hole(span))
} else if is_numeric(content) {
let numeric = self.read_numeric()?;
let span = self.span_since(start);
Expand Down Expand Up @@ -2627,6 +2631,10 @@ impl<'i> Parser<'i> {
let decimal = outer.read_decimal_part()?;
let span = outer.span_since(param_start);
params.push(Expression::Number(Numeric::Integral(decimal.number), span));
} else if content.starts_with('?') {
outer.advance(1);
let span = outer.span_since(param_start);
params.push(Expression::Hole(span));
} else {
let name = outer.read_identifier()?;
let span = name.span;
Expand Down
Loading