parent
a73149767c
commit
0f8219b392
@ -81,6 +81,41 @@ It explains #name.
|
||||
Sum is #add(2, 3).
|
||||
```
|
||||
|
||||
Let bindings can be used to destructure arrays and dictionaries.
|
||||
|
||||
```example
|
||||
#let (x, y) = (1, 2)
|
||||
The coordinates are #x, #y.
|
||||
|
||||
#let (a, .., b) = (1, 2, 3, 4)
|
||||
The first element is #a.
|
||||
The last element is #b.
|
||||
|
||||
#let books = (
|
||||
"Shakespeare": "Hamlet",
|
||||
"Homer": "The Odyssey",
|
||||
"Austen": "Persuasion",
|
||||
)
|
||||
#let (Austen) = books
|
||||
Austen wrote #Austen.
|
||||
|
||||
#let (Homer: h) = books
|
||||
Homer wrote #h.
|
||||
|
||||
#let (Homer, ..other) = books
|
||||
#for (author, title) in other [
|
||||
#author wrote #title,
|
||||
]
|
||||
```
|
||||
|
||||
Note that the underscore `_` is the only identifier that can
|
||||
be used multiple times in the same assignment.
|
||||
|
||||
```
|
||||
#let (_, y, _) = (1, 2, 3)
|
||||
The y coordinate is #y.
|
||||
```
|
||||
|
||||
## Conditionals { #conditionals }
|
||||
With a conditional, you can display or compute different things depending on
|
||||
whether some condition is fulfilled. Typst supports `{if}`, `{else if}` and
|
||||
@ -136,20 +171,12 @@ For loops can iterate over a variety of collections:
|
||||
one cluster.)
|
||||
|
||||
- `{for value in array {..}}` \
|
||||
`{for index, value in array {..}}`\
|
||||
Iterates over the items in the [array]($type/array). Can also provide the
|
||||
index of each item.
|
||||
Iterates over the items in the [array]($type/array). The destructuring syntax
|
||||
described in [Let binding]($scripting/bindings) can also be used here.
|
||||
|
||||
- `{for value in dict {..}}` \
|
||||
`{for key, value in dict {..}}` \
|
||||
Iterates over the values or keys and values of the
|
||||
[dictionary]($type/dictionary).
|
||||
|
||||
- `{for value in args {..}}` \
|
||||
`{for name, value in args {..}}` \
|
||||
Iterates over the values or names and values of the
|
||||
[arguments]($type/arguments). For positional arguments, the `name` is
|
||||
`{none}`.
|
||||
- `{for pair in dict {..}}` \
|
||||
Iterates over the key-value pairs of the [dictionary]($type/dictionary).
|
||||
The pairs can also be destructured by using `{for (key, value) in dict {..}}`.
|
||||
|
||||
To control the execution of the loop, Typst provides the `{break}` and
|
||||
`{continue}` statements. The former performs an early exit from the loop while
|
||||
|
@ -232,7 +232,7 @@ fn format_json_error(error: serde_json::Error) -> String {
|
||||
/// ## Example
|
||||
/// ```example
|
||||
/// #let bookshelf(contents) = {
|
||||
/// for author, works in contents {
|
||||
/// for (author, works) in contents {
|
||||
/// author
|
||||
/// for work in works [
|
||||
/// - #work.title (#work.published)
|
||||
|
@ -81,7 +81,7 @@ pub struct TermsElem {
|
||||
/// collected into term lists, even through constructs like for loops.
|
||||
///
|
||||
/// ```example
|
||||
/// #for year, product in (
|
||||
/// #for (year, product) in (
|
||||
/// "1978": "TeX",
|
||||
/// "1984": "LaTeX",
|
||||
/// "2019": "Typst",
|
||||
|
@ -322,6 +322,17 @@ impl Array {
|
||||
usize::try_from(if index >= 0 { index } else { self.len().checked_add(index)? })
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Enumerate all items in the array.
|
||||
pub fn enumerate(&self) -> Self {
|
||||
let v = self
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, value)| array![i, value.clone()])
|
||||
.map(Value::Array)
|
||||
.collect();
|
||||
Self::from_vec(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Array {
|
||||
|
@ -441,7 +441,10 @@ impl<'a> CapturesVisitor<'a> {
|
||||
if let Some(init) = expr.init() {
|
||||
self.visit(init.as_untyped());
|
||||
}
|
||||
self.bind(expr.binding());
|
||||
|
||||
for ident in expr.kind().idents() {
|
||||
self.bind(ident);
|
||||
}
|
||||
}
|
||||
|
||||
// A for loop contains one or two bindings in its pattern. These are
|
||||
@ -450,11 +453,12 @@ impl<'a> CapturesVisitor<'a> {
|
||||
Some(ast::Expr::For(expr)) => {
|
||||
self.visit(expr.iter().as_untyped());
|
||||
self.internal.enter();
|
||||
|
||||
let pattern = expr.pattern();
|
||||
if let Some(key) = pattern.key() {
|
||||
self.bind(key);
|
||||
for ident in pattern.idents() {
|
||||
self.bind(ident);
|
||||
}
|
||||
self.bind(pattern.value());
|
||||
|
||||
self.visit(expr.body().as_untyped());
|
||||
self.internal.exit();
|
||||
}
|
||||
@ -550,7 +554,7 @@ mod tests {
|
||||
|
||||
// For loop.
|
||||
test("#for x in y { x + z }", &["y", "z"]);
|
||||
test("#for x, y in y { x + y }", &["y"]);
|
||||
test("#for (x, y) in y { x + y }", &["y"]);
|
||||
test("#for x in y {} #x", &["x", "y"]);
|
||||
|
||||
// Import.
|
||||
|
@ -116,6 +116,7 @@ pub fn call(
|
||||
array.join(sep, last).at(span)?
|
||||
}
|
||||
"sorted" => Value::Array(array.sorted().at(span)?),
|
||||
"enumerate" => Value::Array(array.enumerate()),
|
||||
_ => return missing(),
|
||||
},
|
||||
|
||||
@ -297,6 +298,7 @@ pub fn methods_on(type_name: &str) -> &[(&'static str, bool)] {
|
||||
("rev", false),
|
||||
("slice", true),
|
||||
("sorted", false),
|
||||
("enumerate", false),
|
||||
],
|
||||
"dictionary" => &[
|
||||
("at", true),
|
||||
|
151
src/eval/mod.rs
151
src/eval/mod.rs
@ -37,7 +37,7 @@ pub use self::value::*;
|
||||
|
||||
pub(crate) use self::methods::methods_on;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::mem;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@ -1184,6 +1184,97 @@ impl Eval for ast::Closure {
|
||||
}
|
||||
}
|
||||
|
||||
impl ast::Pattern {
|
||||
// Destruct the given value into the pattern.
|
||||
pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> {
|
||||
match self.kind() {
|
||||
ast::PatternKind::Ident(ident) => {
|
||||
vm.define(ident, value);
|
||||
Ok(Value::None)
|
||||
}
|
||||
ast::PatternKind::Destructure(pattern) => {
|
||||
match value {
|
||||
Value::Array(value) => {
|
||||
let mut i = 0;
|
||||
for p in &pattern {
|
||||
match p {
|
||||
ast::DestructuringKind::Ident(ident) => {
|
||||
let Ok(v) = value.at(i) else {
|
||||
bail!(ident.span(), "not enough elements to destructure");
|
||||
};
|
||||
vm.define(ident.clone(), v.clone());
|
||||
i += 1;
|
||||
}
|
||||
ast::DestructuringKind::Sink(ident) => {
|
||||
(1 + value.len() as usize).checked_sub(pattern.len()).and_then(|sink_size| {
|
||||
let Ok(sink) = value.slice(i, Some(i + sink_size as i64)) else {
|
||||
return None;
|
||||
};
|
||||
if let Some(ident) = ident {
|
||||
vm.define(ident.clone(), sink.clone());
|
||||
}
|
||||
i += sink_size as i64;
|
||||
Some(())
|
||||
}).ok_or("not enough elements to destructure").at(self.span())?;
|
||||
}
|
||||
ast::DestructuringKind::Named(key, _) => {
|
||||
bail!(
|
||||
key.span(),
|
||||
"cannot destructure named elements from an array"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if i < value.len() as i64 {
|
||||
bail!(self.span(), "too many elements to destructure");
|
||||
}
|
||||
}
|
||||
Value::Dict(value) => {
|
||||
let mut sink = None;
|
||||
let mut used = HashSet::new();
|
||||
for p in &pattern {
|
||||
match p {
|
||||
ast::DestructuringKind::Ident(ident) => {
|
||||
let Ok(v) = value.at(ident) else {
|
||||
bail!(ident.span(), "destructuring key not found in dictionary");
|
||||
};
|
||||
vm.define(ident.clone(), v.clone());
|
||||
used.insert(ident.clone().take());
|
||||
}
|
||||
ast::DestructuringKind::Sink(ident) => {
|
||||
sink = ident.clone()
|
||||
}
|
||||
ast::DestructuringKind::Named(key, ident) => {
|
||||
let Ok(v) = value.at(key) else {
|
||||
bail!(ident.span(), "destructuring key not found in dictionary");
|
||||
};
|
||||
vm.define(ident.clone(), v.clone());
|
||||
used.insert(key.clone().take());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ident) = sink {
|
||||
let mut sink = Dict::new();
|
||||
for (key, value) in value {
|
||||
if !used.contains(key.as_str()) {
|
||||
sink.insert(key, value);
|
||||
}
|
||||
}
|
||||
vm.define(ident, Value::Dict(sink));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
bail!(self.span(), "cannot destructure {}", value.type_name());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eval for ast::LetBinding {
|
||||
type Output = Value;
|
||||
|
||||
@ -1192,8 +1283,14 @@ impl Eval for ast::LetBinding {
|
||||
Some(expr) => expr.eval(vm)?,
|
||||
None => Value::None,
|
||||
};
|
||||
vm.define(self.binding(), value);
|
||||
Ok(Value::None)
|
||||
|
||||
match self.kind() {
|
||||
ast::LetBindingKind::Normal(pattern) => pattern.define(vm, value),
|
||||
ast::LetBindingKind::Closure(ident) => {
|
||||
vm.define(ident, value);
|
||||
Ok(Value::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1333,12 +1430,12 @@ impl Eval for ast::ForLoop {
|
||||
let mut output = Value::None;
|
||||
|
||||
macro_rules! iter {
|
||||
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
|
||||
(for $pat:ident in $iter:expr) => {{
|
||||
vm.scopes.enter();
|
||||
|
||||
#[allow(unused_parens)]
|
||||
for ($($value),*) in $iter {
|
||||
$(vm.define($binding.clone(), $value);)*
|
||||
for value in $iter {
|
||||
$pat.define(vm, Value::from(value))?;
|
||||
|
||||
let body = self.body();
|
||||
let value = body.eval(vm)?;
|
||||
@ -1361,40 +1458,26 @@ impl Eval for ast::ForLoop {
|
||||
|
||||
let iter = self.iter().eval(vm)?;
|
||||
let pattern = self.pattern();
|
||||
let key = pattern.key();
|
||||
let value = pattern.value();
|
||||
|
||||
match (key, value, iter) {
|
||||
(None, v, Value::Str(string)) => {
|
||||
iter!(for (v => value) in string.as_str().graphemes(true));
|
||||
match (pattern.kind(), iter.clone()) {
|
||||
(ast::PatternKind::Ident(_), Value::Str(string)) => {
|
||||
// iterate over characters of string
|
||||
iter!(for pattern in string.as_str().graphemes(true));
|
||||
}
|
||||
(None, v, Value::Array(array)) => {
|
||||
iter!(for (v => value) in array.into_iter());
|
||||
(_, Value::Dict(dict)) => {
|
||||
// iterate over keys of dict
|
||||
iter!(for pattern in dict.pairs());
|
||||
}
|
||||
(Some(i), v, Value::Array(array)) => {
|
||||
iter!(for (i => idx, v => value) in array.into_iter().enumerate());
|
||||
(_, Value::Array(array)) => {
|
||||
// iterate over values of array and allow destructuring
|
||||
iter!(for pattern in array.into_iter());
|
||||
}
|
||||
(None, v, Value::Dict(dict)) => {
|
||||
iter!(for (v => value) in dict.into_iter().map(|p| p.1));
|
||||
}
|
||||
(Some(k), v, Value::Dict(dict)) => {
|
||||
iter!(for (k => key, v => value) in dict.into_iter());
|
||||
}
|
||||
(None, v, Value::Args(args)) => {
|
||||
iter!(for (v => value) in args.items.into_iter()
|
||||
.filter(|arg| arg.name.is_none())
|
||||
.map(|arg| arg.value.v));
|
||||
}
|
||||
(Some(k), v, Value::Args(args)) => {
|
||||
iter!(for (k => key, v => value) in args.items.into_iter()
|
||||
.map(|arg| (arg.name.map_or(Value::None, Value::Str), arg.value.v)));
|
||||
}
|
||||
(_, _, Value::Str(_)) => {
|
||||
bail!(pattern.span(), "mismatched pattern");
|
||||
}
|
||||
(_, _, iter) => {
|
||||
(ast::PatternKind::Ident(_), _) => {
|
||||
bail!(self.iter().span(), "cannot loop over {}", iter.type_name());
|
||||
}
|
||||
(_, _) => {
|
||||
bail!(pattern.span(), "cannot destructure values of {}", iter.type_name())
|
||||
}
|
||||
}
|
||||
|
||||
if flow.is_some() {
|
||||
|
@ -1096,7 +1096,9 @@ impl<'a> CompletionContext<'a> {
|
||||
let mut sibling = Some(node.clone());
|
||||
while let Some(node) = &sibling {
|
||||
if let Some(v) = node.cast::<ast::LetBinding>() {
|
||||
defined.insert(v.binding().take());
|
||||
for ident in v.kind().idents() {
|
||||
defined.insert(ident.take());
|
||||
}
|
||||
}
|
||||
sibling = node.prev_sibling();
|
||||
}
|
||||
@ -1105,10 +1107,9 @@ impl<'a> CompletionContext<'a> {
|
||||
if let Some(v) = parent.cast::<ast::ForLoop>() {
|
||||
if node.prev_sibling_kind() != Some(SyntaxKind::In) {
|
||||
let pattern = v.pattern();
|
||||
if let Some(key) = pattern.key() {
|
||||
defined.insert(key.take());
|
||||
for ident in pattern.idents() {
|
||||
defined.insert(ident.take());
|
||||
}
|
||||
defined.insert(pattern.value().take());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,6 +246,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Tag> {
|
||||
SyntaxKind::LoopBreak => None,
|
||||
SyntaxKind::LoopContinue => None,
|
||||
SyntaxKind::FuncReturn => None,
|
||||
SyntaxKind::Pattern => None,
|
||||
|
||||
SyntaxKind::LineComment => Some(Tag::Comment),
|
||||
SyntaxKind::BlockComment => Some(Tag::Comment),
|
||||
|
@ -1533,7 +1533,10 @@ impl Closure {
|
||||
///
|
||||
/// This only exists if you use the function syntax sugar: `let f(x) = y`.
|
||||
pub fn name(&self) -> Option<Ident> {
|
||||
self.0.children().next()?.cast()
|
||||
match self.0.cast_first_match::<Pattern>()?.kind() {
|
||||
PatternKind::Ident(ident) => Some(ident),
|
||||
_ => Option::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameter bindings.
|
||||
@ -1589,29 +1592,122 @@ impl AstNode for Param {
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A destructuring pattern: `x` or `(x, _, ..y)`.
|
||||
Pattern
|
||||
}
|
||||
|
||||
/// The kind of a pattern.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum PatternKind {
|
||||
/// A single identifier: `x`.
|
||||
Ident(Ident),
|
||||
/// A destructuring pattern: `(x, _, ..y)`.
|
||||
Destructure(Vec<DestructuringKind>),
|
||||
}
|
||||
|
||||
/// The kind of an element in a destructuring pattern.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum DestructuringKind {
|
||||
/// An identifier: `x`.
|
||||
Ident(Ident),
|
||||
/// An argument sink: `..y`.
|
||||
Sink(Option<Ident>),
|
||||
/// Named arguments: `x: 1`.
|
||||
Named(Ident, Ident),
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
/// The kind of the pattern.
|
||||
pub fn kind(&self) -> PatternKind {
|
||||
if self.0.children().len() <= 1 {
|
||||
return PatternKind::Ident(self.0.cast_first_match().unwrap_or_default());
|
||||
}
|
||||
|
||||
let mut bindings = Vec::new();
|
||||
for child in self.0.children() {
|
||||
match child.kind() {
|
||||
SyntaxKind::Ident => {
|
||||
bindings
|
||||
.push(DestructuringKind::Ident(child.cast().unwrap_or_default()));
|
||||
}
|
||||
SyntaxKind::Spread => {
|
||||
bindings.push(DestructuringKind::Sink(child.cast_first_match()));
|
||||
}
|
||||
SyntaxKind::Named => {
|
||||
let mut filtered = child.children().filter_map(SyntaxNode::cast);
|
||||
let key = filtered.next().unwrap_or_default();
|
||||
let ident = filtered.next().unwrap_or_default();
|
||||
bindings.push(DestructuringKind::Named(key, ident));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
PatternKind::Destructure(bindings)
|
||||
}
|
||||
|
||||
// Returns a list of all identifiers in the pattern.
|
||||
pub fn idents(&self) -> Vec<Ident> {
|
||||
match self.kind() {
|
||||
PatternKind::Ident(ident) => vec![ident],
|
||||
PatternKind::Destructure(bindings) => bindings
|
||||
.into_iter()
|
||||
.filter_map(|binding| match binding {
|
||||
DestructuringKind::Ident(ident) => Some(ident),
|
||||
DestructuringKind::Sink(ident) => ident,
|
||||
DestructuringKind::Named(_, ident) => Some(ident),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A let binding: `let x = 1`.
|
||||
LetBinding
|
||||
}
|
||||
|
||||
pub enum LetBindingKind {
|
||||
/// A normal binding: `let x = 1`.
|
||||
Normal(Pattern),
|
||||
/// A closure binding: `let f(x) = 1`.
|
||||
Closure(Ident),
|
||||
}
|
||||
|
||||
impl LetBindingKind {
|
||||
// Returns a list of all identifiers in the pattern.
|
||||
pub fn idents(&self) -> Vec<Ident> {
|
||||
match self {
|
||||
LetBindingKind::Normal(pattern) => pattern.idents(),
|
||||
LetBindingKind::Closure(ident) => {
|
||||
vec![ident.clone()]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LetBinding {
|
||||
/// The binding to assign to.
|
||||
pub fn binding(&self) -> Ident {
|
||||
match self.0.cast_first_match() {
|
||||
Some(Expr::Ident(binding)) => binding,
|
||||
Some(Expr::Closure(closure)) => closure.name().unwrap_or_default(),
|
||||
_ => Ident::default(),
|
||||
/// The kind of the let binding.
|
||||
pub fn kind(&self) -> LetBindingKind {
|
||||
if let Some(pattern) = self.0.cast_first_match::<Pattern>() {
|
||||
LetBindingKind::Normal(pattern)
|
||||
} else {
|
||||
LetBindingKind::Closure(
|
||||
self.0
|
||||
.cast_first_match::<Closure>()
|
||||
.unwrap_or_default()
|
||||
.name()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// The expression the binding is initialized with.
|
||||
pub fn init(&self) -> Option<Expr> {
|
||||
if self.0.cast_first_match::<Ident>().is_some() {
|
||||
// This is a normal binding like `let x = 1`.
|
||||
self.0.children().filter_map(SyntaxNode::cast).nth(1)
|
||||
} else {
|
||||
// This is a closure binding like `let f(x) = 1`.
|
||||
self.0.cast_first_match()
|
||||
match self.kind() {
|
||||
LetBindingKind::Normal(_) => self.0.cast_last_match(),
|
||||
LetBindingKind::Closure(_) => self.0.cast_first_match(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1712,7 +1808,7 @@ node! {
|
||||
|
||||
impl ForLoop {
|
||||
/// The pattern to assign to.
|
||||
pub fn pattern(&self) -> ForPattern {
|
||||
pub fn pattern(&self) -> Pattern {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
@ -1727,29 +1823,6 @@ impl ForLoop {
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A for loop's destructuring pattern: `x` or `x, y`.
|
||||
ForPattern
|
||||
}
|
||||
|
||||
impl ForPattern {
|
||||
/// The key part of the pattern: index for arrays, name for dictionaries.
|
||||
pub fn key(&self) -> Option<Ident> {
|
||||
let mut children = self.0.children().filter_map(SyntaxNode::cast);
|
||||
let key = children.next();
|
||||
if children.next().is_some() {
|
||||
key
|
||||
} else {
|
||||
Option::None
|
||||
}
|
||||
}
|
||||
|
||||
/// The value part of the pattern.
|
||||
pub fn value(&self) -> Ident {
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A module import: `import "utils.typ": a, b, c`.
|
||||
ModuleImport
|
||||
|
@ -244,6 +244,8 @@ pub enum SyntaxKind {
|
||||
LoopContinue,
|
||||
/// A return from a function: `return`, `return x + 1`.
|
||||
FuncReturn,
|
||||
/// A destructuring pattern: `x`, `(x, _, ..y)`.
|
||||
Pattern,
|
||||
|
||||
/// A line comment: `// ...`.
|
||||
LineComment,
|
||||
@ -430,6 +432,7 @@ impl SyntaxKind {
|
||||
Self::LoopBreak => "`break` expression",
|
||||
Self::LoopContinue => "`continue` expression",
|
||||
Self::FuncReturn => "`return` expression",
|
||||
Self::Pattern => "destructuring pattern",
|
||||
Self::LineComment => "line comment",
|
||||
Self::BlockComment => "block comment",
|
||||
Self::Error => "syntax error",
|
||||
|
@ -777,6 +777,11 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind {
|
||||
let m = p.marker();
|
||||
|
||||
if p.eat_if(SyntaxKind::Dots) {
|
||||
if p.at(SyntaxKind::Comma) || p.at(SyntaxKind::RightParen) {
|
||||
p.wrap(m, SyntaxKind::Spread);
|
||||
return SyntaxKind::Spread;
|
||||
}
|
||||
|
||||
code_expr(p);
|
||||
p.wrap(m, SyntaxKind::Spread);
|
||||
return SyntaxKind::Spread;
|
||||
@ -833,22 +838,56 @@ fn args(p: &mut Parser) {
|
||||
p.wrap(m, SyntaxKind::Args);
|
||||
}
|
||||
|
||||
enum PatternKind {
|
||||
Normal,
|
||||
Destructuring,
|
||||
}
|
||||
|
||||
fn pattern(p: &mut Parser) -> PatternKind {
|
||||
let m = p.marker();
|
||||
|
||||
if p.at(SyntaxKind::LeftParen) {
|
||||
collection(p, false);
|
||||
validate_destruct_pattern(p, m);
|
||||
p.wrap(m, SyntaxKind::Pattern);
|
||||
|
||||
PatternKind::Destructuring
|
||||
} else {
|
||||
let success = p.expect(SyntaxKind::Ident);
|
||||
if p.at(SyntaxKind::Comma) {
|
||||
// TODO: this should only be a warning instead
|
||||
p.expected("keyword `in`. did you mean to use a destructuring pattern?");
|
||||
}
|
||||
|
||||
if success {
|
||||
p.wrap(m, SyntaxKind::Pattern);
|
||||
}
|
||||
|
||||
PatternKind::Normal
|
||||
}
|
||||
}
|
||||
|
||||
fn let_binding(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.assert(SyntaxKind::Let);
|
||||
|
||||
let m2 = p.marker();
|
||||
p.expect(SyntaxKind::Ident);
|
||||
|
||||
let closure = p.directly_at(SyntaxKind::LeftParen);
|
||||
if closure {
|
||||
let m3 = p.marker();
|
||||
collection(p, false);
|
||||
validate_params(p, m3);
|
||||
p.wrap(m3, SyntaxKind::Params);
|
||||
let mut closure = false;
|
||||
let mut destructuring = false;
|
||||
match pattern(p) {
|
||||
PatternKind::Normal => {
|
||||
closure = p.directly_at(SyntaxKind::LeftParen);
|
||||
if closure {
|
||||
let m3 = p.marker();
|
||||
collection(p, false);
|
||||
validate_params(p, m3);
|
||||
p.wrap(m3, SyntaxKind::Params);
|
||||
}
|
||||
}
|
||||
PatternKind::Destructuring => destructuring = true,
|
||||
}
|
||||
|
||||
let f = if closure { Parser::expect } else { Parser::eat_if };
|
||||
let f = if closure || destructuring { Parser::expect } else { Parser::eat_if };
|
||||
if f(p, SyntaxKind::Eq) {
|
||||
code_expr(p);
|
||||
}
|
||||
@ -924,23 +963,13 @@ fn while_loop(p: &mut Parser) {
|
||||
fn for_loop(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.assert(SyntaxKind::For);
|
||||
for_pattern(p);
|
||||
pattern(p);
|
||||
p.expect(SyntaxKind::In);
|
||||
code_expr(p);
|
||||
block(p);
|
||||
p.wrap(m, SyntaxKind::ForLoop);
|
||||
}
|
||||
|
||||
fn for_pattern(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
if p.expect(SyntaxKind::Ident) {
|
||||
if p.eat_if(SyntaxKind::Comma) {
|
||||
p.expect(SyntaxKind::Ident);
|
||||
}
|
||||
p.wrap(m, SyntaxKind::ForPattern);
|
||||
}
|
||||
}
|
||||
|
||||
fn module_import(p: &mut Parser) {
|
||||
let m = p.marker();
|
||||
p.assert(SyntaxKind::Import);
|
||||
@ -1086,6 +1115,73 @@ fn validate_args(p: &mut Parser, m: Marker) {
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
||||
let mut used_spread = false;
|
||||
let mut used = HashSet::new();
|
||||
for child in p.post_process(m) {
|
||||
match child.kind() {
|
||||
SyntaxKind::Ident => {
|
||||
if child.text() != "_" && !used.insert(child.text().clone()) {
|
||||
child.convert_to_error(
|
||||
"at most one binding per identifier is allowed",
|
||||
);
|
||||
}
|
||||
}
|
||||
SyntaxKind::Spread => {
|
||||
let Some(within) = child.children_mut().last_mut() else { continue };
|
||||
if used_spread {
|
||||
child.convert_to_error("at most one destructuring sink is allowed");
|
||||
continue;
|
||||
}
|
||||
used_spread = true;
|
||||
|
||||
if within.kind() == SyntaxKind::Dots {
|
||||
continue;
|
||||
} else if within.kind() != SyntaxKind::Ident {
|
||||
within.convert_to_error(eco_format!(
|
||||
"expected identifier, found {}",
|
||||
within.kind().name(),
|
||||
));
|
||||
child.make_erroneous();
|
||||
continue;
|
||||
}
|
||||
|
||||
if within.text() != "_" && !used.insert(within.text().clone()) {
|
||||
within.convert_to_error(
|
||||
"at most one binding per identifier is allowed",
|
||||
);
|
||||
child.make_erroneous();
|
||||
}
|
||||
}
|
||||
SyntaxKind::Named => {
|
||||
let Some(within) = child.children_mut().first_mut() else { return };
|
||||
if !used.insert(within.text().clone()) {
|
||||
within.convert_to_error(
|
||||
"at most one binding per identifier is allowed",
|
||||
);
|
||||
child.make_erroneous();
|
||||
}
|
||||
|
||||
let Some(within) = child.children_mut().last_mut() else { return };
|
||||
if within.kind() != SyntaxKind::Ident {
|
||||
within.convert_to_error(eco_format!(
|
||||
"expected identifier, found {}",
|
||||
within.kind().name(),
|
||||
));
|
||||
child.make_erroneous();
|
||||
}
|
||||
}
|
||||
SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {}
|
||||
kind => {
|
||||
child.convert_to_error(eco_format!(
|
||||
"expected identifier or destructuring sink, found {}",
|
||||
kind.name()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages parsing of a stream of tokens.
|
||||
struct Parser<'s> {
|
||||
text: &'s str,
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
// Dictionary is not traversed in insertion order.
|
||||
// Should output `Age: 2. Name: Typst.`.
|
||||
#for k, v in (Name: "Typst", Age: 2) [
|
||||
#for (k, v) in (Name: "Typst", Age: 2) [
|
||||
#k: #v.
|
||||
]
|
||||
|
||||
@ -47,22 +47,22 @@
|
||||
}
|
||||
|
||||
// Indices and values of array.
|
||||
#for i, v in ("1", "2", "3") {
|
||||
#for (i, v) in ("1", "2", "3").enumerate() {
|
||||
test(repr(i + 1), v)
|
||||
}
|
||||
|
||||
// Values of dictionary.
|
||||
// Pairs of dictionary.
|
||||
#for v in (a: 4, b: 5) {
|
||||
out += (v,)
|
||||
}
|
||||
|
||||
// Keys and values of dictionary.
|
||||
#for k, v in (a: 6, b: 7) {
|
||||
#for (k, v) in (a: 6, b: 7) {
|
||||
out += (k,)
|
||||
out += (v,)
|
||||
}
|
||||
|
||||
#test(out, (1, 2, 3, 4, 5, "a", 6, "b", 7))
|
||||
#test(out, (1, 2, 3, ("a", 4), ("b", 5), "a", 6, "b", 7))
|
||||
|
||||
// Grapheme clusters of string.
|
||||
#let first = true
|
||||
@ -85,12 +85,19 @@
|
||||
|
||||
---
|
||||
// Keys and values of strings.
|
||||
// Error: 6-10 mismatched pattern
|
||||
#for k, v in "hi" {
|
||||
// Error: 6-12 cannot destructure values of string
|
||||
#for (k, v) in "hi" {
|
||||
dont-care
|
||||
}
|
||||
|
||||
---
|
||||
// Destructuring without parentheses.
|
||||
// Error: 7 expected keyword `in`. did you mean to use a destructuring pattern?
|
||||
// Error: 7 expected keyword `in`
|
||||
#for k, v in (a: 4, b: 5) {
|
||||
dont-care
|
||||
}
|
||||
|
||||
// Error: 5 expected identifier
|
||||
#for
|
||||
|
||||
|
@ -32,6 +32,156 @@ Three
|
||||
#test(v2, 2)
|
||||
#test(v3, 3)
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Simple destructuring.
|
||||
#let (a, b) = (1, 2)
|
||||
#test(a, 1)
|
||||
#test(b, 2)
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with multiple placeholders.
|
||||
#let (a, _, c, _) = (1, 2, 3, 4)
|
||||
#test(a, 1)
|
||||
#test(c, 3)
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with a sink.
|
||||
#let (a, b, ..c) = (1, 2, 3, 4, 5, 6)
|
||||
#test(a, 1)
|
||||
#test(b, 2)
|
||||
#test(c, (3, 4, 5, 6))
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with a sink in the middle.
|
||||
#let (a, ..b, c) = (1, 2, 3, 4, 5, 6)
|
||||
#test(a, 1)
|
||||
#test(b, (2, 3, 4, 5))
|
||||
#test(c, 6)
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with an empty sink.
|
||||
#let (..a, b, c) = (1, 2)
|
||||
#test(a, ())
|
||||
#test(b, 1)
|
||||
#test(c, 2)
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with an empty sink.
|
||||
#let (a, ..b, c) = (1, 2)
|
||||
#test(a, 1)
|
||||
#test(b, ())
|
||||
#test(c, 2)
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with an empty sink.
|
||||
#let (a, b, ..c) = (1, 2)
|
||||
#test(a, 1)
|
||||
#test(b, 2)
|
||||
#test(c, ())
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with an empty sink and empty array.
|
||||
#let (..a) = ()
|
||||
#test(a, ())
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with unnamed sink.
|
||||
#let (a, .., b) = (1, 2, 3, 4)
|
||||
#test(a, 1)
|
||||
#test(b, 4)
|
||||
|
||||
// Error: 10-11 at most one binding per identifier is allowed
|
||||
#let (a, a) = (1, 2)
|
||||
|
||||
// Error: 12-15 at most one destructuring sink is allowed
|
||||
#let (..a, ..a) = (1, 2)
|
||||
|
||||
// Error: 12-13 at most one binding per identifier is allowed
|
||||
#let (a, ..a) = (1, 2)
|
||||
|
||||
// Error: 13-14 at most one binding per identifier is allowed
|
||||
#let (a: a, a) = (a: 1, b: 2)
|
||||
|
||||
---
|
||||
// Error: 13-14 not enough elements to destructure
|
||||
#let (a, b, c) = (1, 2)
|
||||
|
||||
---
|
||||
// Error: 6-9 too many elements to destructure
|
||||
#let (a) = (1, 2)
|
||||
|
||||
---
|
||||
// Error: 6-20 not enough elements to destructure
|
||||
#let (..a, b, c, d) = (1, 2)
|
||||
|
||||
---
|
||||
// Error: 6-12 cannot destructure boolean
|
||||
#let (a, b) = true
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Simple destructuring.
|
||||
#let (a: a, b, x: c) = (a: 1, b: 2, x: 3)
|
||||
#test(a, 1)
|
||||
#test(b, 2)
|
||||
#test(c, 3)
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with a sink.
|
||||
#let (a: _, ..b) = (a: 1, b: 2, c: 3)
|
||||
#test(b, (b: 2, c: 3))
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with a sink in the middle.
|
||||
#let (a: _, ..b, c: _) = (a: 1, b: 2, c: 3)
|
||||
#test(b, (b: 2))
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with an empty sink.
|
||||
#let (a: _, ..b) = (a: 1)
|
||||
#test(b, (:))
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with an empty sink and empty dict.
|
||||
#let (..a) = (:)
|
||||
#test(a, (:))
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Destructuring with unnamed sink.
|
||||
#let (a, ..) = (a: 1, b: 2)
|
||||
#test(a, 1)
|
||||
|
||||
---
|
||||
// Error: 10-13 expected identifier, found string
|
||||
// Error: 18-19 expected identifier, found integer
|
||||
#let (a: "a", b: 2) = (a: 1, b: 2)
|
||||
|
||||
---
|
||||
// Error: 10-11 destructuring key not found in dictionary
|
||||
#let (a, b) = (a: 1)
|
||||
|
||||
---
|
||||
// Error: 13-14 destructuring key not found in dictionary
|
||||
#let (a, b: b) = (a: 1)
|
||||
|
||||
---
|
||||
// Error: 7-8 cannot destructure named elements from an array
|
||||
#let (a: a, b) = (1, 2, 3)
|
||||
|
||||
---
|
||||
// Error: 5 expected identifier
|
||||
#let
|
||||
@ -62,6 +212,9 @@ Three
|
||||
// Error: 18 expected closing paren
|
||||
#let v5 = (1, 2 + ; Five
|
||||
|
||||
// Error: 9-13 expected identifier, found boolean
|
||||
#let (..true) = false
|
||||
|
||||
---
|
||||
// Error: 13 expected equals sign
|
||||
#let func(x)
|
||||
|
@ -12,7 +12,7 @@
|
||||
rect(width: w, height: 10pt, fill: rgb(v, v, v))
|
||||
}
|
||||
|
||||
#let items = for i, w in widths {
|
||||
#let items = for (i, w) in widths.enumerate() {
|
||||
(align(right, shaded(i, w)),)
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
#let variant = rect.with(width: 20pt, height: 10pt)
|
||||
#let items = for i, item in (
|
||||
#let items = for (i, item) in (
|
||||
variant(stroke: none),
|
||||
variant(),
|
||||
variant(fill: none),
|
||||
@ -15,7 +15,7 @@
|
||||
variant(fill: forest, stroke: conifer),
|
||||
variant(fill: forest, stroke: black + 2pt),
|
||||
variant(fill: forest, stroke: conifer + 2pt),
|
||||
) {
|
||||
).enumerate() {
|
||||
(align(horizon)[#(i + 1).], item, [])
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user