Skip to main content

hypen_parser/
ast.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::ops::Range;
4
5/// Represents an argument passed to a component
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub enum Argument {
8    /// Positional argument with index and value
9    Positioned { position: usize, value: Value },
10    /// Named argument with key and value
11    Named { key: String, value: Value },
12}
13
14/// Represents different value types in Hypen
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub enum Value {
17    String(String),
18    Number(f64),
19    Boolean(bool),
20    List(Vec<Value>),
21    Map(HashMap<String, Value>),
22    Reference(String),          // e.g., @state.user, @actions.login
23    DataSourceReference(String), // e.g., $spacetime.messages, $firebase.user.name
24}
25
26/// List of arguments for a component
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct ArgumentList {
29    pub arguments: Vec<Argument>,
30}
31
32impl ArgumentList {
33    pub fn new(arguments: Vec<Argument>) -> Self {
34        Self { arguments }
35    }
36
37    pub fn empty() -> Self {
38        Self { arguments: vec![] }
39    }
40
41    pub fn as_map(&self) -> HashMap<String, Value> {
42        self.arguments
43            .iter()
44            .enumerate()
45            .map(|(i, arg)| match arg {
46                Argument::Positioned { value, .. } => (i.to_string(), value.clone()),
47                Argument::Named { key, value } => (key.clone(), value.clone()),
48            })
49            .collect()
50    }
51
52    pub fn get_named(&self, key: &str) -> Option<&Value> {
53        self.arguments.iter().find_map(|arg| match arg {
54            Argument::Named { key: k, value } if k == key => Some(value),
55            _ => None,
56        })
57    }
58
59    pub fn get_positioned(&self, position: usize) -> Option<&Value> {
60        self.arguments.iter().find_map(|arg| match arg {
61            Argument::Positioned { position: p, value } if *p == position => Some(value),
62            _ => None,
63        })
64    }
65}
66
67/// Metadata about a component's position in source text
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct MetaData {
70    pub internal_id: String,
71    pub name_range: Range<usize>,
72    pub block_range: Option<Range<usize>>,
73}
74
75/// An applicator specification (style/modifier applied to a component)
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct ApplicatorSpecification {
78    pub name: String,
79    pub arguments: ArgumentList,
80    pub children: Vec<ComponentSpecification>,
81    pub internal_id: String,
82}
83
84/// Declaration type for components
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub enum DeclarationType {
87    /// Regular component: `ComponentName(...)`
88    Component,
89    /// Module declaration: `module ComponentName(...)`
90    Module,
91    /// Component declaration: `component ComponentName(...)`
92    ComponentKeyword,
93}
94
95/// The main component specification representing a parsed component
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct ComponentSpecification {
98    pub id: String,
99    pub name: String,
100    pub declaration_type: DeclarationType,
101    pub arguments: ArgumentList,
102    pub applicators: Vec<ApplicatorSpecification>,
103    pub children: Vec<ComponentSpecification>,
104    pub metadata: MetaData,
105}
106
107impl ComponentSpecification {
108    pub fn new(
109        id: String,
110        name: String,
111        arguments: ArgumentList,
112        applicators: Vec<ApplicatorSpecification>,
113        children: Vec<ComponentSpecification>,
114        metadata: MetaData,
115    ) -> Self {
116        Self {
117            id,
118            name,
119            declaration_type: DeclarationType::Component,
120            arguments,
121            applicators,
122            children,
123            metadata,
124        }
125    }
126
127    pub fn with_declaration_type(mut self, declaration_type: DeclarationType) -> Self {
128        self.declaration_type = declaration_type;
129        self
130    }
131
132    /// Flatten the component tree into a list
133    pub fn flatten(&self) -> Vec<ComponentSpecification> {
134        let mut result = vec![self.clone()];
135        for child in &self.children {
136            result.extend(child.flatten());
137        }
138        result
139    }
140
141    /// Convert a component to an applicator (when it starts with '.')
142    pub fn to_applicator(&self) -> ApplicatorSpecification {
143        ApplicatorSpecification {
144            name: self.name.trim_start_matches('.').to_string(),
145            arguments: self.arguments.clone(),
146            children: self.children.clone(),
147            internal_id: self.metadata.internal_id.clone(),
148        }
149    }
150}
151
152/// Intermediate representation during parsing
153#[derive(Debug, Clone)]
154pub struct ComponentOutline {
155    pub constructor_start: usize,
156    pub constructor_end: usize,
157    pub block_range: Option<Range<usize>>,
158    pub children: Vec<ComponentOutline>,
159    pub name_range: Range<usize>,
160}
161
162/// Import clause specifying what to import
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164pub enum ImportClause {
165    /// Named imports: import { Button, Card } from "..."
166    Named(Vec<String>),
167    /// Default import: import HomePage from "..."
168    Default(String),
169}
170
171/// Import source (local path or URL)
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub enum ImportSource {
174    /// Local file path: "./components/ui"
175    Local(String),
176    /// Web URL: "https://cdn.example.com/..."
177    Url(String),
178}
179
180/// Import statement
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct ImportStatement {
183    /// What to import (named or default)
184    pub clause: ImportClause,
185    /// Where to import from (local path or URL)
186    pub source: ImportSource,
187}
188
189impl ImportStatement {
190    pub fn new(clause: ImportClause, source: ImportSource) -> Self {
191        Self { clause, source }
192    }
193
194    /// Get the source path as a string
195    pub fn source_path(&self) -> &str {
196        match &self.source {
197            ImportSource::Local(path) => path,
198            ImportSource::Url(url) => url,
199        }
200    }
201
202    /// Get the list of component names being imported
203    pub fn imported_names(&self) -> Vec<String> {
204        match &self.clause {
205            ImportClause::Named(names) => names.clone(),
206            ImportClause::Default(name) => vec![name.clone()],
207        }
208    }
209}
210
211/// A Hypen document consisting of imports and component definitions
212#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
213pub struct Document {
214    /// Import statements at the top of the document
215    pub imports: Vec<ImportStatement>,
216    /// Component definitions
217    pub components: Vec<ComponentSpecification>,
218}
219
220impl Document {
221    pub fn new(imports: Vec<ImportStatement>, components: Vec<ComponentSpecification>) -> Self {
222        Self {
223            imports,
224            components,
225        }
226    }
227
228    pub fn empty() -> Self {
229        Self {
230            imports: vec![],
231            components: vec![],
232        }
233    }
234}