mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Destructuring assign (#703)
This commit is contained in:
parent
efad1e71fa
commit
d5d98b67a8
@ -445,7 +445,9 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
match param {
|
match param {
|
||||||
ast::Param::Pos(ident) => self.bind(ident),
|
ast::Param::Pos(ident) => self.bind(ident),
|
||||||
ast::Param::Named(named) => self.bind(named.name()),
|
ast::Param::Named(named) => self.bind(named.name()),
|
||||||
ast::Param::Sink(Some(ident)) => self.bind(ident),
|
ast::Param::Sink(spread) => {
|
||||||
|
self.bind(spread.name().unwrap_or_default())
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
163
src/eval/mod.rs
163
src/eval/mod.rs
@ -456,6 +456,7 @@ impl Eval for ast::Expr {
|
|||||||
Self::Unary(v) => v.eval(vm),
|
Self::Unary(v) => v.eval(vm),
|
||||||
Self::Binary(v) => v.eval(vm),
|
Self::Binary(v) => v.eval(vm),
|
||||||
Self::Let(v) => v.eval(vm),
|
Self::Let(v) => v.eval(vm),
|
||||||
|
Self::DestructAssign(v) => v.eval(vm),
|
||||||
Self::Set(_) => bail!(forbidden("set")),
|
Self::Set(_) => bail!(forbidden("set")),
|
||||||
Self::Show(_) => bail!(forbidden("show")),
|
Self::Show(_) => bail!(forbidden("show")),
|
||||||
Self::Conditional(v) => v.eval(vm),
|
Self::Conditional(v) => v.eval(vm),
|
||||||
@ -1212,8 +1213,8 @@ impl Eval for ast::Closure {
|
|||||||
ast::Param::Named(named) => {
|
ast::Param::Named(named) => {
|
||||||
params.push(Param::Named(named.name(), named.expr().eval(vm)?));
|
params.push(Param::Named(named.name(), named.expr().eval(vm)?));
|
||||||
}
|
}
|
||||||
ast::Param::Sink(name) => params.push(Param::Sink(name)),
|
ast::Param::Sink(spread) => params.push(Param::Sink(spread.name())),
|
||||||
ast::Param::Placeholder => params.push(Param::Placeholder),
|
ast::Param::Placeholder(_) => params.push(Param::Placeholder),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1231,97 +1232,141 @@ impl Eval for ast::Closure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ast::Pattern {
|
impl ast::Pattern {
|
||||||
// Destruct the given value into the pattern.
|
fn destruct_array<T>(
|
||||||
#[tracing::instrument(skip_all)]
|
&self,
|
||||||
pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> {
|
vm: &mut Vm,
|
||||||
match self {
|
value: Array,
|
||||||
ast::Pattern::Ident(ident) => {
|
f: T,
|
||||||
vm.define(ident.clone(), value);
|
destruct: &ast::Destructuring,
|
||||||
Ok(Value::None)
|
) -> SourceResult<Value>
|
||||||
}
|
where
|
||||||
ast::Pattern::Placeholder => Ok(Value::None),
|
T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>,
|
||||||
ast::Pattern::Destructuring(destruct) => {
|
{
|
||||||
match value {
|
|
||||||
Value::Array(value) => {
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
for p in destruct.bindings() {
|
for p in destruct.bindings() {
|
||||||
match p {
|
match p {
|
||||||
ast::DestructuringKind::Ident(ident) => {
|
ast::DestructuringKind::Normal(expr) => {
|
||||||
let Ok(v) = value.at(i) else {
|
let Ok(v) = value.at(i) else {
|
||||||
bail!(ident.span(), "not enough elements to destructure");
|
bail!(expr.span(), "not enough elements to destructure");
|
||||||
};
|
};
|
||||||
vm.define(ident.clone(), v.clone());
|
f(vm, expr, v.clone())?;
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
ast::DestructuringKind::Sink(ident) => {
|
ast::DestructuringKind::Sink(spread) => {
|
||||||
(1 + value.len() as usize).checked_sub(destruct.bindings().count()).and_then(|sink_size| {
|
let sink_size = (1 + value.len() as usize)
|
||||||
let Ok(sink) = value.slice(i, Some(i + sink_size as i64)) else {
|
.checked_sub(destruct.bindings().count());
|
||||||
return None;
|
let sink =
|
||||||
};
|
sink_size.and_then(|s| value.slice(i, Some(i + s as i64)).ok());
|
||||||
if let Some(ident) = ident {
|
|
||||||
vm.define(ident, sink);
|
if let (Some(sink_size), Some(sink)) = (sink_size, sink) {
|
||||||
|
if let Some(expr) = spread.expr() {
|
||||||
|
f(vm, expr, Value::Array(sink.clone()))?;
|
||||||
}
|
}
|
||||||
i += sink_size as i64;
|
i += sink_size as i64;
|
||||||
Some(())
|
} else {
|
||||||
}).ok_or("not enough elements to destructure").at(self.span())?;
|
bail!(self.span(), "not enough elements to destructure")
|
||||||
}
|
}
|
||||||
ast::DestructuringKind::Named(key, _) => {
|
|
||||||
bail!(
|
|
||||||
key.span(),
|
|
||||||
"cannot destructure named elements from an array"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
ast::DestructuringKind::Placeholder => i += 1,
|
ast::DestructuringKind::Named(named) => {
|
||||||
|
bail!(named.span(), "cannot destructure named elements from an array")
|
||||||
|
}
|
||||||
|
ast::DestructuringKind::Placeholder(_) => i += 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i < value.len() {
|
if i < value.len() {
|
||||||
bail!(self.span(), "too many elements to destructure");
|
bail!(self.span(), "too many elements to destructure");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
Value::Dict(value) => {
|
|
||||||
|
fn destruct_dict<T>(
|
||||||
|
&self,
|
||||||
|
vm: &mut Vm,
|
||||||
|
value: Dict,
|
||||||
|
f: T,
|
||||||
|
destruct: &ast::Destructuring,
|
||||||
|
) -> SourceResult<Value>
|
||||||
|
where
|
||||||
|
T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>,
|
||||||
|
{
|
||||||
let mut sink = None;
|
let mut sink = None;
|
||||||
let mut used = HashSet::new();
|
let mut used = HashSet::new();
|
||||||
for p in destruct.bindings() {
|
for p in destruct.bindings() {
|
||||||
match p {
|
match p {
|
||||||
ast::DestructuringKind::Ident(ident) => {
|
ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => {
|
||||||
let Ok(v) = value.at(&ident) else {
|
let Ok(v) = value.at(&ident) else {
|
||||||
bail!(ident.span(), "destructuring key not found in dictionary");
|
bail!(ident.span(), "destructuring key not found in dictionary");
|
||||||
};
|
};
|
||||||
vm.define(ident.clone(), v.clone());
|
f(vm, ast::Expr::Ident(ident.clone()), v.clone())?;
|
||||||
used.insert(ident.clone().take());
|
used.insert(ident.take());
|
||||||
}
|
}
|
||||||
ast::DestructuringKind::Sink(ident) => {
|
ast::DestructuringKind::Sink(spread) => sink = spread.expr(),
|
||||||
sink = ident.clone()
|
ast::DestructuringKind::Named(named) => {
|
||||||
}
|
let Ok(v) = value.at(named.name().as_str()) else {
|
||||||
ast::DestructuringKind::Named(key, ident) => {
|
bail!(named.name().span(), "destructuring key not found in dictionary");
|
||||||
let Ok(v) = value.at(&key) else {
|
|
||||||
bail!(ident.span(), "destructuring key not found in dictionary");
|
|
||||||
};
|
};
|
||||||
vm.define(ident.clone(), v.clone());
|
f(vm, named.expr(), v.clone())?;
|
||||||
used.insert(key.clone().take());
|
used.insert(named.name().take());
|
||||||
|
}
|
||||||
|
ast::DestructuringKind::Placeholder(_) => {}
|
||||||
|
ast::DestructuringKind::Normal(expr) => {
|
||||||
|
bail!(expr.span(), "expected key, found expression");
|
||||||
}
|
}
|
||||||
ast::DestructuringKind::Placeholder => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ident) = sink {
|
if let Some(expr) = sink {
|
||||||
let mut sink = Dict::new();
|
let mut sink = Dict::new();
|
||||||
for (key, value) in value {
|
for (key, value) in value {
|
||||||
if !used.contains(key.as_str()) {
|
if !used.contains(key.as_str()) {
|
||||||
sink.insert(key, value);
|
sink.insert(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vm.define(ident, Value::Dict(sink));
|
f(vm, expr, Value::Dict(sink))?;
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
bail!(self.span(), "cannot destructure {}", value.type_name());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Destruct the given value into the pattern and apply the function to each binding.
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
fn apply<T>(&self, vm: &mut Vm, value: Value, f: T) -> SourceResult<Value>
|
||||||
|
where
|
||||||
|
T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<Value>,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
ast::Pattern::Normal(expr) => {
|
||||||
|
f(vm, expr.clone(), value)?;
|
||||||
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
|
ast::Pattern::Placeholder(_) => Ok(Value::None),
|
||||||
|
ast::Pattern::Destructuring(destruct) => match value {
|
||||||
|
Value::Array(value) => self.destruct_array(vm, value, f, destruct),
|
||||||
|
Value::Dict(value) => self.destruct_dict(vm, value, f, destruct),
|
||||||
|
_ => bail!(self.span(), "cannot destructure {}", value.type_name()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destruct the value into the pattern by binding.
|
||||||
|
pub fn define(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> {
|
||||||
|
self.apply(vm, value, |vm, expr, value| match expr {
|
||||||
|
ast::Expr::Ident(ident) => {
|
||||||
|
vm.define(ident, value);
|
||||||
|
Ok(Value::None)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destruct the value into the pattern by assignment.
|
||||||
|
pub fn assign(&self, vm: &mut Vm, value: Value) -> SourceResult<Value> {
|
||||||
|
self.apply(vm, value, |vm, expr, value| {
|
||||||
|
let location = expr.access(vm)?;
|
||||||
|
*location = value;
|
||||||
|
Ok(Value::None)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1345,6 +1390,16 @@ impl Eval for ast::LetBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Eval for ast::DestructAssignment {
|
||||||
|
type Output = Value;
|
||||||
|
|
||||||
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
|
let value = self.value().eval(vm)?;
|
||||||
|
self.pattern().assign(vm, value)?;
|
||||||
|
Ok(Value::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Eval for ast::SetRule {
|
impl Eval for ast::SetRule {
|
||||||
type Output = Styles;
|
type Output = Styles;
|
||||||
|
|
||||||
@ -1514,7 +1569,7 @@ impl Eval for ast::ForLoop {
|
|||||||
let pattern = self.pattern();
|
let pattern = self.pattern();
|
||||||
|
|
||||||
match (&pattern, iter.clone()) {
|
match (&pattern, iter.clone()) {
|
||||||
(ast::Pattern::Ident(_), Value::Str(string)) => {
|
(ast::Pattern::Normal(_), Value::Str(string)) => {
|
||||||
// Iterate over graphemes of string.
|
// Iterate over graphemes of string.
|
||||||
iter!(for pattern in string.as_str().graphemes(true));
|
iter!(for pattern in string.as_str().graphemes(true));
|
||||||
}
|
}
|
||||||
@ -1526,7 +1581,7 @@ impl Eval for ast::ForLoop {
|
|||||||
// Iterate over values of array.
|
// Iterate over values of array.
|
||||||
iter!(for pattern in array);
|
iter!(for pattern in array);
|
||||||
}
|
}
|
||||||
(ast::Pattern::Ident(_), _) => {
|
(ast::Pattern::Normal(_), _) => {
|
||||||
bail!(self.iter().span(), "cannot loop over {}", iter.type_name());
|
bail!(self.iter().span(), "cannot loop over {}", iter.type_name());
|
||||||
}
|
}
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
|
@ -246,6 +246,7 @@ pub fn highlight(node: &LinkedNode) -> Option<Tag> {
|
|||||||
SyntaxKind::LoopContinue => None,
|
SyntaxKind::LoopContinue => None,
|
||||||
SyntaxKind::FuncReturn => None,
|
SyntaxKind::FuncReturn => None,
|
||||||
SyntaxKind::Destructuring => None,
|
SyntaxKind::Destructuring => None,
|
||||||
|
SyntaxKind::DestructAssignment => None,
|
||||||
|
|
||||||
SyntaxKind::LineComment => Some(Tag::Comment),
|
SyntaxKind::LineComment => Some(Tag::Comment),
|
||||||
SyntaxKind::BlockComment => Some(Tag::Comment),
|
SyntaxKind::BlockComment => Some(Tag::Comment),
|
||||||
|
@ -164,6 +164,8 @@ pub enum Expr {
|
|||||||
Closure(Closure),
|
Closure(Closure),
|
||||||
/// A let binding: `let x = 1`.
|
/// A let binding: `let x = 1`.
|
||||||
Let(LetBinding),
|
Let(LetBinding),
|
||||||
|
//// A destructuring assignment: `(x, y) = (1, 2)`.
|
||||||
|
DestructAssign(DestructAssignment),
|
||||||
/// A set rule: `set text(...)`.
|
/// A set rule: `set text(...)`.
|
||||||
Set(SetRule),
|
Set(SetRule),
|
||||||
/// A show rule: `show heading: it => emph(it.body)`.
|
/// A show rule: `show heading: it => emph(it.body)`.
|
||||||
@ -240,6 +242,7 @@ impl AstNode for Expr {
|
|||||||
SyntaxKind::FuncCall => node.cast().map(Self::FuncCall),
|
SyntaxKind::FuncCall => node.cast().map(Self::FuncCall),
|
||||||
SyntaxKind::Closure => node.cast().map(Self::Closure),
|
SyntaxKind::Closure => node.cast().map(Self::Closure),
|
||||||
SyntaxKind::LetBinding => node.cast().map(Self::Let),
|
SyntaxKind::LetBinding => node.cast().map(Self::Let),
|
||||||
|
SyntaxKind::DestructAssignment => node.cast().map(Self::DestructAssign),
|
||||||
SyntaxKind::SetRule => node.cast().map(Self::Set),
|
SyntaxKind::SetRule => node.cast().map(Self::Set),
|
||||||
SyntaxKind::ShowRule => node.cast().map(Self::Show),
|
SyntaxKind::ShowRule => node.cast().map(Self::Show),
|
||||||
SyntaxKind::Conditional => node.cast().map(Self::Conditional),
|
SyntaxKind::Conditional => node.cast().map(Self::Conditional),
|
||||||
@ -299,6 +302,7 @@ impl AstNode for Expr {
|
|||||||
Self::FuncCall(v) => v.as_untyped(),
|
Self::FuncCall(v) => v.as_untyped(),
|
||||||
Self::Closure(v) => v.as_untyped(),
|
Self::Closure(v) => v.as_untyped(),
|
||||||
Self::Let(v) => v.as_untyped(),
|
Self::Let(v) => v.as_untyped(),
|
||||||
|
Self::DestructAssign(v) => v.as_untyped(),
|
||||||
Self::Set(v) => v.as_untyped(),
|
Self::Set(v) => v.as_untyped(),
|
||||||
Self::Show(v) => v.as_untyped(),
|
Self::Show(v) => v.as_untyped(),
|
||||||
Self::Conditional(v) => v.as_untyped(),
|
Self::Conditional(v) => v.as_untyped(),
|
||||||
@ -1179,6 +1183,11 @@ impl Named {
|
|||||||
pub fn expr(&self) -> Expr {
|
pub fn expr(&self) -> Expr {
|
||||||
self.0.cast_last_match().unwrap_or_default()
|
self.0.cast_last_match().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The right-hand side of the pair as an identifier.
|
||||||
|
pub fn expr_ident(&self) -> Option<Ident> {
|
||||||
|
self.0.cast_last_match()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
@ -1559,6 +1568,28 @@ impl Params {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node! {
|
||||||
|
/// A spread: `..x` or `..x.at(0)`.
|
||||||
|
Spread
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spread {
|
||||||
|
/// Try to get an identifier.
|
||||||
|
pub fn name(&self) -> Option<Ident> {
|
||||||
|
self.0.cast_first_match()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to get an expression.
|
||||||
|
pub fn expr(&self) -> Option<Expr> {
|
||||||
|
self.0.cast_first_match()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node! {
|
||||||
|
/// An underscore: `_`
|
||||||
|
Underscore
|
||||||
|
}
|
||||||
|
|
||||||
/// A parameter to a closure.
|
/// A parameter to a closure.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum Param {
|
pub enum Param {
|
||||||
@ -1567,9 +1598,9 @@ pub enum Param {
|
|||||||
/// A named parameter with a default value: `draw: false`.
|
/// A named parameter with a default value: `draw: false`.
|
||||||
Named(Named),
|
Named(Named),
|
||||||
/// An argument sink: `..args`.
|
/// An argument sink: `..args`.
|
||||||
Sink(Option<Ident>),
|
Sink(Spread),
|
||||||
/// A placeholder: `_`.
|
/// A placeholder: `_`.
|
||||||
Placeholder,
|
Placeholder(Underscore),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AstNode for Param {
|
impl AstNode for Param {
|
||||||
@ -1577,8 +1608,8 @@ impl AstNode for Param {
|
|||||||
match node.kind() {
|
match node.kind() {
|
||||||
SyntaxKind::Ident => node.cast().map(Self::Pos),
|
SyntaxKind::Ident => node.cast().map(Self::Pos),
|
||||||
SyntaxKind::Named => node.cast().map(Self::Named),
|
SyntaxKind::Named => node.cast().map(Self::Named),
|
||||||
SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())),
|
SyntaxKind::Spread => node.cast().map(Self::Sink),
|
||||||
SyntaxKind::Underscore => Some(Self::Placeholder),
|
SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
|
||||||
_ => Option::None,
|
_ => Option::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1587,8 +1618,8 @@ impl AstNode for Param {
|
|||||||
match self {
|
match self {
|
||||||
Self::Pos(v) => v.as_untyped(),
|
Self::Pos(v) => v.as_untyped(),
|
||||||
Self::Named(v) => v.as_untyped(),
|
Self::Named(v) => v.as_untyped(),
|
||||||
Self::Sink(_) => self.as_untyped(),
|
Self::Sink(v) => v.as_untyped(),
|
||||||
Self::Placeholder => self.as_untyped(),
|
Self::Placeholder(v) => v.as_untyped(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1598,56 +1629,63 @@ node! {
|
|||||||
Destructuring
|
Destructuring
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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),
|
|
||||||
/// A placeholder: `_`.
|
|
||||||
Placeholder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Destructuring {
|
impl Destructuring {
|
||||||
/// The bindings of the destructuring.
|
/// The bindings of the destructuring.
|
||||||
pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ {
|
pub fn bindings(&self) -> impl Iterator<Item = DestructuringKind> + '_ {
|
||||||
self.0.children().filter_map(|child| match child.kind() {
|
self.0.children().filter_map(SyntaxNode::cast)
|
||||||
SyntaxKind::Ident => {
|
|
||||||
Some(DestructuringKind::Ident(child.cast().unwrap_or_default()))
|
|
||||||
}
|
|
||||||
SyntaxKind::Spread => Some(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();
|
|
||||||
Some(DestructuringKind::Named(key, ident))
|
|
||||||
}
|
|
||||||
SyntaxKind::Underscore => Some(DestructuringKind::Placeholder),
|
|
||||||
_ => Option::None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a list of all identifiers in the pattern.
|
// Returns a list of all identifiers in the pattern.
|
||||||
pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ {
|
pub fn idents(&self) -> impl Iterator<Item = Ident> + '_ {
|
||||||
self.bindings().filter_map(|binding| match binding {
|
self.bindings().filter_map(|binding| match binding {
|
||||||
DestructuringKind::Ident(ident) => Some(ident),
|
DestructuringKind::Normal(Expr::Ident(ident)) => Some(ident),
|
||||||
DestructuringKind::Sink(ident) => ident,
|
DestructuringKind::Sink(spread) => spread.name(),
|
||||||
DestructuringKind::Named(_, ident) => Some(ident),
|
DestructuringKind::Named(named) => named.expr_ident(),
|
||||||
DestructuringKind::Placeholder => Option::None,
|
_ => Option::None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The kind of an element in a destructuring pattern.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub enum DestructuringKind {
|
||||||
|
/// An expression: `x`.
|
||||||
|
Normal(Expr),
|
||||||
|
/// An argument sink: `..y`.
|
||||||
|
Sink(Spread),
|
||||||
|
/// Named arguments: `x: 1`.
|
||||||
|
Named(Named),
|
||||||
|
/// A placeholder: `_`.
|
||||||
|
Placeholder(Underscore),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AstNode for DestructuringKind {
|
||||||
|
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
|
||||||
|
match node.kind() {
|
||||||
|
SyntaxKind::Named => node.cast().map(Self::Named),
|
||||||
|
SyntaxKind::Spread => node.cast().map(Self::Sink),
|
||||||
|
SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
|
||||||
|
_ => node.cast().map(Self::Normal),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_untyped(&self) -> &SyntaxNode {
|
||||||
|
match self {
|
||||||
|
Self::Normal(v) => v.as_untyped(),
|
||||||
|
Self::Named(v) => v.as_untyped(),
|
||||||
|
Self::Sink(v) => v.as_untyped(),
|
||||||
|
Self::Placeholder(v) => v.as_untyped(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The kind of a pattern.
|
/// The kind of a pattern.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub enum Pattern {
|
pub enum Pattern {
|
||||||
/// A single identifier: `x`.
|
/// A single expression: `x`.
|
||||||
Ident(Ident),
|
Normal(Expr),
|
||||||
/// A placeholder: `_`.
|
/// A placeholder: `_`.
|
||||||
Placeholder,
|
Placeholder(Underscore),
|
||||||
/// A destructuring pattern: `(x, _, ..y)`.
|
/// A destructuring pattern: `(x, _, ..y)`.
|
||||||
Destructuring(Destructuring),
|
Destructuring(Destructuring),
|
||||||
}
|
}
|
||||||
@ -1655,18 +1693,17 @@ pub enum Pattern {
|
|||||||
impl AstNode for Pattern {
|
impl AstNode for Pattern {
|
||||||
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
|
fn from_untyped(node: &SyntaxNode) -> Option<Self> {
|
||||||
match node.kind() {
|
match node.kind() {
|
||||||
SyntaxKind::Ident => node.cast().map(Self::Ident),
|
|
||||||
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
|
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
|
||||||
SyntaxKind::Underscore => Some(Self::Placeholder),
|
SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
|
||||||
_ => Option::None,
|
_ => node.cast().map(Self::Normal),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_untyped(&self) -> &SyntaxNode {
|
fn as_untyped(&self) -> &SyntaxNode {
|
||||||
match self {
|
match self {
|
||||||
Self::Ident(v) => v.as_untyped(),
|
Self::Normal(v) => v.as_untyped(),
|
||||||
Self::Destructuring(v) => v.as_untyped(),
|
Self::Destructuring(v) => v.as_untyped(),
|
||||||
Self::Placeholder => self.as_untyped(),
|
Self::Placeholder(v) => v.as_untyped(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1675,16 +1712,16 @@ impl Pattern {
|
|||||||
// Returns a list of all identifiers in the pattern.
|
// Returns a list of all identifiers in the pattern.
|
||||||
pub fn idents(&self) -> Vec<Ident> {
|
pub fn idents(&self) -> Vec<Ident> {
|
||||||
match self {
|
match self {
|
||||||
Pattern::Ident(ident) => vec![ident.clone()],
|
Pattern::Normal(Expr::Ident(ident)) => vec![ident.clone()],
|
||||||
Pattern::Destructuring(destruct) => destruct.idents().collect(),
|
Pattern::Destructuring(destruct) => destruct.idents().collect(),
|
||||||
Pattern::Placeholder => vec![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Pattern {
|
impl Default for Pattern {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Ident(Ident::default())
|
Self::Normal(Expr::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1716,23 +1753,18 @@ impl LetBindingKind {
|
|||||||
impl LetBinding {
|
impl LetBinding {
|
||||||
/// The kind of the let binding.
|
/// The kind of the let binding.
|
||||||
pub fn kind(&self) -> LetBindingKind {
|
pub fn kind(&self) -> LetBindingKind {
|
||||||
if let Some(pattern) = self.0.cast_first_match::<Pattern>() {
|
match self.0.cast_first_match::<Pattern>() {
|
||||||
LetBindingKind::Normal(pattern)
|
Some(Pattern::Normal(Expr::Closure(closure))) => {
|
||||||
} else {
|
LetBindingKind::Closure(closure.name().unwrap_or_default())
|
||||||
LetBindingKind::Closure(
|
}
|
||||||
self.0
|
pattern => LetBindingKind::Normal(pattern.unwrap_or_default()),
|
||||||
.cast_first_match::<Closure>()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.name()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The expression the binding is initialized with.
|
/// The expression the binding is initialized with.
|
||||||
pub fn init(&self) -> Option<Expr> {
|
pub fn init(&self) -> Option<Expr> {
|
||||||
match self.kind() {
|
match self.kind() {
|
||||||
LetBindingKind::Normal(Pattern::Ident(_)) => {
|
LetBindingKind::Normal(Pattern::Normal(_)) => {
|
||||||
self.0.children().filter_map(SyntaxNode::cast).nth(1)
|
self.0.children().filter_map(SyntaxNode::cast).nth(1)
|
||||||
}
|
}
|
||||||
LetBindingKind::Normal(_) => self.0.cast_first_match(),
|
LetBindingKind::Normal(_) => self.0.cast_first_match(),
|
||||||
@ -1741,6 +1773,23 @@ impl LetBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node! {
|
||||||
|
/// An assignment expression `(x, y) = (1, 2)`.
|
||||||
|
DestructAssignment
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DestructAssignment {
|
||||||
|
/// The pattern of the assignment.
|
||||||
|
pub fn pattern(&self) -> Pattern {
|
||||||
|
self.0.cast_first_match::<Pattern>().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The expression that is assigned.
|
||||||
|
pub fn value(&self) -> Expr {
|
||||||
|
self.0.cast_last_match().unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A set rule: `set text(...)`.
|
/// A set rule: `set text(...)`.
|
||||||
SetRule
|
SetRule
|
||||||
|
@ -244,6 +244,8 @@ pub enum SyntaxKind {
|
|||||||
FuncReturn,
|
FuncReturn,
|
||||||
/// A destructuring pattern: `(x, _, ..y)`.
|
/// A destructuring pattern: `(x, _, ..y)`.
|
||||||
Destructuring,
|
Destructuring,
|
||||||
|
/// A destructuring assignment expression: `(x, y) = (1, 2)`.
|
||||||
|
DestructAssignment,
|
||||||
|
|
||||||
/// A line comment: `// ...`.
|
/// A line comment: `// ...`.
|
||||||
LineComment,
|
LineComment,
|
||||||
@ -430,6 +432,7 @@ impl SyntaxKind {
|
|||||||
Self::LoopContinue => "`continue` expression",
|
Self::LoopContinue => "`continue` expression",
|
||||||
Self::FuncReturn => "`return` expression",
|
Self::FuncReturn => "`return` expression",
|
||||||
Self::Destructuring => "destructuring pattern",
|
Self::Destructuring => "destructuring pattern",
|
||||||
|
Self::DestructAssignment => "destructuring assignment expression",
|
||||||
Self::LineComment => "line comment",
|
Self::LineComment => "line comment",
|
||||||
Self::BlockComment => "block comment",
|
Self::BlockComment => "block comment",
|
||||||
Self::Error => "syntax error",
|
Self::Error => "syntax error",
|
||||||
|
@ -725,7 +725,16 @@ fn with_paren(p: &mut Parser) {
|
|||||||
p.assert(SyntaxKind::Arrow);
|
p.assert(SyntaxKind::Arrow);
|
||||||
code_expr(p);
|
code_expr(p);
|
||||||
kind = SyntaxKind::Closure;
|
kind = SyntaxKind::Closure;
|
||||||
|
} else if p.at(SyntaxKind::Eq) && kind != SyntaxKind::Parenthesized {
|
||||||
|
// TODO: add warning if p.at(SyntaxKind::Eq) && kind == SyntaxKind::Parenthesized
|
||||||
|
|
||||||
|
validate_destruct_pattern(p, m, false);
|
||||||
|
p.wrap(m, SyntaxKind::Destructuring);
|
||||||
|
p.assert(SyntaxKind::Eq);
|
||||||
|
code_expr(p);
|
||||||
|
kind = SyntaxKind::DestructAssignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
SyntaxKind::Array => validate_array(p, m),
|
SyntaxKind::Array => validate_array(p, m),
|
||||||
SyntaxKind::Dict => validate_dict(p, m),
|
SyntaxKind::Dict => validate_dict(p, m),
|
||||||
@ -866,7 +875,7 @@ fn pattern(p: &mut Parser) -> PatternKind {
|
|||||||
let m = p.marker();
|
let m = p.marker();
|
||||||
if p.at(SyntaxKind::LeftParen) {
|
if p.at(SyntaxKind::LeftParen) {
|
||||||
let kind = collection(p, false);
|
let kind = collection(p, false);
|
||||||
validate_destruct_pattern(p, m);
|
validate_destruct_pattern(p, m, true);
|
||||||
|
|
||||||
if kind == SyntaxKind::Parenthesized {
|
if kind == SyntaxKind::Parenthesized {
|
||||||
PatternKind::Ident
|
PatternKind::Ident
|
||||||
@ -1184,7 +1193,7 @@ fn validate_args(p: &mut Parser, m: Marker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
fn validate_destruct_pattern(p: &mut Parser, m: Marker, forbid_expressions: bool) {
|
||||||
let mut used_spread = false;
|
let mut used_spread = false;
|
||||||
let mut used = HashSet::new();
|
let mut used = HashSet::new();
|
||||||
for child in p.post_process(m) {
|
for child in p.post_process(m) {
|
||||||
@ -1206,7 +1215,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
|||||||
|
|
||||||
if within.kind() == SyntaxKind::Dots {
|
if within.kind() == SyntaxKind::Dots {
|
||||||
continue;
|
continue;
|
||||||
} else if within.kind() != SyntaxKind::Ident {
|
} else if forbid_expressions && within.kind() != SyntaxKind::Ident {
|
||||||
within.convert_to_error(eco_format!(
|
within.convert_to_error(eco_format!(
|
||||||
"expected identifier, found {}",
|
"expected identifier, found {}",
|
||||||
within.kind().name(),
|
within.kind().name(),
|
||||||
@ -1231,6 +1240,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
|||||||
child.make_erroneous();
|
child.make_erroneous();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if forbid_expressions {
|
||||||
let Some(within) = child.children_mut().last_mut() else { return };
|
let Some(within) = child.children_mut().last_mut() else { return };
|
||||||
if within.kind() != SyntaxKind::Ident
|
if within.kind() != SyntaxKind::Ident
|
||||||
&& within.kind() != SyntaxKind::Underscore
|
&& within.kind() != SyntaxKind::Underscore
|
||||||
@ -1242,11 +1252,13 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
|||||||
child.make_erroneous();
|
child.make_erroneous();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SyntaxKind::LeftParen
|
SyntaxKind::LeftParen
|
||||||
| SyntaxKind::RightParen
|
| SyntaxKind::RightParen
|
||||||
| SyntaxKind::Comma
|
| SyntaxKind::Comma
|
||||||
| SyntaxKind::Underscore => {}
|
| SyntaxKind::Underscore => {}
|
||||||
kind => {
|
kind => {
|
||||||
|
if forbid_expressions {
|
||||||
child.convert_to_error(eco_format!(
|
child.convert_to_error(eco_format!(
|
||||||
"expected identifier or destructuring sink, found {}",
|
"expected identifier or destructuring sink, found {}",
|
||||||
kind.name()
|
kind.name()
|
||||||
@ -1254,6 +1266,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manages parsing of a stream of tokens.
|
/// Manages parsing of a stream of tokens.
|
||||||
|
@ -121,6 +121,12 @@ Three
|
|||||||
// Error: 13-14 at most one binding per identifier is allowed
|
// Error: 13-14 at most one binding per identifier is allowed
|
||||||
#let (a: a, a) = (a: 1, b: 2)
|
#let (a: a, a) = (a: 1, b: 2)
|
||||||
|
|
||||||
|
// Error: 13-20 expected identifier, found function call
|
||||||
|
#let (a, b: b.at(0)) = (a: 1, b: 2)
|
||||||
|
|
||||||
|
// Error: 7-14 expected identifier or destructuring sink, found function call
|
||||||
|
#let (a.at(0),) = (1,)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 13-14 not enough elements to destructure
|
// Error: 13-14 not enough elements to destructure
|
||||||
#let (a, b, c) = (1, 2)
|
#let (a, b, c) = (1, 2)
|
||||||
@ -181,11 +187,11 @@ Three
|
|||||||
#let (a, b) = (a: 1)
|
#let (a, b) = (a: 1)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 13-14 destructuring key not found in dictionary
|
// Error: 10-11 destructuring key not found in dictionary
|
||||||
#let (a, b: b) = (a: 1)
|
#let (a, b: b) = (a: 1)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 7-8 cannot destructure named elements from an array
|
// Error: 7-11 cannot destructure named elements from an array
|
||||||
#let (a: a, b) = (1, 2, 3)
|
#let (a: a, b) = (1, 2, 3)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -100,6 +100,10 @@
|
|||||||
// Error: 4-5 unknown variable: x
|
// Error: 4-5 unknown variable: x
|
||||||
#((x) = "")
|
#((x) = "")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 4-5 unknown variable: x
|
||||||
|
#((x,) = (1,))
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-8 cannot mutate a temporary value
|
// Error: 3-8 cannot mutate a temporary value
|
||||||
#(1 + 2 += 3)
|
#(1 + 2 += 3)
|
||||||
|
@ -200,6 +200,48 @@
|
|||||||
#(x = "some") #test(x, "some")
|
#(x = "some") #test(x, "some")
|
||||||
#(x += "thing") #test(x, "something")
|
#(x += "thing") #test(x, "something")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test destructuring assignments.
|
||||||
|
|
||||||
|
#let a = none
|
||||||
|
#let b = none
|
||||||
|
#let c = none
|
||||||
|
#((a,) = (1,))
|
||||||
|
#test(a, 1)
|
||||||
|
|
||||||
|
#((_, a, b, _) = (1, 2, 3, 4))
|
||||||
|
#test(a, 2)
|
||||||
|
#test(b, 3)
|
||||||
|
|
||||||
|
#((a, b, ..c) = (1, 2, 3, 4, 5, 6))
|
||||||
|
#test(a, 1)
|
||||||
|
#test(b, 2)
|
||||||
|
#test(c, (3, 4, 5, 6))
|
||||||
|
|
||||||
|
#((a: a, b, x: c) = (a: 1, b: 2, x: 3))
|
||||||
|
#test(a, 1)
|
||||||
|
#test(b, 2)
|
||||||
|
#test(c, 3)
|
||||||
|
|
||||||
|
#let a = (1, 2)
|
||||||
|
#((a: a.at(0), b) = (a: 3, b: 4))
|
||||||
|
#test(a, (3, 2))
|
||||||
|
#test(b, 4)
|
||||||
|
|
||||||
|
#let a = (1, 2)
|
||||||
|
#((a.at(0), b) = (3, 4))
|
||||||
|
#test(a, (3, 2))
|
||||||
|
#test(b, 4)
|
||||||
|
|
||||||
|
#((a, ..b) = (1, 2, 3, 4))
|
||||||
|
#test(a, 1)
|
||||||
|
#test(b, (2, 3, 4))
|
||||||
|
|
||||||
|
#let a = (1, 2)
|
||||||
|
#((b, ..a.at(0)) = (1, 2, 3, 4))
|
||||||
|
#test(a, ((2, 3, 4), 2))
|
||||||
|
#test(b, 1)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 3-6 cannot mutate a constant: box
|
// Error: 3-6 cannot mutate a constant: box
|
||||||
#(box = 1)
|
#(box = 1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user