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
60 changes: 28 additions & 32 deletions src/domain/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,26 +146,18 @@ impl<'i> Scope<'i> {
}
}

/// Returns the tablet pairs if this is a CodeBlock containing a single
/// list whose elements are all labelled values.
/// Returns the tablet entries if this is a CodeBlock containing a single
/// tablet.
pub fn tablet(&self) -> Option<Vec<&Pair<'i>>> {
match self {
Scope::CodeBlock { expressions, .. } => {
if expressions.len() == 1 {
if let Expression::List(elements, _) = &expressions[0] {
let pairs: Vec<&Pair<'i>> = elements
.iter()
.filter_map(|element| {
if let Expression::Pair(pair, _) = element {
Some(pair.as_ref())
} else {
None
}
})
.collect();
if !pairs.is_empty() && pairs.len() == elements.len() {
return Some(pairs);
}
if let [Expression::Tablet(pairs, _)] = expressions.as_slice() {
if !pairs.is_empty() {
return Some(
pairs
.iter()
.collect(),
);
}
}
None
Expand Down Expand Up @@ -382,6 +374,16 @@ fn render_expression(expr: &Expression) -> String {
.collect();
format!("({})", items.join(", "))
}
Expression::Tablet(pairs, _) => {
if pairs.is_empty() {
return "[=]".to_string();
}
let entries: Vec<_> = pairs
.iter()
.map(|pair| format!("\"{}\" = {}", pair.label, render_expression(&pair.value)))
.collect();
format!("[{}]", entries.join(", "))
}
Expression::Hole(_) => "?".to_string(),
Expression::Unit(_) => "()".to_string(),
Expression::Separator => String::new(),
Expand All @@ -407,26 +409,20 @@ impl<'i> Paragraph<'i> {
targets
}

/// Returns tablet pairs if a `CodeInline` in this paragraph is a single
/// list whose elements are all labelled values.
/// Returns tablet entries if a `CodeInline` in this paragraph is a single
/// tablet.
pub fn tablet(&self) -> Option<Vec<&Pair<'i>>> {
for d in &self.0 {
if let Descriptive::CodeInline(exprs) = d {
let [Expression::List(elements, _)] = exprs.as_slice() else {
let [Expression::Tablet(pairs, _)] = exprs.as_slice() else {
continue;
};
let pairs: Vec<&Pair<'i>> = elements
.iter()
.filter_map(|element| {
if let Expression::Pair(pair, _) = element {
Some(pair.as_ref())
} else {
None
}
})
.collect();
if !pairs.is_empty() && pairs.len() == elements.len() {
return Some(pairs);
if !pairs.is_empty() {
return Some(
pairs
.iter()
.collect(),
);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/editor/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,10 @@ impl TechniqueLanguageServer {
"Section content must be steps or procedures".to_string(),
DiagnosticSeverity::ERROR,
),
ParsingError::MixedBracketContent(_) => (
"Must either be a list (values) or a tablet (labelled pairs), not a mix of the two".to_string(),
DiagnosticSeverity::ERROR,
),
ParsingError::InvalidInvocation(_) => (
"Invalid procedure Invocation".to_string(),
DiagnosticSeverity::ERROR,
Expand Down
68 changes: 29 additions & 39 deletions src/formatting/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,26 +292,11 @@ fn hugs_left(word: &str) -> bool {
}
}

/// A list reads as a tablet when it is non-empty and every element is a
/// labelled value. Such lists are laid out and treated as blocks rather than
/// inline.
fn is_tablet_list(elements: &[Expression]) -> bool {
!elements.is_empty()
&& elements
.iter()
.all(|element| {
if let Expression::Pair(_, _) = element {
true
} else {
false
}
})
}

/// True when an expression is a tablet-shaped list (see `is_tablet_list`).
fn is_tablet_list_expr(expr: &Expression) -> bool {
if let Expression::List(elements, _) = expr {
is_tablet_list(elements)
/// A tablet with entries is laid out as a block rather than inline. The empty
/// tablet `[=]` has nothing to lay out, so it stays inline.
fn is_tablet_block(expr: &Expression) -> bool {
if let Expression::Tablet(pairs, _) = expr {
!pairs.is_empty()
} else {
false
}
Expand All @@ -335,7 +320,7 @@ fn is_inline_code_block(expressions: &[Expression]) -> bool {
if has_separator {
true
} else if expressions.len() == 1 {
!is_tablet_list_expr(&expressions[0])
!is_tablet_block(&expressions[0])
} else {
false
}
Expand Down Expand Up @@ -485,7 +470,7 @@ impl<'i> Formatter<'i> {
}

fn render_inline_code(&self, expr: &'i Expression) -> Vec<(Syntax, Cow<'i, str>)> {
if is_tablet_list_expr(expr) {
if is_tablet_block(expr) {
// Not inline; caller handles the block layout specially.
return Vec::new();
}
Expand Down Expand Up @@ -900,7 +885,7 @@ impl<'i> Formatter<'i> {
}
}
match expr {
_ if is_tablet_list_expr(expr) => {
_ if is_tablet_block(expr) => {
line.flush();
self.append_char('\n');
self.indent();
Expand Down Expand Up @@ -1382,6 +1367,7 @@ impl<'i> Formatter<'i> {
}
Expression::Pair(pair, _) => self.append_pair(pair),
Expression::List(elements, _) => self.append_list(elements),
Expression::Tablet(pairs, _) => self.append_tablet(pairs),
Expression::Tuple(elements, _) => self.append_tuple(elements),
Expression::Hole(_) => {
self.add_fragment_reference(Syntax::Hole, "?");
Expand Down Expand Up @@ -1539,28 +1525,32 @@ impl<'i> Formatter<'i> {
self.append_expression(&pair.value);
}

/// A list whose elements are all labelled (a tablet) is laid out one
/// element per line; any other list, and the empty list, is inline.
fn append_list(&mut self, elements: &'i Vec<Expression>) {
if elements.is_empty() {
self.add_fragment_reference(Syntax::Structure, "[]");
/// A tablet is laid out one entry per line. The empty tablet has nothing
/// to lay out and is written `[=]`.
fn append_tablet(&mut self, pairs: &'i Vec<Pair>) {
if pairs.is_empty() {
self.add_fragment_reference(Syntax::Structure, "[=]");
return;
}

if is_tablet_list(elements) {
self.add_fragment_reference(Syntax::Structure, "[");
self.add_fragment_reference(Syntax::Structure, "[");
self.append_char('\n');

self.increase(4);
for pair in pairs {
self.indent();
self.append_pair(pair);
self.append_char('\n');
}
self.decrease(4);

self.increase(4);
for element in elements {
self.indent();
self.append_expression(element);
self.append_char('\n');
}
self.decrease(4);
self.indent();
self.add_fragment_reference(Syntax::Structure, "]");
}

self.indent();
self.add_fragment_reference(Syntax::Structure, "]");
fn append_list(&mut self, elements: &'i Vec<Expression>) {
if elements.is_empty() {
self.add_fragment_reference(Syntax::Structure, "[]");
return;
}

Expand Down
2 changes: 2 additions & 0 deletions src/language/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ pub enum Expression<'i> {
Binding(Box<Expression<'i>>, Vec<Identifier<'i>>, Span),
Pair(Box<Pair<'i>>, Span),
List(Vec<Expression<'i>>, Span),
Tablet(Vec<Pair<'i>>, Span),
Tuple(Vec<Expression<'i>>, Span),
Hole(Span),
Unit(Span),
Expand Down Expand Up @@ -482,6 +483,7 @@ impl PartialEq for Expression<'_> {
}
(Expression::Pair(a, _), Expression::Pair(b, _)) => a == b,
(Expression::List(a, _), Expression::List(b, _)) => a == b,
(Expression::Tablet(a, _), Expression::Tablet(b, _)) => a == b,
(Expression::Tuple(a, _), Expression::Tuple(b, _)) => a == b,
(Expression::Hole(_), Expression::Hole(_)) => true,
(Expression::Unit(_), Expression::Unit(_)) => true,
Expand Down
Loading