Skip to main content

nifty_check/
hir.rs

1//! The checked program: every name resolved to an id, every expression typed, and every
2//! dynamic part of the UI annotated with the state it reads.
3
4use std::rc::Rc;
5
6use nifty_runtime::{
7    Backdrop, ButtonStyle, Icon, ImageSize, LabelStyle, Size, StackStyle, ToggleStyle,
8};
9pub use nifty_runtime::{
10    DialogKind, DownloadCommand, DownloadField, Menu, MenuEntry, Shortcut, WindowAction,
11};
12pub use nifty_runtime::{StateId, StateSet};
13use nifty_syntax::Span;
14pub use nifty_syntax::ast::BinaryOp;
15
16macro_rules! id {
17    ($name:ident) => {
18        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19        pub struct $name(pub usize);
20    };
21}
22
23id!(StructId);
24id!(FnId);
25id!(ViewId);
26id!(LocalId);
27id!(WindowId);
28
29#[derive(Debug, Default)]
30pub struct Program {
31    pub structs: Vec<StructDef>,
32    pub states: Vec<StateDef>,
33    pub functions: Vec<FnDef>,
34    pub views: Vec<ViewDef>,
35    pub windows: Vec<WindowDef>,
36    pub menu_bar: MenuBar,
37}
38
39/// The app's menus, in order, whose items' actions share one frame of locals.
40#[derive(Debug, Default)]
41pub struct MenuBar {
42    pub menus: Vec<Menu<Block>>,
43    /// The type of every local, by id.
44    pub locals: Vec<Type>,
45}
46
47#[derive(Debug)]
48pub struct StructDef {
49    pub name: String,
50    pub fields: Vec<FieldDef>,
51}
52
53#[derive(Debug)]
54pub struct FieldDef {
55    pub name: String,
56    pub ty: Type,
57}
58
59#[derive(Debug)]
60pub struct StateDef {
61    pub name: String,
62    pub ty: Type,
63    pub init: Expr,
64}
65
66/// Parameters occupy the first locals, in order.
67#[derive(Debug)]
68pub struct FnDef {
69    pub name: String,
70    pub params: usize,
71    pub return_type: Type,
72    pub body: Block,
73    /// The type of every local, by id.
74    pub locals: Vec<Type>,
75}
76
77#[derive(Debug)]
78pub struct ViewDef {
79    pub name: String,
80    pub params: usize,
81    pub body: Nodes,
82    /// The type of every local, by id.
83    pub locals: Vec<Type>,
84}
85
86/// A sidebar row, selected while `selection` holds `value`.
87#[derive(Debug)]
88pub struct NavItem {
89    pub title: Rc<Dynamic>,
90    pub value: Rc<Dynamic>,
91    pub selection: Rc<Binding>,
92    pub icon: Icon,
93    pub badge: Option<Rc<Dynamic>>,
94    /// Everything the row shows depends on: its texts and its selection.
95    pub deps: StateSet,
96}
97
98#[derive(Debug)]
99pub struct WindowDef {
100    pub title: String,
101    /// Whether it opens when the app starts, rather than when code opens it.
102    pub starts_open: bool,
103    pub size: Size,
104    pub backdrop: Backdrop,
105    /// The stack holding its content: its one top-level `column` or `row`, or else an
106    /// implicit column around its top-level elements, such as a `scroll` area.
107    pub style: StackStyle,
108    pub children: Nodes,
109    /// The type of every local, by id.
110    pub locals: Vec<Type>,
111}
112
113#[derive(Clone, Debug, PartialEq, Eq, Hash)]
114pub enum Type {
115    Int,
116    Float,
117    Bool,
118    String,
119    List(Rc<Type>),
120    Struct(StructId),
121    Void,
122    /// The type of an expression that already has an error, so it reports nothing further.
123    Error,
124}
125
126/// The built-in types; [`Type::primitive_name`] spells them.
127pub const PRIMITIVE_TYPES: &[Type] = &[Type::Int, Type::Float, Type::Bool, Type::String];
128
129impl Type {
130    #[must_use]
131    pub fn is_error(&self) -> bool {
132        matches!(self, Self::Error)
133    }
134
135    /// The built-in type written as `name`.
136    #[must_use]
137    pub fn primitive(name: &str) -> Option<Self> {
138        PRIMITIVE_TYPES
139            .iter()
140            .find(|primitive| primitive.primitive_name() == Some(name))
141            .cloned()
142    }
143
144    /// How a built-in type is written, or `None` for any other type.
145    #[must_use]
146    pub const fn primitive_name(&self) -> Option<&'static str> {
147        match self {
148            Self::Int => Some("int"),
149            Self::Float => Some("float"),
150            Self::Bool => Some("bool"),
151            Self::String => Some("string"),
152            Self::List(_) | Self::Struct(_) | Self::Void | Self::Error => None,
153        }
154    }
155}
156
157pub type Block = Rc<[Stmt]>;
158pub type Nodes = Rc<[Node]>;
159
160#[derive(Debug)]
161pub enum Stmt {
162    Let(LocalId, Expr),
163    Assign {
164        place: Place,
165        /// `Some` for compound assignment such as `+=`.
166        op: Option<BinaryOp>,
167        value: Expr,
168    },
169    If {
170        branches: Vec<(Expr, Block)>,
171        otherwise: Option<Block>,
172    },
173    For {
174        head: Loop,
175        body: Block,
176    },
177    Return(Option<Expr>),
178    Expr(Expr),
179}
180
181/// A `for`'s variables and what it loops over.
182#[derive(Debug)]
183pub struct Loop {
184    /// Each element of a list, or each `int` of a range.
185    pub item: LocalId,
186    /// The element's 0-based position, in `for index, item in list`.
187    pub index: Option<LocalId>,
188    pub iterable: Iterable,
189}
190
191/// What a `for` loops over. Looping over a place makes the loop variable an alias of each
192/// element, so assigning through it changes the original list.
193#[derive(Debug)]
194pub enum Iterable {
195    Place(Place),
196    Value(Expr),
197    /// Each `int` from `start` up to, but not including, `end`.
198    Range {
199        start: Expr,
200        end: Expr,
201    },
202}
203
204/// A location that can be assigned or bound: state or a local, then fields and indexes.
205#[derive(Debug)]
206pub struct Place {
207    pub root: PlaceRoot,
208    pub path: Vec<PlaceStep>,
209    pub ty: Type,
210    pub span: Span,
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
214pub enum PlaceRoot {
215    State(StateId),
216    Local(LocalId),
217}
218
219#[derive(Debug)]
220pub enum PlaceStep {
221    Field(usize),
222    Index(Expr),
223}
224
225#[derive(Debug)]
226pub struct Expr {
227    pub kind: ExprKind,
228    pub ty: Type,
229    pub span: Span,
230}
231
232#[derive(Debug)]
233pub enum ExprKind {
234    Int(i64),
235    Float(f64),
236    Bool(bool),
237    String(Rc<str>),
238    Interpolate(Vec<Segment>),
239    State(StateId),
240    Local(LocalId),
241    List(Vec<Expr>),
242    Construct(StructId, Vec<Expr>),
243    Field(Box<Expr>, usize),
244    Index(Box<Expr>, Box<Expr>),
245    Not(Box<Expr>),
246    Negate(Box<Expr>),
247    Binary(BinaryOp, Box<Expr>, Box<Expr>),
248    Call(FnId, Vec<Expr>),
249    Len(Box<Expr>),
250    IsEmpty(Box<Expr>),
251    Push(Place, Box<Expr>),
252    Remove(Place, Box<Expr>),
253    Download(Box<Download>),
254    /// `format_bytes(n)`: the size `n` bytes as text.
255    FormatBytes(Box<Expr>),
256    /// `cancel_download(id)` and the like, done once the event finishes.
257    DownloadCommand(DownloadCommand, Box<Expr>),
258    Dialog(Box<Dialog>),
259    /// `open_window(…)` or `close_window(…)`, which happens once the event finishes.
260    Window(WindowAction, WindowId),
261    /// Stands in for an expression with an error; never evaluated.
262    Error,
263}
264
265/// A `download(…)` call: fetches `url` into the folder `folder` on another thread, as
266/// `name`, or after the address when that is `""`, over up to `connections` connections
267/// at once, sending `headers` and trying failed connections again up to `retries` times,
268/// writing how it is going to each bound place:
269/// always its percentage done and a description of where it stands, and the other fields
270/// the call binds.
271#[derive(Debug)]
272pub struct Download {
273    pub url: Expr,
274    pub folder: Expr,
275    pub name: Expr,
276    pub connections: Expr,
277    /// A `[string]` of headers, each written `Name: value`.
278    pub headers: Expr,
279    pub retries: Expr,
280    /// In the order the function table declares them.
281    pub bindings: Vec<(DownloadField, Place)>,
282}
283
284/// A `pick_…(…)` call: once the event finishes, shows a file dialog, and writes the path
285/// or paths the user picks to `answer`. Cancelling leaves `answer` as it was. Settings the
286/// call leaves out are empty: `""`, or `[]` for `types`.
287#[derive(Debug)]
288pub struct Dialog {
289    pub kind: DialogKind,
290    pub answer: Place,
291    pub title: Expr,
292    pub types: Expr,
293    pub folder: Expr,
294    pub name: Expr,
295}
296
297#[derive(Debug)]
298pub enum Segment {
299    Text(Rc<str>),
300    Value(Expr),
301}
302
303/// An expression the UI shows, with the states whose changes require showing it again.
304#[derive(Debug)]
305pub struct Dynamic {
306    pub expr: Expr,
307    pub deps: StateSet,
308}
309
310/// A two-way binding: the control shows the place's value and writes the user's edits to it.
311#[derive(Debug)]
312pub struct Binding {
313    pub place: Place,
314    pub deps: StateSet,
315}
316
317/// UI parts are shared, so whatever runs the program can keep them for as long as it shows
318/// them.
319#[derive(Debug)]
320pub enum Node {
321    /// A `column` or `row`, or, when it `scrolls`, a `scroll` area.
322    Stack {
323        style: StackStyle,
324        children: Nodes,
325        scrolls: bool,
326    },
327    Label {
328        text: Rc<Dynamic>,
329        style: LabelStyle,
330    },
331    /// Shown as selected while `selected` holds, when it has one.
332    Button {
333        title: Rc<Dynamic>,
334        style: ButtonStyle,
335        icon: Icon,
336        selected: Option<Rc<Dynamic>>,
337        action: Block,
338    },
339    /// Edits text on one line, or wraps it over `lines` lines and scrolls it. A one-line
340    /// field with an `action` runs it on Return.
341    TextField {
342        value: Rc<Binding>,
343        placeholder: String,
344        lines: u32,
345        action: Option<Block>,
346    },
347    /// A `checkbox`, or a `switch`, by its `style`.
348    Checkbox {
349        checked: Rc<Binding>,
350        title: Option<Rc<Dynamic>>,
351        style: ToggleStyle,
352    },
353    /// Picks a whole number from `min` to `max`.
354    Slider {
355        value: Rc<Binding>,
356        min: i64,
357        max: i64,
358    },
359    /// Picks one of `options` into `selection`.
360    Picker {
361        selection: Rc<Binding>,
362        options: Rc<Dynamic>,
363        /// What it shows depends on: its options and its selection.
364        deps: StateSet,
365    },
366    NavItem(NavItem),
367    /// The image in the file at `path`, shown at `size`.
368    Image {
369        path: Rc<Dynamic>,
370        size: ImageSize,
371    },
372    /// A progress bar showing `value` percent done, or that work is under way when it has
373    /// no value.
374    Progress {
375        value: Option<Rc<Dynamic>>,
376    },
377    Spacer,
378    Divider,
379    /// A menu shown when the stack it is in is right-clicked.
380    ContextMenu {
381        entries: Vec<MenuEntry<Block>>,
382    },
383    /// Takes files and links dropped on the stack it is in: writes their paths and
384    /// addresses to `dropped`, then runs `action`, if it has one.
385    DropTarget {
386        dropped: Rc<Binding>,
387        action: Option<Block>,
388    },
389    /// Rebuilt when `deps` change; the first branch whose condition holds is shown.
390    If {
391        branches: Vec<(Rc<Expr>, Nodes)>,
392        otherwise: Nodes,
393        deps: StateSet,
394    },
395    /// Rebuilt when `deps` change; `body` is shown once per element.
396    For {
397        head: Rc<Loop>,
398        body: Nodes,
399        deps: StateSet,
400    },
401    /// Rebuilt when `deps` change; the arguments fill the view's parameters.
402    View {
403        view: ViewId,
404        args: Rc<[Expr]>,
405        deps: StateSet,
406    },
407}