Skip to main content

sysml_v2_parser/ast/
core.rs

1//! Span, Node, Expression, and shared AST traits.
2
3/// Source location: byte offset, line, column, and length in the source file.
4/// Line and column are **1-based**. Use [`Span::to_lsp_range`] for 0-based LSP ranges.
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct Span {
7    pub offset: usize,
8    pub line: u32,
9    pub column: usize,
10    pub len: usize,
11}
12
13impl Span {
14    /// Dummy span for tests or synthetic nodes (offset 0, line 1, column 1, len 0).
15    pub fn dummy() -> Self {
16        Self {
17            offset: 0,
18            line: 1,
19            column: 1,
20            len: 0,
21        }
22    }
23
24    /// LSP uses 0-based line and 0-based character. Returns (start_line, start_character, end_line, end_character).
25    pub fn to_lsp_range(&self) -> (u32, u32, u32, u32) {
26        let start_line = self.line.saturating_sub(1);
27        let start_char = self.column.saturating_sub(1);
28        let end_char = start_char.saturating_add(self.len);
29        (start_line, start_char as u32, start_line, end_char as u32)
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::Span;
36
37    #[test]
38    fn span_dummy() {
39        let s = Span::dummy();
40        assert_eq!(s.offset, 0);
41        assert_eq!(s.line, 1);
42        assert_eq!(s.column, 1);
43        assert_eq!(s.len, 0);
44    }
45}
46
47#[derive(Debug, Clone)]
48pub struct Node<T> {
49    pub span: Span,
50    pub value: T,
51}
52
53impl<T: PartialEq> PartialEq for Node<T> {
54    fn eq(&self, other: &Self) -> bool {
55        self.value == other.value
56    }
57}
58
59impl<T: Eq> Eq for Node<T> {}
60
61impl<T> Node<T> {
62    pub fn new(span: Span, value: T) -> Self {
63        Self { span, value }
64    }
65}
66
67impl<T> std::ops::Deref for Node<T> {
68    type Target = T;
69    fn deref(&self) -> &T {
70        &self.value
71    }
72}
73
74/// Trait for generic access to node source span (e.g. visitors).
75pub trait AstNode {
76    fn span(&self) -> Span;
77}
78
79impl<T> AstNode for Node<T> {
80    fn span(&self) -> Span {
81        self.span.clone()
82    }
83}
84
85/// Classified binary operator for semantic diagnostics.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum BinaryOperator {
88    Eq,
89    Ne,
90    StrictEq,
91    StrictNe,
92    Lt,
93    Le,
94    Gt,
95    Ge,
96    Add,
97    Sub,
98    Mul,
99    Div,
100    Mod,
101    Exp,
102    Pow,
103    And,
104    Or,
105    Xor,
106    Implies,
107    Range,
108    BitOr,
109    BitAnd,
110    /// Unclassified or extension operator; retains source token.
111    Other(String),
112}
113
114impl BinaryOperator {
115    pub fn from_token(token: &str) -> Self {
116        match token {
117            "==" => Self::Eq,
118            "!=" => Self::Ne,
119            "===" => Self::StrictEq,
120            "!==" => Self::StrictNe,
121            "<" => Self::Lt,
122            "<=" => Self::Le,
123            ">" => Self::Gt,
124            ">=" => Self::Ge,
125            "+" => Self::Add,
126            "-" => Self::Sub,
127            "*" => Self::Mul,
128            "/" => Self::Div,
129            "%" => Self::Mod,
130            "^" => Self::Pow,
131            "**" => Self::Exp,
132            "&&" | "and" => Self::And,
133            "||" | "or" => Self::Or,
134            "xor" => Self::Xor,
135            "implies" => Self::Implies,
136            ".." => Self::Range,
137            "|" => Self::BitOr,
138            "&" => Self::BitAnd,
139            other => Self::Other(other.to_string()),
140        }
141    }
142
143    pub fn as_str(&self) -> &str {
144        match self {
145            Self::Eq => "==",
146            Self::Ne => "!=",
147            Self::StrictEq => "===",
148            Self::StrictNe => "!==",
149            Self::Lt => "<",
150            Self::Le => "<=",
151            Self::Gt => ">",
152            Self::Ge => ">=",
153            Self::Add => "+",
154            Self::Sub => "-",
155            Self::Mul => "*",
156            Self::Div => "/",
157            Self::Mod => "%",
158            Self::Pow => "^",
159            Self::Exp => "**",
160            Self::And => "&&",
161            Self::Or => "||",
162            Self::Xor => "xor",
163            Self::Implies => "implies",
164            Self::Range => "..",
165            Self::BitOr => "|",
166            Self::BitAnd => "&",
167            Self::Other(s) => s.as_str(),
168        }
169    }
170}
171
172/// KerML type-check operator (`istype`, `hastype`, `as`).
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub enum TypeCheckKind {
175    Istype,
176    Hastype,
177    As,
178}
179
180/// Classified unary operator.
181#[derive(Debug, Clone, PartialEq, Eq)]
182pub enum UnaryOperator {
183    Plus,
184    Minus,
185    Not,
186    BitNot,
187    Other(String),
188}
189
190impl UnaryOperator {
191    pub fn from_token(token: &str) -> Self {
192        match token {
193            "+" => Self::Plus,
194            "-" => Self::Minus,
195            "not" => Self::Not,
196            "~" => Self::BitNot,
197            other => Self::Other(other.to_string()),
198        }
199    }
200
201    pub fn as_str(&self) -> &str {
202        match self {
203            Self::Plus => "+",
204            Self::Minus => "-",
205            Self::Not => "not",
206            Self::BitNot => "~",
207            Self::Other(s) => s.as_str(),
208        }
209    }
210}
211
212/// Expression: literals, feature refs, member access, index, bracket/unit, etc.
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub enum Expression {
215    LiteralInteger(i64),
216    LiteralReal(String),
217    LiteralString(String),
218    LiteralBoolean(bool),
219    /// Single name or qualified name.
220    FeatureRef(String),
221    /// base.member (e.g. engine.fuelCmdPort).
222    MemberAccess(Box<Node<Expression>>, String),
223    /// base#(index) e.g. frontWheel#(1).
224    Index {
225        base: Box<Node<Expression>>,
226        index: Box<Node<Expression>>,
227    },
228    /// [unit] e.g. [kg].
229    Bracket(Box<Node<Expression>>),
230    /// value [unit] e.g. 1750 [kg].
231    LiteralWithUnit {
232        value: Box<Node<Expression>>,
233        unit: Box<Node<Expression>>,
234    },
235    /// Binary infix operation e.g. `a >= b * c`, `x / y`.
236    BinaryOp {
237        op: BinaryOperator,
238        left: Box<Node<Expression>>,
239        right: Box<Node<Expression>>,
240    },
241    /// Unary prefix: + - ~ not
242    UnaryOp {
243        op: UnaryOperator,
244        operand: Box<Node<Expression>>,
245    },
246    /// Function-like invocation, e.g. `ComputeMargin(a, b)`.
247    Invocation {
248        callee: Box<Node<Expression>>,
249        args: Vec<Node<Expression>>,
250    },
251    /// Comma-separated sequence in parentheses, e.g. `(engine1, engine2)` for ordered composition values.
252    Tuple(Vec<Node<Expression>>),
253    /// Metadata classification: `@Metaclass` (e.g. `@SysML::PartUsage`).
254    Classification {
255        metaclass: String,
256    },
257    /// Type test: `expr istype Type`, `expr hastype Type`, or `expr as Type`.
258    TypeCheck {
259        kind: TypeCheckKind,
260        operand: Option<Box<Node<Expression>>>,
261        type_name: String,
262    },
263    /// Select expression: `base.?selector`.
264    Select {
265        base: Box<Node<Expression>>,
266        selector: String,
267    },
268    /// Collect expression: `base.**selector`.
269    Collect {
270        base: Box<Node<Expression>>,
271        selector: String,
272    },
273    /// KerML null or empty sequence ().
274    Null,
275}
276
277impl Expression {
278    /// Whether this expression node is a literal Boolean.
279    pub fn is_boolean_literal(&self) -> bool {
280        matches!(self, Self::LiteralBoolean(_))
281    }
282
283    /// Whether this expression is a metadata `@Metaclass` classification.
284    pub fn is_classification(&self) -> bool {
285        matches!(self, Self::Classification { .. })
286    }
287
288    /// Whether this expression is a KerML type test (`istype` / `hastype` / `as`).
289    pub fn is_type_check(&self) -> bool {
290        matches!(self, Self::TypeCheck { .. })
291    }
292
293    /// Whether a binary operator is a comparison.
294    pub fn binary_op_is_comparison(op: &BinaryOperator) -> bool {
295        matches!(
296            op,
297            BinaryOperator::Eq
298                | BinaryOperator::Ne
299                | BinaryOperator::StrictEq
300                | BinaryOperator::StrictNe
301                | BinaryOperator::Lt
302                | BinaryOperator::Le
303                | BinaryOperator::Gt
304                | BinaryOperator::Ge
305        )
306    }
307
308    /// Whether a binary operator is logical (`and` / `or` / `xor` / `implies`).
309    pub fn binary_op_is_logical(op: &BinaryOperator) -> bool {
310        matches!(
311            op,
312            BinaryOperator::And
313                | BinaryOperator::Or
314                | BinaryOperator::Xor
315                | BinaryOperator::Implies
316        )
317    }
318}