seqc/ast/types.rs
1//! Abstract Syntax Tree for Seq
2//!
3//! Minimal AST sufficient for hello-world and basic programs.
4//! Will be extended as we add more language features.
5
6use crate::types::Effect;
7use std::path::PathBuf;
8
9/// Source location for error reporting and tooling
10#[derive(Debug, Clone, PartialEq)]
11pub struct SourceLocation {
12 pub file: PathBuf,
13 /// Start line (0-indexed for LSP compatibility)
14 pub start_line: usize,
15 /// End line (0-indexed, inclusive)
16 pub end_line: usize,
17}
18
19impl SourceLocation {
20 /// Create a new source location with just a single line (for backward compatibility)
21 pub fn new(file: PathBuf, line: usize) -> Self {
22 SourceLocation {
23 file,
24 start_line: line,
25 end_line: line,
26 }
27 }
28
29 /// Create a source location spanning multiple lines
30 pub fn span(file: PathBuf, start_line: usize, end_line: usize) -> Self {
31 debug_assert!(
32 start_line <= end_line,
33 "SourceLocation: start_line ({}) must be <= end_line ({})",
34 start_line,
35 end_line
36 );
37 SourceLocation {
38 file,
39 start_line,
40 end_line,
41 }
42 }
43}
44
45impl std::fmt::Display for SourceLocation {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 if self.start_line == self.end_line {
48 write!(f, "{}:{}", self.file.display(), self.start_line + 1)
49 } else {
50 write!(
51 f,
52 "{}:{}-{}",
53 self.file.display(),
54 self.start_line + 1,
55 self.end_line + 1
56 )
57 }
58 }
59}
60
61/// Include statement
62#[derive(Debug, Clone, PartialEq)]
63pub enum Include {
64 /// Standard library include: `include std:http`
65 Std(String),
66 /// Relative path include: `include "my-utils"`
67 Relative(String),
68 /// FFI library include: `include ffi:readline`
69 Ffi(String),
70}
71
72// ============================================================================
73// ALGEBRAIC DATA TYPES (ADTs)
74// ============================================================================
75
76/// A field in a union variant
77/// Example: `response-chan: Int`
78#[derive(Debug, Clone, PartialEq)]
79pub struct UnionField {
80 pub name: String,
81 pub type_name: String, // For now, just store the type name as string
82}
83
84/// A variant in a union type
85/// Example: `Get { response-chan: Int }`
86#[derive(Debug, Clone, PartialEq)]
87pub struct UnionVariant {
88 pub name: String,
89 pub fields: Vec<UnionField>,
90 pub source: Option<SourceLocation>,
91}
92
93/// A union type definition
94/// Example:
95/// ```seq
96/// union Message {
97/// Get { response-chan: Int }
98/// Increment { response-chan: Int }
99/// Report { op: Int, delta: Int, total: Int }
100/// }
101/// ```
102#[derive(Debug, Clone, PartialEq)]
103pub struct UnionDef {
104 pub name: String,
105 pub variants: Vec<UnionVariant>,
106 pub source: Option<SourceLocation>,
107}
108
109/// A pattern in a match expression
110/// For Phase 1: just the variant name (stack-based matching)
111/// Later phases will add field bindings: `Get { chan }`
112#[derive(Debug, Clone, PartialEq)]
113pub enum Pattern {
114 /// Match a variant by name, pushing all fields to stack
115 /// Example: `Get ->` pushes response-chan to stack
116 Variant(String),
117
118 /// Match a variant with named field bindings (Phase 5)
119 /// Example: `Get { chan } ->` binds chan to the response-chan field
120 VariantWithBindings { name: String, bindings: Vec<String> },
121}
122
123/// A single arm in a match expression
124#[derive(Debug, Clone, PartialEq)]
125pub struct MatchArm {
126 pub pattern: Pattern,
127 pub body: Vec<Statement>,
128 /// Source span for error reporting (points to variant name)
129 pub span: Option<Span>,
130}
131
132#[derive(Debug, Clone, PartialEq)]
133pub struct Program {
134 pub includes: Vec<Include>,
135 pub unions: Vec<UnionDef>,
136 pub words: Vec<WordDef>,
137}
138
139#[derive(Debug, Clone, PartialEq)]
140pub struct WordDef {
141 pub name: String,
142 /// Optional stack effect declaration
143 /// Example: ( ..a Int -- ..a Bool )
144 pub effect: Option<Effect>,
145 pub body: Vec<Statement>,
146 /// Source location for error reporting (collision detection)
147 pub source: Option<SourceLocation>,
148 /// Lint IDs that are allowed (suppressed) for this word
149 /// Set via `# seq:allow(lint-id)` annotation before the word definition
150 pub allowed_lints: Vec<String>,
151}
152
153/// Source span for a single token or expression
154#[derive(Debug, Clone, PartialEq, Default)]
155pub struct Span {
156 /// Line number (0-indexed)
157 pub line: usize,
158 /// Start column (0-indexed)
159 pub column: usize,
160 /// Length of the span in characters
161 pub length: usize,
162}
163
164impl Span {
165 pub fn new(line: usize, column: usize, length: usize) -> Self {
166 Span {
167 line,
168 column,
169 length,
170 }
171 }
172}
173
174/// Source span for a quotation, supporting multi-line ranges
175#[derive(Debug, Clone, PartialEq, Default)]
176pub struct QuotationSpan {
177 /// Start line (0-indexed)
178 pub start_line: usize,
179 /// Start column (0-indexed)
180 pub start_column: usize,
181 /// End line (0-indexed)
182 pub end_line: usize,
183 /// End column (0-indexed, exclusive)
184 pub end_column: usize,
185}
186
187impl QuotationSpan {
188 pub fn new(start_line: usize, start_column: usize, end_line: usize, end_column: usize) -> Self {
189 QuotationSpan {
190 start_line,
191 start_column,
192 end_line,
193 end_column,
194 }
195 }
196
197 /// Check if a position (line, column) falls within this span
198 pub fn contains(&self, line: usize, column: usize) -> bool {
199 if line < self.start_line || line > self.end_line {
200 return false;
201 }
202 if line == self.start_line && column < self.start_column {
203 return false;
204 }
205 if line == self.end_line && column >= self.end_column {
206 return false;
207 }
208 true
209 }
210}
211
212#[derive(Debug, Clone, PartialEq)]
213pub enum Statement {
214 /// Integer literal: pushes value onto stack
215 IntLiteral(i64),
216
217 /// Floating-point literal: pushes IEEE 754 double onto stack
218 FloatLiteral(f64),
219
220 /// Boolean literal: pushes true/false onto stack
221 BoolLiteral(bool),
222
223 /// String literal: pushes string onto stack
224 StringLiteral(String),
225
226 /// Symbol literal: pushes symbol onto stack
227 /// Syntax: :foo, :some-name, :ok
228 /// Used for dynamic variant construction and SON.
229 /// Note: Symbols are not currently interned (future optimization).
230 Symbol(String),
231
232 /// Word call: calls another word or built-in
233 /// Contains the word name and optional source span for precise diagnostics
234 WordCall { name: String, span: Option<Span> },
235
236 /// Conditional: if/else/then
237 ///
238 /// Pops an integer from the stack (0 = zero, non-zero = non-zero)
239 /// and executes the appropriate branch
240 If {
241 /// Statements to execute when condition is non-zero (the 'then' clause)
242 then_branch: Vec<Statement>,
243 /// Optional statements to execute when condition is zero (the 'else' clause)
244 else_branch: Option<Vec<Statement>>,
245 /// Source span for error reporting (points to 'if' keyword)
246 span: Option<Span>,
247 },
248
249 /// Quotation: [ ... ]
250 ///
251 /// A block of deferred code (quotation/lambda)
252 /// Quotations are first-class values that can be pushed onto the stack
253 /// and executed later with combinators like `call`, `times`, or `while`
254 ///
255 /// The id field is used by the typechecker to track the inferred type
256 /// (Quotation vs Closure) for this quotation. The id is assigned during parsing.
257 /// The span field records the source location for LSP hover support.
258 Quotation {
259 id: usize,
260 body: Vec<Statement>,
261 span: Option<QuotationSpan>,
262 },
263
264 /// Match expression: pattern matching on union types
265 ///
266 /// Pops a union value from the stack and dispatches to the
267 /// appropriate arm based on the variant tag.
268 ///
269 /// Example:
270 /// ```seq
271 /// match
272 /// Get -> send-response
273 /// Increment -> do-increment send-response
274 /// Report -> aggregate-add
275 /// end
276 /// ```
277 Match {
278 /// The match arms in order
279 arms: Vec<MatchArm>,
280 /// Source span for error reporting (points to 'match' keyword)
281 span: Option<Span>,
282 },
283}