1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Lvalues for arithmetic expressions.

use core::fmt;

use crate::{alloc::Vec, spans::Spanned};

/// Length of an assigned lvalue.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum LvalueLen {
    /// Exact length.
    Exact(usize),
    /// Minimum length.
    AtLeast(usize),
}

impl LvalueLen {
    /// Checks if this length matches the provided length of the rvalue.
    pub fn matches(self, value: usize) -> bool {
        match self {
            Self::Exact(len) => value == len,
            Self::AtLeast(len) => value >= len,
        }
    }
}

impl fmt::Display for LvalueLen {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Exact(len) => write!(formatter, "{len}"),
            Self::AtLeast(len) => write!(formatter, "at least {len}"),
        }
    }
}

impl From<usize> for LvalueLen {
    fn from(value: usize) -> Self {
        Self::Exact(value)
    }
}

/// Tuple destructuring, such as `(a, b, ..., c)`.
#[derive(Debug, Clone, PartialEq)]
pub struct Destructure<'a, T> {
    /// Start part of the destructuring, e.g, `a` and `b` in `(a, b, ..., c)`.
    pub start: Vec<SpannedLvalue<'a, T>>,
    /// Middle part of the destructuring, e.g., `rest` in `(a, b, ...rest, _)`.
    pub middle: Option<Spanned<'a, DestructureRest<'a, T>>>,
    /// End part of the destructuring, e.g., `c` in `(a, b, ..., c)`.
    pub end: Vec<SpannedLvalue<'a, T>>,
}

impl<T> Destructure<'_, T> {
    /// Returns the length of destructured elements.
    pub fn len(&self) -> LvalueLen {
        if self.middle.is_some() {
            LvalueLen::AtLeast(self.start.len() + self.end.len())
        } else {
            LvalueLen::Exact(self.start.len())
        }
    }

    /// Checks if the destructuring is empty.
    pub fn is_empty(&self) -> bool {
        self.start.is_empty()
    }
}

/// Rest syntax, such as `...rest` in `(a, ...rest, b)`.
#[derive(Debug, Clone, PartialEq)]
pub enum DestructureRest<'a, T> {
    /// Unnamed rest syntax, i.e., `...`.
    Unnamed,
    /// Named rest syntax, e.g., `...rest`.
    Named {
        /// Variable span, e.g., `rest`.
        variable: Spanned<'a>,
        /// Type annotation of the value.
        ty: Option<Spanned<'a, T>>,
    },
}

impl<'a, T> DestructureRest<'a, T> {
    /// Tries to convert this rest declaration into an lvalue. Return `None` if the rest declaration
    /// is unnamed.
    pub fn to_lvalue(&self) -> Option<SpannedLvalue<'a, T>> {
        match self {
            Self::Named { variable, .. } => {
                Some(variable.copy_with_extra(Lvalue::Variable { ty: None }))
            }
            Self::Unnamed => None,
        }
    }
}

/// Object destructuring, such as `{ x, y: new_y }`.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct ObjectDestructure<'a, T> {
    /// Fields mentioned in the destructuring.
    pub fields: Vec<ObjectDestructureField<'a, T>>,
}

/// Single field in [`ObjectDestructure`], such as `x` and `y: new_y` in `{ x, y: new_y }`.
///
/// In addition to the "ordinary" `field: lvalue` syntax for a field with binding,
/// an alternative one is supported: `field -> lvalue`. This makes the case
/// of a field with type annotation easier to recognize (for humans); `field -> lvalue: Type` is
/// arguably more readable than `field: lvalue: Type` (although the latter is still valid syntax).
#[derive(Debug, Clone, PartialEq)]
pub struct ObjectDestructureField<'a, T> {
    /// Field name, such as `xs` in `xs: (x, ...tail)`.
    pub field_name: Spanned<'a>,
    /// Binding for the field, such as `(x, ...tail)` in `xs: (x, ...tail)`.
    pub binding: Option<SpannedLvalue<'a, T>>,
}

/// Assignable value.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Lvalue<'a, T> {
    /// Simple variable, e.g., `x`.
    Variable {
        /// Type annotation of the value.
        ty: Option<Spanned<'a, T>>,
    },
    /// Tuple destructuring, e.g., `(x, y)`.
    Tuple(Destructure<'a, T>),
    /// Object destructuring, e.g., `{ x, y }`.
    Object(ObjectDestructure<'a, T>),
}

impl<T> Lvalue<'_, T> {
    /// Returns type of this lvalue.
    pub fn ty(&self) -> LvalueType {
        match self {
            Self::Variable { .. } => LvalueType::Variable,
            Self::Tuple(_) => LvalueType::Tuple,
            Self::Object(_) => LvalueType::Object,
        }
    }
}

/// [`Lvalue`] with the associated code span.
pub type SpannedLvalue<'a, T> = Spanned<'a, Lvalue<'a, T>>;

/// Type of an [`Lvalue`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum LvalueType {
    /// Simple variable, e.g., `x`.
    Variable,
    /// Tuple destructuring, e.g., `(x, y)`.
    Tuple,
    /// Object destructuring, e.g., `{ x, y }`.
    Object,
}

impl fmt::Display for LvalueType {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(match self {
            Self::Variable => "simple variable",
            Self::Tuple => "tuple destructuring",
            Self::Object => "object destructuring",
        })
    }
}