1use crate::error::Span;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone)]
8pub struct Program {
9 pub items: Vec<ProgramItem>,
10}
11
12#[derive(Debug, Clone)]
13pub enum ProgramItem {
14 Variable(VariableDecl),
15 Component(ComponentDecl),
16 Screen(ScreenDecl),
17 Theme(ThemeDecl),
18 Import(ImportDecl),
19 UseTheme(String),
20 StateDecl(StateVarDecl),
21}
22
23#[derive(Debug, Clone)]
24pub struct ThemeDecl {
25 pub name: String,
26 pub vars: Vec<VariableDecl>,
27 pub span: Span,
28}
29
30#[derive(Debug, Clone)]
31pub struct ImportDecl {
32 pub path: String,
33 pub span: Span,
34}
35
36#[derive(Debug, Clone)]
37pub struct VariableDecl {
38 pub name: String,
39 pub value: Expr,
40 pub span: Span,
41}
42
43#[derive(Debug, Clone)]
44pub struct StateVarDecl {
45 pub name: String,
46 pub initial_value: Expr,
47 pub span: Span,
48}
49
50#[derive(Debug, Clone)]
51pub struct ComponentDecl {
52 pub name: String,
53 pub params: Vec<String>,
54 pub body: Expr,
55 pub span: Span,
56}
57
58#[derive(Debug, Clone)]
59pub struct ScreenDecl {
60 pub name: String,
61 pub body: Expr,
62 pub span: Span,
63}
64
65#[derive(Debug, Clone)]
66pub enum Expr {
67 Literal(Literal),
68 Ident(String, Span),
69 Binary {
70 left: Box<Expr>,
71 op: BinaryOp,
72 right: Box<Expr>,
73 span: Span,
74 },
75 Unary {
76 op: UnaryOp,
77 inner: Box<Expr>,
78 span: Span,
79 },
80 Call {
81 callee: String,
82 args: Vec<Expr>,
83 slot_args: Option<Vec<(String, Expr)>>,
85 span: Span,
86 },
87 Element {
88 kind: ElementKind,
89 props: Vec<Prop>,
90 children: Vec<Expr>,
91 span: Span,
92 },
93 Block {
94 stmts: Vec<Stmt>,
95 span: Span,
96 },
97 If {
98 cond: Box<Expr>,
99 then_branch: Box<Expr>,
100 else_branch: Option<Box<Expr>>,
101 span: Span,
102 },
103 For {
104 var: String,
105 iter: Box<Expr>,
106 body: Box<Expr>,
107 span: Span,
108 },
109 InterpolatedString {
110 parts: Vec<InterpSegment>,
111 span: Span,
112 },
113 Assignment {
114 name: String,
115 value: Box<Expr>,
116 span: Span,
117 },
118}
119
120#[derive(Debug, Clone)]
122pub enum InterpSegment {
123 Literal(String),
125 Expr(Box<Expr>),
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum BinaryOp {
131 Add,
132 Sub,
133 Mul,
134 Div,
135 Mod,
136 Eq,
137 Ne,
138 Lt,
139 Le,
140 Gt,
141 Ge,
142 And,
143 Or,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum UnaryOp {
148 Not,
149 Neg,
150}
151
152#[derive(Debug, Clone)]
153pub enum Literal {
154 Number(f64),
155 String(String),
156 Bool(bool),
157 Color { r: u8, g: u8, b: u8, a: u8 },
158 Array(Vec<Expr>),
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum ElementKind {
163 Header,
164 Footer,
165 Container,
166 Sidebar,
167 Section,
168 Box,
169 Text,
170 Row,
171 Column,
172 Grid,
173 Stack,
174 Center,
175 Spacer,
176 Image,
177 Button,
178 Input,
179 Card,
180 Widget,
181 Accordion,
182 Bento,
183 Breadcrumb,
184 Hamburger,
185 Kebab,
186 Meatballs,
187 Doner,
188 Tabs,
189 Pagination,
190 LinkList,
191 Nav,
192 Password,
193 Search,
194 Checkbox,
195 Radio,
196 Dropdown,
197 Combobox,
198 Multiselect,
199 DatePicker,
200 Picker,
201 Slider,
202 Stepper,
203 Toggle,
204 Form,
205 Modal,
206 ConfirmDialog,
207 Toast,
208 Notification,
209 Alert,
210 MessageBox,
211 Tooltip,
212 Loader,
213 ProgressBar,
214 Badge,
215 Icon,
216 Tag,
217 Comment,
218 Feed,
219 Carousel,
220 Chart,
221
222 Table,
224 Avatar,
225 Skeleton,
226 Drawer,
227 Select,
228 Textarea,
229 Popover,
230 Separator,
231 Timeline,
232 Rating,
233 FileUpload,
234 ColorPicker,
235 TreeView,
236 CommandPalette,
237 Splitter,
238}
239
240#[derive(Debug, Clone)]
241pub struct Prop {
242 pub name: PropName,
243 pub value: PropValue,
244 pub span: Span,
245}
246
247#[derive(Debug, Clone)]
248pub enum PropName {
249 Ident(String),
250 Width,
251 Height,
252 Fill,
253 Stroke,
254 Radius,
255 Padding,
256 Gap,
257 Grow,
258 Shrink,
259 Align,
260 Justify,
261 Direction,
262 FontSize,
263 FontWeight,
264 Shadow,
265 Src, Content, MinWidth,
268 MaxWidth,
269 MinHeight,
270 MaxHeight,
271 Transition,
272 Role,
273 AriaLabel,
274 FocusOrder,
275}
276
277#[derive(Debug, Clone)]
278pub enum PropValue {
279 Expr(Expr),
280 Number(f64),
281 String(String),
282 Color { r: u8, g: u8, b: u8, a: u8 },
283}
284
285#[derive(Debug, Clone)]
286pub enum Stmt {
287 Expr(Expr),
288 Let { name: String, value: Expr, span: Span },
289 StateDecl(StateVarDecl),
290}
291
292impl ElementKind {
293 pub fn from_token_kind(t: &crate::lexer::TokenKind) -> Option<Self> {
294 use crate::lexer::TokenKind;
295 match t {
296 TokenKind::Header => Some(ElementKind::Header),
297 TokenKind::Footer => Some(ElementKind::Footer),
298 TokenKind::Container => Some(ElementKind::Container),
299 TokenKind::Sidebar => Some(ElementKind::Sidebar),
300 TokenKind::Section => Some(ElementKind::Section),
301 TokenKind::Box => Some(ElementKind::Box),
302 TokenKind::Text => Some(ElementKind::Text),
303 TokenKind::Row => Some(ElementKind::Row),
304 TokenKind::Column => Some(ElementKind::Column),
305 TokenKind::Grid => Some(ElementKind::Grid),
306 TokenKind::Stack => Some(ElementKind::Stack),
307 TokenKind::Center => Some(ElementKind::Center),
308 TokenKind::Spacer => Some(ElementKind::Spacer),
309 TokenKind::Image => Some(ElementKind::Image),
310 TokenKind::Button => Some(ElementKind::Button),
311 TokenKind::Input => Some(ElementKind::Input),
312 TokenKind::Card => Some(ElementKind::Card),
313 TokenKind::Widget => Some(ElementKind::Widget),
314 TokenKind::Accordion => Some(ElementKind::Accordion),
315 TokenKind::Bento => Some(ElementKind::Bento),
316 TokenKind::Breadcrumb => Some(ElementKind::Breadcrumb),
317 TokenKind::Hamburger => Some(ElementKind::Hamburger),
318 TokenKind::Kebab => Some(ElementKind::Kebab),
319 TokenKind::Meatballs => Some(ElementKind::Meatballs),
320 TokenKind::Doner => Some(ElementKind::Doner),
321 TokenKind::Tabs => Some(ElementKind::Tabs),
322 TokenKind::Pagination => Some(ElementKind::Pagination),
323 TokenKind::LinkList => Some(ElementKind::LinkList),
324 TokenKind::Nav => Some(ElementKind::Nav),
325 TokenKind::Password => Some(ElementKind::Password),
326 TokenKind::Search => Some(ElementKind::Search),
327 TokenKind::Checkbox => Some(ElementKind::Checkbox),
328 TokenKind::Radio => Some(ElementKind::Radio),
329 TokenKind::Dropdown => Some(ElementKind::Dropdown),
330 TokenKind::Combobox => Some(ElementKind::Combobox),
331 TokenKind::Multiselect => Some(ElementKind::Multiselect),
332 TokenKind::DatePicker => Some(ElementKind::DatePicker),
333 TokenKind::Picker => Some(ElementKind::Picker),
334 TokenKind::Slider => Some(ElementKind::Slider),
335 TokenKind::Stepper => Some(ElementKind::Stepper),
336 TokenKind::Toggle => Some(ElementKind::Toggle),
337 TokenKind::Form => Some(ElementKind::Form),
338 TokenKind::Modal => Some(ElementKind::Modal),
339 TokenKind::ConfirmDialog => Some(ElementKind::ConfirmDialog),
340 TokenKind::Toast => Some(ElementKind::Toast),
341 TokenKind::Notification => Some(ElementKind::Notification),
342 TokenKind::Alert => Some(ElementKind::Alert),
343 TokenKind::MessageBox => Some(ElementKind::MessageBox),
344 TokenKind::Tooltip => Some(ElementKind::Tooltip),
345 TokenKind::Loader => Some(ElementKind::Loader),
346 TokenKind::ProgressBar => Some(ElementKind::ProgressBar),
347 TokenKind::Badge => Some(ElementKind::Badge),
348 TokenKind::Icon => Some(ElementKind::Icon),
349 TokenKind::Tag => Some(ElementKind::Tag),
350 TokenKind::Comment => Some(ElementKind::Comment),
351 TokenKind::Feed => Some(ElementKind::Feed),
352 TokenKind::Carousel => Some(ElementKind::Carousel),
353 TokenKind::Chart => Some(ElementKind::Chart),
354 TokenKind::Table => Some(ElementKind::Table),
355 TokenKind::Avatar => Some(ElementKind::Avatar),
356 TokenKind::Skeleton => Some(ElementKind::Skeleton),
357 TokenKind::Drawer => Some(ElementKind::Drawer),
358 TokenKind::Select => Some(ElementKind::Select),
359 TokenKind::Textarea => Some(ElementKind::Textarea),
360 TokenKind::Popover => Some(ElementKind::Popover),
361 TokenKind::Separator => Some(ElementKind::Separator),
362 TokenKind::Timeline => Some(ElementKind::Timeline),
363 TokenKind::Rating => Some(ElementKind::Rating),
364 TokenKind::FileUpload => Some(ElementKind::FileUpload),
365 TokenKind::ColorPicker => Some(ElementKind::ColorPicker),
366 TokenKind::TreeView => Some(ElementKind::TreeView),
367 TokenKind::CommandPalette => Some(ElementKind::CommandPalette),
368 TokenKind::Splitter => Some(ElementKind::Splitter),
369 _ => None,
370 }
371 }
372}
373
374pub fn substitute_slots(expr: &Expr, slot_args: &[(String, Expr)]) -> Expr {
376 let map: HashMap<_, _> = slot_args.iter().cloned().collect();
377 substitute_slots_inner(expr, &map)
378}
379
380fn substitute_slots_inner(expr: &Expr, map: &HashMap<String, Expr>) -> Expr {
381 match expr {
382 Expr::Ident(name, span) => {
383 if let Some(slot_expr) = map.get(name) {
384 substitute_slots_inner(slot_expr, map)
385 } else {
386 Expr::Ident(name.clone(), *span)
387 }
388 }
389 Expr::Element {
390 kind,
391 props,
392 children,
393 span,
394 } => Expr::Element {
395 kind: *kind,
396 props: props.clone(),
397 children: children
398 .iter()
399 .map(|e| substitute_slots_inner(e, map))
400 .collect(),
401 span: *span,
402 },
403 Expr::Call {
404 callee,
405 args,
406 slot_args: sa,
407 span,
408 } => Expr::Call {
409 callee: callee.clone(),
410 args: args.iter().map(|e| substitute_slots_inner(e, map)).collect(),
411 slot_args: sa.clone(),
412 span: *span,
413 },
414 Expr::Block { stmts, span } => Expr::Block {
415 stmts: stmts
416 .iter()
417 .map(|s| match s {
418 Stmt::Expr(e) => Stmt::Expr(substitute_slots_inner(e, map)),
419 Stmt::StateDecl(sd) => Stmt::StateDecl(StateVarDecl {
420 name: sd.name.clone(),
421 initial_value: substitute_slots_inner(&sd.initial_value, map),
422 span: sd.span,
423 }),
424 other => other.clone(),
425 })
426 .collect(),
427 span: *span,
428 },
429 Expr::If {
430 cond,
431 then_branch,
432 else_branch,
433 span,
434 } => Expr::If {
435 cond: Box::new(substitute_slots_inner(cond, map)),
436 then_branch: Box::new(substitute_slots_inner(then_branch, map)),
437 else_branch: else_branch
438 .as_ref()
439 .map(|e| Box::new(substitute_slots_inner(e, map))),
440 span: *span,
441 },
442 Expr::For {
443 var,
444 iter,
445 body,
446 span,
447 } => Expr::For {
448 var: var.clone(),
449 iter: Box::new(substitute_slots_inner(iter, map)),
450 body: Box::new(substitute_slots_inner(body, map)),
451 span: *span,
452 },
453 Expr::InterpolatedString { parts, span } => Expr::InterpolatedString {
454 parts: parts
455 .iter()
456 .map(|seg| match seg {
457 InterpSegment::Literal(s) => InterpSegment::Literal(s.clone()),
458 InterpSegment::Expr(e) => {
459 InterpSegment::Expr(Box::new(substitute_slots_inner(e, map)))
460 }
461 })
462 .collect(),
463 span: *span,
464 },
465 Expr::Assignment { name, value, span } => Expr::Assignment {
466 name: name.clone(),
467 value: Box::new(substitute_slots_inner(value, map)),
468 span: *span,
469 },
470 other => other.clone(),
471 }
472}