Destructuring improvements (#3463)
This commit is contained in:
parent
b2e509d472
commit
be49935753
@ -1285,7 +1285,7 @@ impl<'a> CompletionContext<'a> {
|
||||
let mut sibling = Some(node.clone());
|
||||
while let Some(node) = &sibling {
|
||||
if let Some(v) = node.cast::<ast::LetBinding>() {
|
||||
for ident in v.kind().idents() {
|
||||
for ident in v.kind().bindings() {
|
||||
defined.insert(ident.get().clone());
|
||||
}
|
||||
}
|
||||
@ -1323,7 +1323,7 @@ impl<'a> CompletionContext<'a> {
|
||||
if let Some(v) = parent.cast::<ast::ForLoop>() {
|
||||
if node.prev_sibling_kind() != Some(SyntaxKind::In) {
|
||||
let pattern = v.pattern();
|
||||
for ident in pattern.idents() {
|
||||
for ident in pattern.bindings() {
|
||||
defined.insert(ident.get().clone());
|
||||
}
|
||||
}
|
||||
|
@ -1157,9 +1157,18 @@ node! {
|
||||
|
||||
impl<'a> Parenthesized<'a> {
|
||||
/// The wrapped expression.
|
||||
///
|
||||
/// Should only be accessed if this is contained in an `Expr`.
|
||||
pub fn expr(self) -> Expr<'a> {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The wrapped pattern.
|
||||
///
|
||||
/// Should only be accessed if this is contained in a `Pattern`.
|
||||
pub fn pattern(self) -> Pattern<'a> {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
@ -1180,13 +1189,13 @@ pub enum ArrayItem<'a> {
|
||||
/// A bare expression: `12`.
|
||||
Pos(Expr<'a>),
|
||||
/// A spread expression: `..things`.
|
||||
Spread(Expr<'a>),
|
||||
Spread(Spread<'a>),
|
||||
}
|
||||
|
||||
impl<'a> AstNode<'a> for ArrayItem<'a> {
|
||||
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
|
||||
match node.kind() {
|
||||
SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
|
||||
SyntaxKind::Spread => node.cast().map(Self::Spread),
|
||||
_ => node.cast().map(Self::Pos),
|
||||
}
|
||||
}
|
||||
@ -1219,7 +1228,7 @@ pub enum DictItem<'a> {
|
||||
/// A keyed pair: `"spacy key": true`.
|
||||
Keyed(Keyed<'a>),
|
||||
/// A spread expression: `..things`.
|
||||
Spread(Expr<'a>),
|
||||
Spread(Spread<'a>),
|
||||
}
|
||||
|
||||
impl<'a> AstNode<'a> for DictItem<'a> {
|
||||
@ -1227,7 +1236,7 @@ impl<'a> AstNode<'a> for DictItem<'a> {
|
||||
match node.kind() {
|
||||
SyntaxKind::Named => node.cast().map(Self::Named),
|
||||
SyntaxKind::Keyed => node.cast().map(Self::Keyed),
|
||||
SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
|
||||
SyntaxKind::Spread => node.cast().map(Self::Spread),
|
||||
_ => Option::None,
|
||||
}
|
||||
}
|
||||
@ -1253,13 +1262,19 @@ impl<'a> Named<'a> {
|
||||
}
|
||||
|
||||
/// The right-hand side of the pair: `3pt`.
|
||||
///
|
||||
/// This should only be accessed if this `Named` is contained in a
|
||||
/// `DictItem`, `Arg`, or `Param`.
|
||||
pub fn expr(self) -> Expr<'a> {
|
||||
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<'a>> {
|
||||
self.0.cast_last_match()
|
||||
/// The right-hand side of the pair as a pattern.
|
||||
///
|
||||
/// This should only be accessed if this `Named` is contained in a
|
||||
/// `Destructuring`.
|
||||
pub fn pattern(self) -> Pattern<'a> {
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1275,11 +1290,45 @@ impl<'a> Keyed<'a> {
|
||||
}
|
||||
|
||||
/// The right-hand side of the pair: `true`.
|
||||
///
|
||||
/// This should only be accessed if this `Keyed` is contained in a
|
||||
/// `DictItem`.
|
||||
pub fn expr(self) -> Expr<'a> {
|
||||
self.0.cast_last_match().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A spread: `..x` or `..x.at(0)`.
|
||||
Spread
|
||||
}
|
||||
|
||||
impl<'a> Spread<'a> {
|
||||
/// The spreaded expression.
|
||||
///
|
||||
/// This should only be accessed if this `Spread` is contained in an
|
||||
/// `ArrayItem`, `DictItem`, or `Arg`.
|
||||
pub fn expr(self) -> Expr<'a> {
|
||||
self.0.cast_first_match().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// The sink identifier, if present.
|
||||
///
|
||||
/// This should only be accessed if this `Spread` is contained in a
|
||||
/// `Param` or binding `DestructuringItem`.
|
||||
pub fn sink_ident(self) -> Option<Ident<'a>> {
|
||||
self.0.cast_first_match()
|
||||
}
|
||||
|
||||
/// The sink expressions, if present.
|
||||
///
|
||||
/// This should only be accessed if this `Spread` is contained in a
|
||||
/// `DestructuringItem`.
|
||||
pub fn sink_expr(self) -> Option<Expr<'a>> {
|
||||
self.0.cast_first_match()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A unary operation: `-x`.
|
||||
Unary
|
||||
@ -1591,14 +1640,14 @@ pub enum Arg<'a> {
|
||||
/// A named argument: `draw: false`.
|
||||
Named(Named<'a>),
|
||||
/// A spread argument: `..things`.
|
||||
Spread(Expr<'a>),
|
||||
Spread(Spread<'a>),
|
||||
}
|
||||
|
||||
impl<'a> AstNode<'a> for Arg<'a> {
|
||||
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
|
||||
match node.kind() {
|
||||
SyntaxKind::Named => node.cast().map(Self::Named),
|
||||
SyntaxKind::Spread => node.cast_first_match().map(Self::Spread),
|
||||
SyntaxKind::Spread => node.cast().map(Self::Spread),
|
||||
_ => node.cast().map(Self::Pos),
|
||||
}
|
||||
}
|
||||
@ -1648,28 +1697,6 @@ impl<'a> Params<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A spread: `..x` or `..x.at(0)`.
|
||||
Spread
|
||||
}
|
||||
|
||||
impl<'a> Spread<'a> {
|
||||
/// Try to get an identifier.
|
||||
pub fn name(self) -> Option<Ident<'a>> {
|
||||
self.0.cast_first_match()
|
||||
}
|
||||
|
||||
/// Try to get an expression.
|
||||
pub fn expr(self) -> Option<Expr<'a>> {
|
||||
self.0.cast_first_match()
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// An underscore: `_`
|
||||
Underscore
|
||||
}
|
||||
|
||||
/// A parameter to a closure.
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
pub enum Param<'a> {
|
||||
@ -1677,15 +1704,15 @@ pub enum Param<'a> {
|
||||
Pos(Pattern<'a>),
|
||||
/// A named parameter with a default value: `draw: false`.
|
||||
Named(Named<'a>),
|
||||
/// An argument sink: `..args`.
|
||||
Sink(Spread<'a>),
|
||||
/// An argument sink: `..args` or `..`.
|
||||
Spread(Spread<'a>),
|
||||
}
|
||||
|
||||
impl<'a> AstNode<'a> for Param<'a> {
|
||||
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
|
||||
match node.kind() {
|
||||
SyntaxKind::Named => node.cast().map(Self::Named),
|
||||
SyntaxKind::Spread => node.cast().map(Self::Sink),
|
||||
SyntaxKind::Spread => node.cast().map(Self::Spread),
|
||||
_ => node.cast().map(Self::Pos),
|
||||
}
|
||||
}
|
||||
@ -1694,62 +1721,7 @@ impl<'a> AstNode<'a> for Param<'a> {
|
||||
match self {
|
||||
Self::Pos(v) => v.to_untyped(),
|
||||
Self::Named(v) => v.to_untyped(),
|
||||
Self::Sink(v) => v.to_untyped(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A destructuring pattern: `x` or `(x, _, ..y)`.
|
||||
Destructuring
|
||||
}
|
||||
|
||||
impl<'a> Destructuring<'a> {
|
||||
/// The bindings of the destructuring.
|
||||
pub fn bindings(self) -> impl DoubleEndedIterator<Item = DestructuringKind<'a>> {
|
||||
self.0.children().filter_map(SyntaxNode::cast)
|
||||
}
|
||||
|
||||
/// Returns a list of all identifiers in the pattern.
|
||||
pub fn idents(self) -> impl DoubleEndedIterator<Item = Ident<'a>> {
|
||||
self.bindings().filter_map(|binding| match binding {
|
||||
DestructuringKind::Normal(Expr::Ident(ident)) => Some(ident),
|
||||
DestructuringKind::Sink(spread) => spread.name(),
|
||||
DestructuringKind::Named(named) => named.expr_ident(),
|
||||
_ => Option::None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of an element in a destructuring pattern.
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
pub enum DestructuringKind<'a> {
|
||||
/// An expression: `x`.
|
||||
Normal(Expr<'a>),
|
||||
/// An argument sink: `..y`.
|
||||
Sink(Spread<'a>),
|
||||
/// Named arguments: `x: 1`.
|
||||
Named(Named<'a>),
|
||||
/// A placeholder: `_`.
|
||||
Placeholder(Underscore<'a>),
|
||||
}
|
||||
|
||||
impl<'a> AstNode<'a> for DestructuringKind<'a> {
|
||||
fn from_untyped(node: &'a 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 to_untyped(self) -> &'a SyntaxNode {
|
||||
match self {
|
||||
Self::Normal(v) => v.to_untyped(),
|
||||
Self::Named(v) => v.to_untyped(),
|
||||
Self::Sink(v) => v.to_untyped(),
|
||||
Self::Placeholder(v) => v.to_untyped(),
|
||||
Self::Spread(v) => v.to_untyped(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1761,6 +1733,8 @@ pub enum Pattern<'a> {
|
||||
Normal(Expr<'a>),
|
||||
/// A placeholder: `_`.
|
||||
Placeholder(Underscore<'a>),
|
||||
/// A parenthesized pattern.
|
||||
Parenthesized(Parenthesized<'a>),
|
||||
/// A destructuring pattern: `(x, _, ..y)`.
|
||||
Destructuring(Destructuring<'a>),
|
||||
}
|
||||
@ -1768,8 +1742,9 @@ pub enum Pattern<'a> {
|
||||
impl<'a> AstNode<'a> for Pattern<'a> {
|
||||
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
|
||||
match node.kind() {
|
||||
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
|
||||
SyntaxKind::Underscore => node.cast().map(Self::Placeholder),
|
||||
SyntaxKind::Parenthesized => node.cast().map(Self::Parenthesized),
|
||||
SyntaxKind::Destructuring => node.cast().map(Self::Destructuring),
|
||||
_ => node.cast().map(Self::Normal),
|
||||
}
|
||||
}
|
||||
@ -1777,18 +1752,20 @@ impl<'a> AstNode<'a> for Pattern<'a> {
|
||||
fn to_untyped(self) -> &'a SyntaxNode {
|
||||
match self {
|
||||
Self::Normal(v) => v.to_untyped(),
|
||||
Self::Destructuring(v) => v.to_untyped(),
|
||||
Self::Placeholder(v) => v.to_untyped(),
|
||||
Self::Parenthesized(v) => v.to_untyped(),
|
||||
Self::Destructuring(v) => v.to_untyped(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Pattern<'a> {
|
||||
/// Returns a list of all identifiers in the pattern.
|
||||
pub fn idents(self) -> Vec<Ident<'a>> {
|
||||
/// Returns a list of all new bindings introduced by the pattern.
|
||||
pub fn bindings(self) -> Vec<Ident<'a>> {
|
||||
match self {
|
||||
Pattern::Normal(Expr::Ident(ident)) => vec![ident],
|
||||
Pattern::Destructuring(destruct) => destruct.idents().collect(),
|
||||
Self::Normal(Expr::Ident(ident)) => vec![ident],
|
||||
Self::Parenthesized(v) => v.pattern().bindings(),
|
||||
Self::Destructuring(v) => v.bindings(),
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
@ -1800,6 +1777,65 @@ impl Default for Pattern<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// An underscore: `_`
|
||||
Underscore
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A destructuring pattern: `x` or `(x, _, ..y)`.
|
||||
Destructuring
|
||||
}
|
||||
|
||||
impl<'a> Destructuring<'a> {
|
||||
/// The items of the destructuring.
|
||||
pub fn items(self) -> impl DoubleEndedIterator<Item = DestructuringItem<'a>> {
|
||||
self.0.children().filter_map(SyntaxNode::cast)
|
||||
}
|
||||
|
||||
/// Returns a list of all new bindings introduced by the destructuring.
|
||||
pub fn bindings(self) -> Vec<Ident<'a>> {
|
||||
self.items()
|
||||
.flat_map(|binding| match binding {
|
||||
DestructuringItem::Pattern(pattern) => pattern.bindings(),
|
||||
DestructuringItem::Named(named) => named.pattern().bindings(),
|
||||
DestructuringItem::Spread(spread) => {
|
||||
spread.sink_ident().into_iter().collect()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of an element in a destructuring pattern.
|
||||
#[derive(Debug, Copy, Clone, Hash)]
|
||||
pub enum DestructuringItem<'a> {
|
||||
/// A sub-pattern: `x`.
|
||||
Pattern(Pattern<'a>),
|
||||
/// A renamed destructuring: `x: y`.
|
||||
Named(Named<'a>),
|
||||
/// A destructuring sink: `..y` or `..`.
|
||||
Spread(Spread<'a>),
|
||||
}
|
||||
|
||||
impl<'a> AstNode<'a> for DestructuringItem<'a> {
|
||||
fn from_untyped(node: &'a SyntaxNode) -> Option<Self> {
|
||||
match node.kind() {
|
||||
SyntaxKind::Named => node.cast().map(Self::Named),
|
||||
SyntaxKind::Spread => node.cast().map(Self::Spread),
|
||||
_ => node.cast().map(Self::Pattern),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_untyped(self) -> &'a SyntaxNode {
|
||||
match self {
|
||||
Self::Pattern(v) => v.to_untyped(),
|
||||
Self::Named(v) => v.to_untyped(),
|
||||
Self::Spread(v) => v.to_untyped(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node! {
|
||||
/// A let binding: `let x = 1`.
|
||||
LetBinding
|
||||
@ -1815,13 +1851,11 @@ pub enum LetBindingKind<'a> {
|
||||
}
|
||||
|
||||
impl<'a> LetBindingKind<'a> {
|
||||
/// Returns a list of all identifiers in the pattern.
|
||||
pub fn idents(self) -> Vec<Ident<'a>> {
|
||||
/// Returns a list of all new bindings introduced by the let binding.
|
||||
pub fn bindings(self) -> Vec<Ident<'a>> {
|
||||
match self {
|
||||
LetBindingKind::Normal(pattern) => pattern.idents(),
|
||||
LetBindingKind::Closure(ident) => {
|
||||
vec![ident]
|
||||
}
|
||||
LetBindingKind::Normal(pattern) => pattern.bindings(),
|
||||
LetBindingKind::Closure(ident) => vec![ident],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1840,7 +1874,7 @@ impl<'a> LetBinding<'a> {
|
||||
/// The expression the binding is initialized with.
|
||||
pub fn init(self) -> Option<Expr<'a>> {
|
||||
match self.kind() {
|
||||
LetBindingKind::Normal(Pattern::Normal(_)) => {
|
||||
LetBindingKind::Normal(Pattern::Normal(_) | Pattern::Parenthesized(_)) => {
|
||||
self.0.children().filter_map(SyntaxNode::cast).nth(1)
|
||||
}
|
||||
LetBindingKind::Normal(_) => self.0.cast_first_match(),
|
||||
|
@ -136,7 +136,7 @@ pub enum SyntaxKind {
|
||||
StarEq,
|
||||
/// The divide-assign operator: `/=`.
|
||||
SlashEq,
|
||||
/// The spread operator: `..`.
|
||||
/// Indicates a spread or sink: `..`.
|
||||
Dots,
|
||||
/// An arrow between a closure's parameters and body: `=>`.
|
||||
Arrow,
|
||||
|
@ -3,7 +3,7 @@ use std::ops::{Deref, Range};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::{eco_vec, EcoString, EcoVec};
|
||||
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
||||
|
||||
use crate::ast::AstNode;
|
||||
use crate::{FileId, Span, SyntaxKind};
|
||||
@ -177,14 +177,9 @@ impl SyntaxNode {
|
||||
}
|
||||
|
||||
impl SyntaxNode {
|
||||
/// Mark this node as erroneous.
|
||||
pub(super) fn make_erroneous(&mut self) {
|
||||
if let Repr::Inner(inner) = &mut self.0 {
|
||||
Arc::make_mut(inner).erroneous = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the child to another kind.
|
||||
///
|
||||
/// Don't use this for converting to an error!
|
||||
#[track_caller]
|
||||
pub(super) fn convert_to_kind(&mut self, kind: SyntaxKind) {
|
||||
debug_assert!(!kind.is_error());
|
||||
@ -195,10 +190,30 @@ impl SyntaxNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the child to an error.
|
||||
/// Convert the child to an error, if it isn't already one.
|
||||
pub(super) fn convert_to_error(&mut self, message: impl Into<EcoString>) {
|
||||
let text = std::mem::take(self).into_text();
|
||||
*self = SyntaxNode::error(message, text);
|
||||
if !self.kind().is_error() {
|
||||
let text = std::mem::take(self).into_text();
|
||||
*self = SyntaxNode::error(message, text);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the child to an error stating that the given thing was
|
||||
/// expected, but the current kind was found.
|
||||
pub(super) fn expected(&mut self, expected: &str) {
|
||||
let kind = self.kind();
|
||||
self.convert_to_error(eco_format!("expected {expected}, found {}", kind.name()));
|
||||
if kind.is_keyword() && matches!(expected, "identifier" | "pattern") {
|
||||
self.hint(eco_format!(
|
||||
"keyword `{text}` is not allowed as an identifier; try `{text}_` instead",
|
||||
text = self.text(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the child to an error stating it was unexpected.
|
||||
pub(super) fn unexpected(&mut self) {
|
||||
self.convert_to_error(eco_format!("unexpected {}", self.kind().name()));
|
||||
}
|
||||
|
||||
/// Assign spans to each node.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,17 +10,16 @@ pub struct SyntaxSet(u128);
|
||||
|
||||
impl SyntaxSet {
|
||||
/// Create a new set from a slice of kinds.
|
||||
pub const fn new(slice: &[SyntaxKind]) -> Self {
|
||||
let mut bits = 0;
|
||||
let mut i = 0;
|
||||
while i < slice.len() {
|
||||
bits |= bit(slice[i]);
|
||||
i += 1;
|
||||
}
|
||||
Self(bits)
|
||||
pub const fn new() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
/// Insert a syntax kind into the set.
|
||||
pub const fn add(self, kind: SyntaxKind) -> Self {
|
||||
Self(self.0 | bit(kind))
|
||||
}
|
||||
|
||||
/// Combine two syntax sets.
|
||||
pub const fn union(self, other: Self) -> Self {
|
||||
Self(self.0 | other.0)
|
||||
}
|
||||
@ -36,56 +35,53 @@ const fn bit(kind: SyntaxKind) -> u128 {
|
||||
}
|
||||
|
||||
/// Syntax kinds that can start a statement.
|
||||
pub const STMT: SyntaxSet = SyntaxSet::new(&[
|
||||
SyntaxKind::Let,
|
||||
SyntaxKind::Set,
|
||||
SyntaxKind::Show,
|
||||
SyntaxKind::Import,
|
||||
SyntaxKind::Include,
|
||||
SyntaxKind::Return,
|
||||
]);
|
||||
pub const STMT: SyntaxSet = SyntaxSet::new()
|
||||
.add(SyntaxKind::Let)
|
||||
.add(SyntaxKind::Set)
|
||||
.add(SyntaxKind::Show)
|
||||
.add(SyntaxKind::Import)
|
||||
.add(SyntaxKind::Include)
|
||||
.add(SyntaxKind::Return);
|
||||
|
||||
/// Syntax kinds that can start a markup expression.
|
||||
pub const MARKUP_EXPR: SyntaxSet = SyntaxSet::new(&[
|
||||
SyntaxKind::Space,
|
||||
SyntaxKind::Parbreak,
|
||||
SyntaxKind::LineComment,
|
||||
SyntaxKind::BlockComment,
|
||||
SyntaxKind::Text,
|
||||
SyntaxKind::Linebreak,
|
||||
SyntaxKind::Escape,
|
||||
SyntaxKind::Shorthand,
|
||||
SyntaxKind::SmartQuote,
|
||||
SyntaxKind::Raw,
|
||||
SyntaxKind::Link,
|
||||
SyntaxKind::Label,
|
||||
SyntaxKind::Hash,
|
||||
SyntaxKind::Star,
|
||||
SyntaxKind::Underscore,
|
||||
SyntaxKind::HeadingMarker,
|
||||
SyntaxKind::ListMarker,
|
||||
SyntaxKind::EnumMarker,
|
||||
SyntaxKind::TermMarker,
|
||||
SyntaxKind::RefMarker,
|
||||
SyntaxKind::Dollar,
|
||||
SyntaxKind::LeftBracket,
|
||||
SyntaxKind::RightBracket,
|
||||
SyntaxKind::Colon,
|
||||
]);
|
||||
pub const MARKUP_EXPR: SyntaxSet = SyntaxSet::new()
|
||||
.add(SyntaxKind::Space)
|
||||
.add(SyntaxKind::Parbreak)
|
||||
.add(SyntaxKind::LineComment)
|
||||
.add(SyntaxKind::BlockComment)
|
||||
.add(SyntaxKind::Text)
|
||||
.add(SyntaxKind::Linebreak)
|
||||
.add(SyntaxKind::Escape)
|
||||
.add(SyntaxKind::Shorthand)
|
||||
.add(SyntaxKind::SmartQuote)
|
||||
.add(SyntaxKind::Raw)
|
||||
.add(SyntaxKind::Link)
|
||||
.add(SyntaxKind::Label)
|
||||
.add(SyntaxKind::Hash)
|
||||
.add(SyntaxKind::Star)
|
||||
.add(SyntaxKind::Underscore)
|
||||
.add(SyntaxKind::HeadingMarker)
|
||||
.add(SyntaxKind::ListMarker)
|
||||
.add(SyntaxKind::EnumMarker)
|
||||
.add(SyntaxKind::TermMarker)
|
||||
.add(SyntaxKind::RefMarker)
|
||||
.add(SyntaxKind::Dollar)
|
||||
.add(SyntaxKind::LeftBracket)
|
||||
.add(SyntaxKind::RightBracket)
|
||||
.add(SyntaxKind::Colon);
|
||||
|
||||
/// Syntax kinds that can start a math expression.
|
||||
pub const MATH_EXPR: SyntaxSet = SyntaxSet::new(&[
|
||||
SyntaxKind::Hash,
|
||||
SyntaxKind::MathIdent,
|
||||
SyntaxKind::Text,
|
||||
SyntaxKind::Shorthand,
|
||||
SyntaxKind::Linebreak,
|
||||
SyntaxKind::MathAlignPoint,
|
||||
SyntaxKind::Escape,
|
||||
SyntaxKind::Str,
|
||||
SyntaxKind::Root,
|
||||
SyntaxKind::Prime,
|
||||
]);
|
||||
pub const MATH_EXPR: SyntaxSet = SyntaxSet::new()
|
||||
.add(SyntaxKind::Hash)
|
||||
.add(SyntaxKind::MathIdent)
|
||||
.add(SyntaxKind::Text)
|
||||
.add(SyntaxKind::Shorthand)
|
||||
.add(SyntaxKind::Linebreak)
|
||||
.add(SyntaxKind::MathAlignPoint)
|
||||
.add(SyntaxKind::Escape)
|
||||
.add(SyntaxKind::Str)
|
||||
.add(SyntaxKind::Root)
|
||||
.add(SyntaxKind::Prime);
|
||||
|
||||
/// Syntax kinds that can start a code expression.
|
||||
pub const CODE_EXPR: SyntaxSet = CODE_PRIMARY.union(UNARY_OP);
|
||||
@ -94,63 +90,81 @@ pub const CODE_EXPR: SyntaxSet = CODE_PRIMARY.union(UNARY_OP);
|
||||
pub const ATOMIC_CODE_EXPR: SyntaxSet = ATOMIC_CODE_PRIMARY;
|
||||
|
||||
/// Syntax kinds that can start a code primary.
|
||||
pub const CODE_PRIMARY: SyntaxSet =
|
||||
ATOMIC_CODE_PRIMARY.union(SyntaxSet::new(&[SyntaxKind::Underscore]));
|
||||
pub const CODE_PRIMARY: SyntaxSet = ATOMIC_CODE_PRIMARY.add(SyntaxKind::Underscore);
|
||||
|
||||
/// Syntax kinds that can start an atomic code primary.
|
||||
pub const ATOMIC_CODE_PRIMARY: SyntaxSet = SyntaxSet::new(&[
|
||||
SyntaxKind::Ident,
|
||||
SyntaxKind::LeftBrace,
|
||||
SyntaxKind::LeftBracket,
|
||||
SyntaxKind::LeftParen,
|
||||
SyntaxKind::Dollar,
|
||||
SyntaxKind::Let,
|
||||
SyntaxKind::Set,
|
||||
SyntaxKind::Show,
|
||||
SyntaxKind::If,
|
||||
SyntaxKind::While,
|
||||
SyntaxKind::For,
|
||||
SyntaxKind::Import,
|
||||
SyntaxKind::Include,
|
||||
SyntaxKind::Break,
|
||||
SyntaxKind::Continue,
|
||||
SyntaxKind::Return,
|
||||
SyntaxKind::None,
|
||||
SyntaxKind::Auto,
|
||||
SyntaxKind::Int,
|
||||
SyntaxKind::Float,
|
||||
SyntaxKind::Bool,
|
||||
SyntaxKind::Numeric,
|
||||
SyntaxKind::Str,
|
||||
SyntaxKind::Label,
|
||||
SyntaxKind::Raw,
|
||||
]);
|
||||
pub const ATOMIC_CODE_PRIMARY: SyntaxSet = SyntaxSet::new()
|
||||
.add(SyntaxKind::Ident)
|
||||
.add(SyntaxKind::LeftBrace)
|
||||
.add(SyntaxKind::LeftBracket)
|
||||
.add(SyntaxKind::LeftParen)
|
||||
.add(SyntaxKind::Dollar)
|
||||
.add(SyntaxKind::Let)
|
||||
.add(SyntaxKind::Set)
|
||||
.add(SyntaxKind::Show)
|
||||
.add(SyntaxKind::If)
|
||||
.add(SyntaxKind::While)
|
||||
.add(SyntaxKind::For)
|
||||
.add(SyntaxKind::Import)
|
||||
.add(SyntaxKind::Include)
|
||||
.add(SyntaxKind::Break)
|
||||
.add(SyntaxKind::Continue)
|
||||
.add(SyntaxKind::Return)
|
||||
.add(SyntaxKind::None)
|
||||
.add(SyntaxKind::Auto)
|
||||
.add(SyntaxKind::Int)
|
||||
.add(SyntaxKind::Float)
|
||||
.add(SyntaxKind::Bool)
|
||||
.add(SyntaxKind::Numeric)
|
||||
.add(SyntaxKind::Str)
|
||||
.add(SyntaxKind::Label)
|
||||
.add(SyntaxKind::Raw);
|
||||
|
||||
/// Syntax kinds that are unary operators.
|
||||
pub const UNARY_OP: SyntaxSet =
|
||||
SyntaxSet::new(&[SyntaxKind::Plus, SyntaxKind::Minus, SyntaxKind::Not]);
|
||||
pub const UNARY_OP: SyntaxSet = SyntaxSet::new()
|
||||
.add(SyntaxKind::Plus)
|
||||
.add(SyntaxKind::Minus)
|
||||
.add(SyntaxKind::Not);
|
||||
|
||||
/// Syntax kinds that are binary operators.
|
||||
pub const BINARY_OP: SyntaxSet = SyntaxSet::new(&[
|
||||
SyntaxKind::Plus,
|
||||
SyntaxKind::Minus,
|
||||
SyntaxKind::Star,
|
||||
SyntaxKind::Slash,
|
||||
SyntaxKind::And,
|
||||
SyntaxKind::Or,
|
||||
SyntaxKind::EqEq,
|
||||
SyntaxKind::ExclEq,
|
||||
SyntaxKind::Lt,
|
||||
SyntaxKind::LtEq,
|
||||
SyntaxKind::Gt,
|
||||
SyntaxKind::GtEq,
|
||||
SyntaxKind::Eq,
|
||||
SyntaxKind::In,
|
||||
SyntaxKind::PlusEq,
|
||||
SyntaxKind::HyphEq,
|
||||
SyntaxKind::StarEq,
|
||||
SyntaxKind::SlashEq,
|
||||
]);
|
||||
pub const BINARY_OP: SyntaxSet = SyntaxSet::new()
|
||||
.add(SyntaxKind::Plus)
|
||||
.add(SyntaxKind::Minus)
|
||||
.add(SyntaxKind::Star)
|
||||
.add(SyntaxKind::Slash)
|
||||
.add(SyntaxKind::And)
|
||||
.add(SyntaxKind::Or)
|
||||
.add(SyntaxKind::EqEq)
|
||||
.add(SyntaxKind::ExclEq)
|
||||
.add(SyntaxKind::Lt)
|
||||
.add(SyntaxKind::LtEq)
|
||||
.add(SyntaxKind::Gt)
|
||||
.add(SyntaxKind::GtEq)
|
||||
.add(SyntaxKind::Eq)
|
||||
.add(SyntaxKind::In)
|
||||
.add(SyntaxKind::PlusEq)
|
||||
.add(SyntaxKind::HyphEq)
|
||||
.add(SyntaxKind::StarEq)
|
||||
.add(SyntaxKind::SlashEq);
|
||||
|
||||
/// Syntax kinds that can start an argument in a function call.
|
||||
pub const ARRAY_OR_DICT_ITEM: SyntaxSet = CODE_EXPR.add(SyntaxKind::Dots);
|
||||
|
||||
/// Syntax kinds that can start an argument in a function call.
|
||||
pub const ARG: SyntaxSet = CODE_EXPR.add(SyntaxKind::Dots);
|
||||
|
||||
/// Syntax kinds that can start a parameter in a parameter list.
|
||||
pub const PARAM: SyntaxSet = PATTERN.add(SyntaxKind::Dots);
|
||||
|
||||
/// Syntax kinds that can start a destructuring item.
|
||||
pub const DESTRUCTURING_ITEM: SyntaxSet = PATTERN.add(SyntaxKind::Dots);
|
||||
|
||||
/// Syntax kinds that can start a pattern.
|
||||
pub const PATTERN: SyntaxSet =
|
||||
PATTERN_LEAF.add(SyntaxKind::LeftParen).add(SyntaxKind::Underscore);
|
||||
|
||||
/// Syntax kinds that can start a pattern leaf.
|
||||
pub const PATTERN_LEAF: SyntaxSet = ATOMIC_CODE_EXPR;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@ -163,7 +177,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_set() {
|
||||
let set = SyntaxSet::new(&[SyntaxKind::And, SyntaxKind::Or]);
|
||||
let set = SyntaxSet::new().add(SyntaxKind::And).add(SyntaxKind::Or);
|
||||
assert!(set.contains(SyntaxKind::And));
|
||||
assert!(set.contains(SyntaxKind::Or));
|
||||
assert!(!set.contains(SyntaxKind::Not));
|
||||
|
@ -31,7 +31,7 @@ impl Eval for ast::DestructAssignment<'_> {
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let value = self.value().eval(vm)?;
|
||||
destructure_impl(vm, self.pattern(), value, |vm, expr, value| {
|
||||
destructure_impl(vm, self.pattern(), value, &mut |vm, expr, value| {
|
||||
let location = expr.access(vm)?;
|
||||
*location = value;
|
||||
Ok(())
|
||||
@ -46,33 +46,34 @@ pub(crate) fn destructure(
|
||||
pattern: ast::Pattern,
|
||||
value: Value,
|
||||
) -> SourceResult<()> {
|
||||
destructure_impl(vm, pattern, value, |vm, expr, value| match expr {
|
||||
destructure_impl(vm, pattern, value, &mut |vm, expr, value| match expr {
|
||||
ast::Expr::Ident(ident) => {
|
||||
vm.define(ident, value);
|
||||
Ok(())
|
||||
}
|
||||
_ => bail!(expr.span(), "nested patterns are currently not supported"),
|
||||
_ => bail!(expr.span(), "cannot assign to this expression"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Destruct the given value into the pattern and apply the function to each binding.
|
||||
fn destructure_impl<T>(
|
||||
fn destructure_impl<F>(
|
||||
vm: &mut Vm,
|
||||
pattern: ast::Pattern,
|
||||
value: Value,
|
||||
f: T,
|
||||
f: &mut F,
|
||||
) -> SourceResult<()>
|
||||
where
|
||||
T: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
||||
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
||||
{
|
||||
match pattern {
|
||||
ast::Pattern::Normal(expr) => {
|
||||
f(vm, expr, value)?;
|
||||
}
|
||||
ast::Pattern::Normal(expr) => f(vm, expr, value)?,
|
||||
ast::Pattern::Placeholder(_) => {}
|
||||
ast::Pattern::Parenthesized(parenthesized) => {
|
||||
destructure_impl(vm, parenthesized.pattern(), value, f)?
|
||||
}
|
||||
ast::Pattern::Destructuring(destruct) => match value {
|
||||
Value::Array(value) => destructure_array(vm, pattern, value, f, destruct)?,
|
||||
Value::Dict(value) => destructure_dict(vm, value, f, destruct)?,
|
||||
Value::Array(value) => destructure_array(vm, destruct, value, f)?,
|
||||
Value::Dict(value) => destructure_dict(vm, destruct, value, f)?,
|
||||
_ => bail!(pattern.span(), "cannot destructure {}", value.ty()),
|
||||
},
|
||||
}
|
||||
@ -81,51 +82,44 @@ where
|
||||
|
||||
fn destructure_array<F>(
|
||||
vm: &mut Vm,
|
||||
pattern: ast::Pattern,
|
||||
value: Array,
|
||||
f: F,
|
||||
destruct: ast::Destructuring,
|
||||
value: Array,
|
||||
f: &mut F,
|
||||
) -> SourceResult<()>
|
||||
where
|
||||
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
||||
{
|
||||
let mut i = 0;
|
||||
let len = value.as_slice().len();
|
||||
for p in destruct.bindings() {
|
||||
let mut i = 0;
|
||||
|
||||
for p in destruct.items() {
|
||||
match p {
|
||||
ast::DestructuringKind::Normal(expr) => {
|
||||
ast::DestructuringItem::Pattern(pattern) => {
|
||||
let Ok(v) = value.at(i as i64, None) else {
|
||||
bail!(expr.span(), "not enough elements to destructure");
|
||||
bail!(pattern.span(), "not enough elements to destructure");
|
||||
};
|
||||
f(vm, expr, v)?;
|
||||
destructure_impl(vm, pattern, v, f)?;
|
||||
i += 1;
|
||||
}
|
||||
ast::DestructuringKind::Sink(spread) => {
|
||||
let sink_size = (1 + len).checked_sub(destruct.bindings().count());
|
||||
ast::DestructuringItem::Spread(spread) => {
|
||||
let sink_size = (1 + len).checked_sub(destruct.items().count());
|
||||
let sink = sink_size.and_then(|s| value.as_slice().get(i..i + s));
|
||||
if let (Some(sink_size), Some(sink)) = (sink_size, sink) {
|
||||
if let Some(expr) = spread.expr() {
|
||||
f(vm, expr, Value::Array(sink.into()))?;
|
||||
}
|
||||
i += sink_size;
|
||||
} else {
|
||||
bail!(pattern.span(), "not enough elements to destructure")
|
||||
let (Some(sink_size), Some(sink)) = (sink_size, sink) else {
|
||||
bail!(spread.span(), "not enough elements to destructure");
|
||||
};
|
||||
if let Some(expr) = spread.sink_expr() {
|
||||
f(vm, expr, Value::Array(sink.into()))?;
|
||||
}
|
||||
i += sink_size;
|
||||
}
|
||||
ast::DestructuringKind::Named(named) => {
|
||||
bail!(named.span(), "cannot destructure named elements from an array")
|
||||
}
|
||||
ast::DestructuringKind::Placeholder(underscore) => {
|
||||
if i < len {
|
||||
i += 1
|
||||
} else {
|
||||
bail!(underscore.span(), "not enough elements to destructure")
|
||||
}
|
||||
ast::DestructuringItem::Named(named) => {
|
||||
bail!(named.span(), "cannot destructure named pattern from an array")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i < len {
|
||||
bail!(pattern.span(), "too many elements to destructure");
|
||||
bail!(destruct.span(), "too many elements to destructure");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -133,32 +127,35 @@ where
|
||||
|
||||
fn destructure_dict<F>(
|
||||
vm: &mut Vm,
|
||||
dict: Dict,
|
||||
f: F,
|
||||
destruct: ast::Destructuring,
|
||||
dict: Dict,
|
||||
f: &mut F,
|
||||
) -> SourceResult<()>
|
||||
where
|
||||
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
||||
{
|
||||
let mut sink = None;
|
||||
let mut used = HashSet::new();
|
||||
for p in destruct.bindings() {
|
||||
|
||||
for p in destruct.items() {
|
||||
match p {
|
||||
ast::DestructuringKind::Normal(ast::Expr::Ident(ident)) => {
|
||||
// Shorthand for a direct identifier.
|
||||
ast::DestructuringItem::Pattern(ast::Pattern::Normal(ast::Expr::Ident(
|
||||
ident,
|
||||
))) => {
|
||||
let v = dict.get(&ident).at(ident.span())?;
|
||||
f(vm, ast::Expr::Ident(ident), v.clone())?;
|
||||
used.insert(ident.as_str());
|
||||
used.insert(ident.get().clone());
|
||||
}
|
||||
ast::DestructuringKind::Sink(spread) => sink = spread.expr(),
|
||||
ast::DestructuringKind::Named(named) => {
|
||||
ast::DestructuringItem::Named(named) => {
|
||||
let name = named.name();
|
||||
let v = dict.get(&name).at(name.span())?;
|
||||
f(vm, named.expr(), v.clone())?;
|
||||
used.insert(name.as_str());
|
||||
destructure_impl(vm, named.pattern(), v.clone(), f)?;
|
||||
used.insert(name.get().clone());
|
||||
}
|
||||
ast::DestructuringKind::Placeholder(_) => {}
|
||||
ast::DestructuringKind::Normal(expr) => {
|
||||
bail!(expr.span(), "expected key, found expression");
|
||||
ast::DestructuringItem::Spread(spread) => sink = spread.sink_expr(),
|
||||
ast::DestructuringItem::Pattern(expr) => {
|
||||
bail!(expr.span(), "cannot destructure unnamed pattern from dictionary");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,13 +192,14 @@ impl Eval for ast::Args<'_> {
|
||||
});
|
||||
}
|
||||
ast::Arg::Named(named) => {
|
||||
let expr = named.expr();
|
||||
items.push(Arg {
|
||||
span,
|
||||
name: Some(named.name().get().clone().into()),
|
||||
value: Spanned::new(named.expr().eval(vm)?, named.expr().span()),
|
||||
value: Spanned::new(expr.eval(vm)?, expr.span()),
|
||||
});
|
||||
}
|
||||
ast::Arg::Spread(expr) => match expr.eval(vm)? {
|
||||
ast::Arg::Spread(spread) => match spread.expr().eval(vm)? {
|
||||
Value::None => {}
|
||||
Value::Array(array) => {
|
||||
items.extend(array.into_iter().map(|value| Arg {
|
||||
@ -215,7 +216,7 @@ impl Eval for ast::Args<'_> {
|
||||
}));
|
||||
}
|
||||
Value::Args(args) => items.extend(args.items),
|
||||
v => bail!(expr.span(), "cannot spread {}", v.ty()),
|
||||
v => bail!(spread.span(), "cannot spread {}", v.ty()),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -311,7 +312,6 @@ pub(crate) fn call_closure(
|
||||
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
|
||||
vm.define(ident, args.expect::<Value>(&ident)?)
|
||||
}
|
||||
ast::Pattern::Normal(_) => unreachable!(),
|
||||
pattern => {
|
||||
crate::eval::destructure(
|
||||
&mut vm,
|
||||
@ -320,8 +320,8 @@ pub(crate) fn call_closure(
|
||||
)?;
|
||||
}
|
||||
},
|
||||
ast::Param::Sink(ident) => {
|
||||
sink = Some(ident.name());
|
||||
ast::Param::Spread(spread) => {
|
||||
sink = Some(spread.sink_ident());
|
||||
if let Some(sink_size) = sink_size {
|
||||
sink_pos_values = Some(args.consume(sink_size)?);
|
||||
}
|
||||
@ -336,10 +336,10 @@ pub(crate) fn call_closure(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(sink_name) = sink {
|
||||
if let Some(sink) = sink {
|
||||
// Remaining args are captured regardless of whether the sink is named.
|
||||
let mut remaining_args = args.take();
|
||||
if let Some(sink_name) = sink_name {
|
||||
if let Some(sink_name) = sink {
|
||||
if let Some(sink_pos_values) = sink_pos_values {
|
||||
remaining_args.items.extend(sink_pos_values);
|
||||
}
|
||||
@ -436,13 +436,15 @@ impl<'a> CapturesVisitor<'a> {
|
||||
for param in expr.params().children() {
|
||||
match param {
|
||||
ast::Param::Pos(pattern) => {
|
||||
for ident in pattern.idents() {
|
||||
for ident in pattern.bindings() {
|
||||
self.bind(ident);
|
||||
}
|
||||
}
|
||||
ast::Param::Named(named) => self.bind(named.name()),
|
||||
ast::Param::Sink(spread) => {
|
||||
self.bind(spread.name().unwrap_or_default())
|
||||
ast::Param::Spread(spread) => {
|
||||
if let Some(ident) = spread.sink_ident() {
|
||||
self.bind(ident);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -458,7 +460,7 @@ impl<'a> CapturesVisitor<'a> {
|
||||
self.visit(init.to_untyped());
|
||||
}
|
||||
|
||||
for ident in expr.kind().idents() {
|
||||
for ident in expr.kind().bindings() {
|
||||
self.bind(ident);
|
||||
}
|
||||
}
|
||||
@ -471,7 +473,7 @@ impl<'a> CapturesVisitor<'a> {
|
||||
self.internal.enter();
|
||||
|
||||
let pattern = expr.pattern();
|
||||
for ident in pattern.idents() {
|
||||
for ident in pattern.bindings() {
|
||||
self.bind(ident);
|
||||
}
|
||||
|
||||
|
@ -210,10 +210,10 @@ impl Eval for ast::Array<'_> {
|
||||
for item in items {
|
||||
match item {
|
||||
ast::ArrayItem::Pos(expr) => vec.push(expr.eval(vm)?),
|
||||
ast::ArrayItem::Spread(expr) => match expr.eval(vm)? {
|
||||
ast::ArrayItem::Spread(spread) => match spread.expr().eval(vm)? {
|
||||
Value::None => {}
|
||||
Value::Array(array) => vec.extend(array.into_iter()),
|
||||
v => bail!(expr.span(), "cannot spread {} into array", v.ty()),
|
||||
v => bail!(spread.span(), "cannot spread {} into array", v.ty()),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -227,7 +227,6 @@ impl Eval for ast::Dict<'_> {
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let mut map = indexmap::IndexMap::new();
|
||||
|
||||
let mut invalid_keys = eco_vec![];
|
||||
|
||||
for item in self.items() {
|
||||
@ -245,10 +244,10 @@ impl Eval for ast::Dict<'_> {
|
||||
});
|
||||
map.insert(key, keyed.expr().eval(vm)?);
|
||||
}
|
||||
ast::DictItem::Spread(expr) => match expr.eval(vm)? {
|
||||
ast::DictItem::Spread(spread) => match spread.expr().eval(vm)? {
|
||||
Value::None => {}
|
||||
Value::Dict(dict) => map.extend(dict.into_iter()),
|
||||
v => bail!(expr.span(), "cannot spread {} into dictionary", v.ty()),
|
||||
v => bail!(spread.span(), "cannot spread {} into dictionary", v.ty()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -884,7 +884,7 @@ fn print_annotation(
|
||||
let start_col = 1 + source.byte_to_column(range.start).unwrap();
|
||||
let end_line = 1 + line + source.byte_to_line(range.end).unwrap();
|
||||
let end_col = 1 + source.byte_to_column(range.end).unwrap();
|
||||
write!(output, "{start_line}:{start_col}-{end_line}:{end_col}: ").unwrap();
|
||||
write!(output, "{start_line}:{start_col}-{end_line}:{end_col} ").unwrap();
|
||||
}
|
||||
writeln!(output, "{text}").unwrap();
|
||||
}
|
||||
|
86
tests/typ/bugs/parenthesized.typ
Normal file
86
tests/typ/bugs/parenthesized.typ
Normal file
@ -0,0 +1,86 @@
|
||||
// Ref: false
|
||||
// Test bugs related to destructuring and parenthesized parsing.
|
||||
|
||||
---
|
||||
// https://github.com/typst/typst/issues/1338
|
||||
#let foo = "foo"
|
||||
#let bar = "bar"
|
||||
// Error: 8-9 expected expression, found underscore
|
||||
// Error: 16-17 expected expression, found underscore
|
||||
#(foo: _, bar: _)
|
||||
|
||||
---
|
||||
// https://github.com/typst/typst/issues/1342
|
||||
// Error: 5-8 expected named or keyed pair, found identifier
|
||||
// Error: 10-13 expected named or keyed pair, found identifier
|
||||
#(: foo, bar)
|
||||
|
||||
---
|
||||
// https://github.com/typst/typst/issues/1351
|
||||
// Error: 17-22 expected pattern, found string
|
||||
#let foo((test: "bar")) = {}
|
||||
|
||||
---
|
||||
// https://github.com/typst/typst/issues/3014
|
||||
// Error: 8-17 expected expression, found named pair
|
||||
#(box, fill: red)
|
||||
|
||||
---
|
||||
// https://github.com/typst/typst/issues/3144
|
||||
#let f(a: 10) = a(1) + 1
|
||||
#test(f(a: _ => 5), 6)
|
||||
|
||||
---
|
||||
// Error: 18-20 missing argument: pattern parameter
|
||||
#let f(a: 10) = a() + 1
|
||||
#f(a: _ => 5)
|
||||
|
||||
---
|
||||
// This wasn't allowed.
|
||||
#let ((x)) = 1
|
||||
#test(x, 1)
|
||||
|
||||
---
|
||||
// This also wasn't allowed.
|
||||
#let ((a, b)) = (1, 2)
|
||||
#test(a, 1)
|
||||
#test(b, 2)
|
||||
|
||||
---
|
||||
// This was unintentionally allowed ...
|
||||
// Error: 9 expected equals sign
|
||||
#let (a)
|
||||
|
||||
---
|
||||
// ... where this wasn't.
|
||||
// Error: 12 expected equals sign
|
||||
#let (a, b)
|
||||
|
||||
---
|
||||
// This wasn't allowed before the bug fix ...
|
||||
#let f(..) = {}
|
||||
#f(arg: 1)
|
||||
|
||||
---
|
||||
// ... but this was.
|
||||
#let f(..x) = {}
|
||||
#f(arg: 1)
|
||||
|
||||
---
|
||||
// Here, `best` was accessed as a variable, where it shouldn't have.
|
||||
#{
|
||||
(best: _) = (best: "brr")
|
||||
}
|
||||
|
||||
---
|
||||
// Same here.
|
||||
#{
|
||||
let array = (1, 2, 3, 4)
|
||||
(test: array.at(1), best: _) = (test: "baz", best: "brr")
|
||||
test(array, (1, "baz", 3, 4))
|
||||
}
|
||||
|
||||
---
|
||||
// Here, `a` is not duplicate, where it was previously identified as one.
|
||||
#let f((a: b), (c,), a) = (a, b, c)
|
||||
#test(f((a: 1), (2,), 3), (3, 1, 2))
|
33
tests/typ/compiler/backtracking.typ
Normal file
33
tests/typ/compiler/backtracking.typ
Normal file
@ -0,0 +1,33 @@
|
||||
// Ensure that parser backtracking doesn't lead to exponential time consumption.
|
||||
// If this regresses, the test suite will not terminate, which is a bit
|
||||
// unfortunate compared to a good error, but at least we know something is up.
|
||||
//
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
#{
|
||||
let s = "(x: 1) => x"
|
||||
let pat = "(x: {}) => 1 + x()"
|
||||
for _ in range(50) {
|
||||
s = pat.replace("{}", s)
|
||||
}
|
||||
test(eval(s)(), 51)
|
||||
}
|
||||
|
||||
---
|
||||
#{
|
||||
let s = "(x) = 1"
|
||||
let pat = "(x: {_}) = 1"
|
||||
for _ in range(100) {
|
||||
s = pat.replace("_", s)
|
||||
}
|
||||
// Error: 8-9 cannot destructure integer
|
||||
eval(s)
|
||||
}
|
||||
|
||||
---
|
||||
// Test whitespace after memoized part.
|
||||
#( (x: () => 1 ) => 1 )
|
||||
// -------
|
||||
// This is memoized and we want to ensure that whitespace after this
|
||||
// is handled correctly.
|
@ -126,8 +126,7 @@
|
||||
|
||||
// Should output `3`.
|
||||
#{
|
||||
// Error: 6 expected identifier
|
||||
// Error: 10 expected block
|
||||
// Error: 7-10 expected pattern, found string
|
||||
for "v"
|
||||
|
||||
// Error: 8 expected keyword `in`
|
||||
|
@ -75,8 +75,7 @@
|
||||
#f[1](2)
|
||||
|
||||
---
|
||||
// Error: 7 expected expression
|
||||
// Error: 8 expected expression
|
||||
// Error: 7-8 unexpected colon
|
||||
#func(:)
|
||||
|
||||
// Error: 10-12 unexpected end of block comment
|
||||
|
@ -171,25 +171,26 @@
|
||||
#let f((k: a, b), c: 3, (d,)) = (a, b, c, d)
|
||||
#test(f((k: 1, b: 2), (4,)), (1, 2, 3, 4))
|
||||
|
||||
// Error: 22-23 duplicate parameter: a
|
||||
#let f((a: b), (c,), a) = none
|
||||
|
||||
// Error: 8-14 expected identifier, found array
|
||||
// Error: 8-14 expected identifier, found destructuring pattern
|
||||
#let f((a, b): 0) = none
|
||||
|
||||
// Error: 10-19 expected identifier, found destructuring pattern
|
||||
// Error: 10-19 expected pattern, found array
|
||||
#let f(..(a, b: c)) = none
|
||||
|
||||
// Error: 10-16 expected identifier, found array
|
||||
// Error: 10-16 expected pattern, found array
|
||||
#let f(..(a, b)) = none
|
||||
|
||||
// Error: 10-19 expected identifier, found destructuring pattern
|
||||
#let f(..(a, b: c)) = none
|
||||
|
||||
---
|
||||
// Error: 11-12 duplicate parameter: x
|
||||
#let f(x, x) = none
|
||||
|
||||
---
|
||||
// Error: 21 expected comma
|
||||
// Error: 22-23 expected pattern, found integer
|
||||
// Error: 24-25 unexpected plus
|
||||
// Error: 26-27 expected pattern, found integer
|
||||
#let f = (x: () => 1 2 + 3) => 4
|
||||
|
||||
---
|
||||
// Error: 14-15 duplicate parameter: a
|
||||
// Error: 23-24 duplicate parameter: b
|
||||
@ -201,17 +202,18 @@
|
||||
#let f(a, ..a) = none
|
||||
|
||||
---
|
||||
// Error: 7-17 expected identifier, named pair or argument sink, found keyed pair
|
||||
// Error: 7-14 expected pattern, found string
|
||||
#((a, "named": b) => none)
|
||||
|
||||
---
|
||||
// Error: 10-15 expected identifier, found string
|
||||
// Error: 10-15 expected pattern, found string
|
||||
#let foo("key": b) = key
|
||||
|
||||
---
|
||||
// Error: 10-14 expected identifier, found `none`
|
||||
// Error: 10-14 expected pattern, found `none`
|
||||
// Hint: 10-14 keyword `none` is not allowed as an identifier; try `none_` instead
|
||||
#let foo(none: b) = key
|
||||
|
||||
---
|
||||
// Error: 11 expected comma
|
||||
// Error: 10-11 expected identifier, found underscore
|
||||
#let foo(_: 3) = none
|
||||
|
@ -110,7 +110,6 @@
|
||||
// Identified as dictionary due to initial colon.
|
||||
// The boolean key is allowed for now since it will only cause an error at the evaluation stage.
|
||||
// Error: 4-5 expected named or keyed pair, found integer
|
||||
// Error: 5 expected comma
|
||||
// Error: 17 expected expression
|
||||
#(:1 b:"", true:)
|
||||
|
||||
@ -152,7 +151,7 @@
|
||||
|
||||
---
|
||||
// Error: 7-10 expected identifier, found group
|
||||
// Error: 12-14 expected identifier, found integer
|
||||
// Error: 12-14 expected pattern, found integer
|
||||
#let ((a): 10) = "world"
|
||||
|
||||
---
|
||||
|
@ -2,13 +2,13 @@
|
||||
// Ref: false
|
||||
|
||||
---
|
||||
// Error: 6-8 expected identifier, found keyword `as`
|
||||
// Error: 6-8 expected pattern, found keyword `as`
|
||||
// Hint: 6-8 keyword `as` is not allowed as an identifier; try `as_` instead
|
||||
#let as = 1 + 2
|
||||
|
||||
---
|
||||
#{
|
||||
// Error: 7-9 expected identifier, found keyword `as`
|
||||
// Error: 7-9 expected pattern, found keyword `as`
|
||||
// Hint: 7-9 keyword `as` is not allowed as an identifier; try `as_` instead
|
||||
let as = 10
|
||||
}
|
||||
|
@ -92,19 +92,24 @@
|
||||
|
||||
---
|
||||
// Destructuring without parentheses.
|
||||
// Error: 7 expected keyword `in`
|
||||
// Hint: 7 did you mean to use a destructuring pattern?
|
||||
// Error: 7-8 unexpected comma
|
||||
// Hint: 7-8 destructuring patterns must be wrapped in parentheses
|
||||
#for k, v in (a: 4, b: 5) {
|
||||
dont-care
|
||||
}
|
||||
|
||||
// Error: 5 expected identifier
|
||||
// Error: 7-8 unexpected comma
|
||||
// Hint: 7-8 destructuring patterns must be wrapped in parentheses
|
||||
#for k, in () {}
|
||||
|
||||
---
|
||||
// Error: 5 expected pattern
|
||||
#for
|
||||
|
||||
// Error: 5 expected identifier
|
||||
// Error: 5 expected pattern
|
||||
#for//
|
||||
|
||||
// Error: 6 expected identifier
|
||||
// Error: 6 expected pattern
|
||||
#{for}
|
||||
|
||||
// Error: 7 expected keyword `in`
|
||||
@ -116,15 +121,15 @@
|
||||
// Error: 15 expected block
|
||||
#for v in iter
|
||||
|
||||
// Error: 5 expected identifier
|
||||
// Error: 5 expected pattern
|
||||
#for
|
||||
v in iter {}
|
||||
|
||||
// Error: 6 expected identifier
|
||||
// Error: 10 expected block
|
||||
// Error: 7-10 expected pattern, found string
|
||||
// Error: 16 expected block
|
||||
A#for "v" thing
|
||||
|
||||
// Error: 5 expected identifier
|
||||
// Error: 6-9 expected pattern, found string
|
||||
#for "v" in iter {}
|
||||
|
||||
// Error: 7 expected keyword `in`
|
||||
|
@ -125,22 +125,22 @@ Three
|
||||
#test(a, 1)
|
||||
#test(b, 4)
|
||||
|
||||
// Error: 10-11 at most one binding per identifier is allowed
|
||||
// Error: 10-11 duplicate binding: a
|
||||
#let (a, a) = (1, 2)
|
||||
|
||||
// Error: 12-15 at most one destructuring sink is allowed
|
||||
// Error: 12-15 only one destructuring sink is allowed
|
||||
#let (..a, ..a) = (1, 2)
|
||||
|
||||
// Error: 12-13 at most one binding per identifier is allowed
|
||||
// Error: 12-13 duplicate binding: a
|
||||
#let (a, ..a) = (1, 2)
|
||||
|
||||
// Error: 13-14 at most one binding per identifier is allowed
|
||||
// Error: 13-14 duplicate binding: a
|
||||
#let (a: a, a) = (a: 1, b: 2)
|
||||
|
||||
// Error: 13-20 expected identifier, found function call
|
||||
// Error: 13-20 expected pattern, found function call
|
||||
#let (a, b: b.at(0)) = (a: 1, b: 2)
|
||||
|
||||
// Error: 7-14 expected identifier or destructuring sink, found function call
|
||||
// Error: 7-14 expected pattern, found function call
|
||||
#let (a.at(0),) = (1,)
|
||||
|
||||
---
|
||||
@ -148,7 +148,7 @@ Three
|
||||
#let (a, b, c) = (1, 2)
|
||||
|
||||
---
|
||||
// Error: 6-20 not enough elements to destructure
|
||||
// Error: 7-10 not enough elements to destructure
|
||||
#let (..a, b, c, d) = (1, 2)
|
||||
|
||||
---
|
||||
@ -193,6 +193,24 @@ Three
|
||||
#let (a, ..) = (a: 1, b: 2)
|
||||
#test(a, 1)
|
||||
|
||||
---
|
||||
// Ref: false
|
||||
// Nested destructuring.
|
||||
#let ((a, b), (key: c)) = ((1, 2), (key: 3))
|
||||
#test((a, b, c), (1, 2, 3))
|
||||
|
||||
---
|
||||
// Keyed destructuring is not currently supported.
|
||||
// Error: 7-18 expected pattern, found string
|
||||
#let ("spacy key": val) = ("spacy key": 123)
|
||||
#val
|
||||
|
||||
---
|
||||
// Keyed destructuring is not currently supported.
|
||||
#let x = "spacy key"
|
||||
// Error: 7-10 expected identifier, found group
|
||||
#let ((x): v) = ("spacy key": 123)
|
||||
|
||||
---
|
||||
// Trailing placeholders.
|
||||
// Error: 10-11 not enough elements to destructure
|
||||
@ -200,8 +218,8 @@ Three
|
||||
#test(a, 1)
|
||||
|
||||
---
|
||||
// Error: 10-13 expected identifier, found string
|
||||
// Error: 18-19 expected identifier, found integer
|
||||
// Error: 10-13 expected pattern, found string
|
||||
// Error: 18-19 expected pattern, found integer
|
||||
#let (a: "a", b: 2) = (a: 1, b: 2)
|
||||
|
||||
---
|
||||
@ -213,18 +231,17 @@ Three
|
||||
#let (a, b: b) = (a: 1)
|
||||
|
||||
---
|
||||
// Error: 7-11 cannot destructure named elements from an array
|
||||
// Error: 7-11 cannot destructure named pattern from an array
|
||||
#let (a: a, b) = (1, 2, 3)
|
||||
|
||||
---
|
||||
// Error: 5 expected identifier
|
||||
// Error: 5 expected pattern
|
||||
#let
|
||||
|
||||
// Error: 6 expected identifier
|
||||
// Error: 6 expected pattern
|
||||
#{let}
|
||||
|
||||
// Error: 5 expected identifier
|
||||
// Error: 5 expected semicolon or line break
|
||||
// Error: 6-9 expected pattern, found string
|
||||
#let "v"
|
||||
|
||||
// Error: 7 expected semicolon or line break
|
||||
@ -233,8 +250,7 @@ Three
|
||||
// Error: 9 expected expression
|
||||
#let v =
|
||||
|
||||
// Error: 5 expected identifier
|
||||
// Error: 5 expected semicolon or line break
|
||||
// Error: 6-9 expected pattern, found string
|
||||
#let "v" = 1
|
||||
|
||||
// Terminated because expression ends.
|
||||
@ -246,7 +262,7 @@ Three
|
||||
// Error: 11-12 unclosed delimiter
|
||||
#let v5 = (1, 2 + ; Five
|
||||
|
||||
// Error: 9-13 expected identifier, found boolean
|
||||
// Error: 9-13 expected pattern, found boolean
|
||||
#let (..true) = false
|
||||
|
||||
---
|
||||
@ -257,7 +273,7 @@ Three
|
||||
// Error: 2-3 unexpected underscore
|
||||
#_
|
||||
|
||||
// Error: 8-9 unexpected underscore
|
||||
// Error: 8-9 expected expression, found underscore
|
||||
#lorem(_)
|
||||
|
||||
// Error: 3-4 expected expression, found underscore
|
||||
@ -275,9 +291,11 @@ Three
|
||||
|
||||
// Error: 15 expected expression
|
||||
#let func(x) =
|
||||
|
||||
---
|
||||
// Error: 12 expected equals sign
|
||||
#let (func)(x)
|
||||
|
||||
---
|
||||
// Error: 12 expected equals sign
|
||||
// Error: 15-15 expected semicolon or line break
|
||||
|
@ -273,6 +273,38 @@
|
||||
#test(a, ((2, 3, 4), 2))
|
||||
#test(b, 1)
|
||||
|
||||
---
|
||||
// Test comma placement in destructuring assignment.
|
||||
#let array = (1, 2, 3)
|
||||
#((key: array.at(1)) = (key: "hi"))
|
||||
#test(array, (1, "hi", 3))
|
||||
|
||||
#let array = (1, 2, 3)
|
||||
#((array.at(1)) = ("hi"))
|
||||
#test(array, (1, "hi", 3))
|
||||
|
||||
#let array = (1, 2, 3)
|
||||
#((array.at(1),) = ("hi",))
|
||||
#test(array, (1, "hi", 3))
|
||||
|
||||
#let array = (1, 2, 3)
|
||||
#((array.at(1)) = ("hi",))
|
||||
#test(array, (1, ("hi",), 3))
|
||||
|
||||
---
|
||||
// Test nested destructuring assignment.
|
||||
#let a
|
||||
#let b
|
||||
#let c
|
||||
#(((a, b), (key: c)) = ((1, 2), (key: 3)))
|
||||
#test((a, b, c), (1, 2, 3))
|
||||
|
||||
---
|
||||
#let array = (1, 2, 3)
|
||||
// Error: 3-17 cannot destructure string
|
||||
#((array.at(1),) = ("hi"))
|
||||
#test(array, (1, ("hi",), 3))
|
||||
|
||||
---
|
||||
// Error: 3-6 cannot mutate a constant: box
|
||||
#(box = 1)
|
||||
|
@ -61,11 +61,11 @@
|
||||
#test(f(1, 2, 3), 3)
|
||||
|
||||
---
|
||||
// Error: 13-19 cannot spread string
|
||||
// Error: 11-19 cannot spread string
|
||||
#calc.min(.."nope")
|
||||
|
||||
---
|
||||
// Error: 10-14 expected identifier, found boolean
|
||||
// Error: 10-14 expected pattern, found boolean
|
||||
#let f(..true) = none
|
||||
|
||||
---
|
||||
@ -90,11 +90,11 @@
|
||||
}
|
||||
|
||||
---
|
||||
// Error: 11-17 cannot spread dictionary into array
|
||||
// Error: 9-17 cannot spread dictionary into array
|
||||
#(1, 2, ..(a: 1))
|
||||
|
||||
---
|
||||
// Error: 5-11 cannot spread array into dictionary
|
||||
// Error: 3-11 cannot spread array into dictionary
|
||||
#(..(1, 2), a: 1)
|
||||
|
||||
---
|
||||
|
@ -86,7 +86,7 @@
|
||||
#eval("RR_1^NN", mode: "math", scope: (RR: math.NN, NN: math.RR))
|
||||
|
||||
---
|
||||
// Error: 7-12 expected identifier
|
||||
// Error: 7-12 expected pattern
|
||||
#eval("let")
|
||||
|
||||
---
|
||||
|
Loading…
Reference in New Issue
Block a user