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, @spacetime.messages
23    DataSourceReference(String), // DEPRECATED: use Reference instead. Kept for backward compat.
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
153/// Import clause specifying what to import
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
155pub enum ImportClause {
156    /// Named imports: import { Button, Card } from "..."
157    Named(Vec<String>),
158    /// Default import: import HomePage from "..."
159    Default(String),
160}
161
162/// Import source (local path or URL)
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164pub enum ImportSource {
165    /// Local file path: "./components/ui"
166    Local(String),
167    /// Web URL: "https://cdn.example.com/..."
168    Url(String),
169}
170
171/// Import statement
172#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
173pub struct ImportStatement {
174    /// What to import (named or default)
175    pub clause: ImportClause,
176    /// Where to import from (local path or URL)
177    pub source: ImportSource,
178}
179
180impl ImportStatement {
181    pub fn new(clause: ImportClause, source: ImportSource) -> Self {
182        Self { clause, source }
183    }
184
185    /// Get the source path as a string
186    pub fn source_path(&self) -> &str {
187        match &self.source {
188            ImportSource::Local(path) => path,
189            ImportSource::Url(url) => url,
190        }
191    }
192
193    /// Get the list of component names being imported
194    pub fn imported_names(&self) -> Vec<String> {
195        match &self.clause {
196            ImportClause::Named(names) => names.clone(),
197            ImportClause::Default(name) => vec![name.clone()],
198        }
199    }
200}
201
202/// A Hypen document consisting of imports and component definitions
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204pub struct Document {
205    /// Import statements at the top of the document
206    pub imports: Vec<ImportStatement>,
207    /// Component definitions
208    pub components: Vec<ComponentSpecification>,
209}
210
211impl Document {
212    pub fn new(imports: Vec<ImportStatement>, components: Vec<ComponentSpecification>) -> Self {
213        Self {
214            imports,
215            components,
216        }
217    }
218
219    pub fn empty() -> Self {
220        Self {
221            imports: vec![],
222            components: vec![],
223        }
224    }
225}