Skip to main content

newter_compiler/
ast.rs

1//! Abstract Syntax Tree for the Newt UI language.
2
3use crate::error::Span;
4use std::collections::HashMap;
5
6/// Root node: list of top-level items (variables, components, screens, themes, imports, use theme).
7#[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        /// When present, args are passed by slot name (e.g. sidebar: expr, main: expr).
84        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/// A segment of an interpolated string in the AST.
121#[derive(Debug, Clone)]
122pub enum InterpSegment {
123    /// Literal text.
124    Literal(String),
125    /// A parsed expression to be evaluated and coerced to string.
126    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    // New elements (v0.2)
223    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, // image src
266    Content, // text content
267    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
374/// Replace Ident(slot_name) in expr with the corresponding slot expression when using named slots.
375pub 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}