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/// Expression: literals, feature refs, member access, index, bracket/unit, etc.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum Expression {
88    LiteralInteger(i64),
89    LiteralReal(String),
90    LiteralString(String),
91    LiteralBoolean(bool),
92    /// Single name or qualified name.
93    FeatureRef(String),
94    /// base.member (e.g. engine.fuelCmdPort).
95    MemberAccess(Box<Node<Expression>>, String),
96    /// base#(index) e.g. frontWheel#(1).
97    Index {
98        base: Box<Node<Expression>>,
99        index: Box<Node<Expression>>,
100    },
101    /// [unit] e.g. [kg].
102    Bracket(Box<Node<Expression>>),
103    /// value [unit] e.g. 1750 [kg].
104    LiteralWithUnit {
105        value: Box<Node<Expression>>,
106        unit: Box<Node<Expression>>,
107    },
108    /// Binary infix operation e.g. `a >= b * c`, `x / y`.
109    BinaryOp {
110        op: String,
111        left: Box<Node<Expression>>,
112        right: Box<Node<Expression>>,
113    },
114    /// Unary prefix: + - ~ not
115    UnaryOp {
116        op: String,
117        operand: Box<Node<Expression>>,
118    },
119    /// Function-like invocation, e.g. `ComputeMargin(a, b)`.
120    Invocation {
121        callee: Box<Node<Expression>>,
122        args: Vec<Node<Expression>>,
123    },
124    /// Comma-separated sequence in parentheses, e.g. `(engine1, engine2)` for ordered composition values.
125    Tuple(Vec<Node<Expression>>),
126    /// KerML null or empty sequence ().
127    Null,
128}