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/// Classified unary operator.
173#[derive(Debug, Clone, PartialEq, Eq)]
174pub enum UnaryOperator {
175    Plus,
176    Minus,
177    Not,
178    BitNot,
179    Other(String),
180}
181
182impl UnaryOperator {
183    pub fn from_token(token: &str) -> Self {
184        match token {
185            "+" => Self::Plus,
186            "-" => Self::Minus,
187            "not" => Self::Not,
188            "~" => Self::BitNot,
189            other => Self::Other(other.to_string()),
190        }
191    }
192
193    pub fn as_str(&self) -> &str {
194        match self {
195            Self::Plus => "+",
196            Self::Minus => "-",
197            Self::Not => "not",
198            Self::BitNot => "~",
199            Self::Other(s) => s.as_str(),
200        }
201    }
202}
203
204/// Expression: literals, feature refs, member access, index, bracket/unit, etc.
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub enum Expression {
207    LiteralInteger(i64),
208    LiteralReal(String),
209    LiteralString(String),
210    LiteralBoolean(bool),
211    /// Single name or qualified name.
212    FeatureRef(String),
213    /// base.member (e.g. engine.fuelCmdPort).
214    MemberAccess(Box<Node<Expression>>, String),
215    /// base#(index) e.g. frontWheel#(1).
216    Index {
217        base: Box<Node<Expression>>,
218        index: Box<Node<Expression>>,
219    },
220    /// [unit] e.g. [kg].
221    Bracket(Box<Node<Expression>>),
222    /// value [unit] e.g. 1750 [kg].
223    LiteralWithUnit {
224        value: Box<Node<Expression>>,
225        unit: Box<Node<Expression>>,
226    },
227    /// Binary infix operation e.g. `a >= b * c`, `x / y`.
228    BinaryOp {
229        op: BinaryOperator,
230        left: Box<Node<Expression>>,
231        right: Box<Node<Expression>>,
232    },
233    /// Unary prefix: + - ~ not
234    UnaryOp {
235        op: UnaryOperator,
236        operand: Box<Node<Expression>>,
237    },
238    /// Function-like invocation, e.g. `ComputeMargin(a, b)`.
239    Invocation {
240        callee: Box<Node<Expression>>,
241        args: Vec<Node<Expression>>,
242    },
243    /// Comma-separated sequence in parentheses, e.g. `(engine1, engine2)` for ordered composition values.
244    Tuple(Vec<Node<Expression>>),
245    /// KerML null or empty sequence ().
246    Null,
247}