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