vize_l2 0.482.0

L2 - the Davinci semantic IR (codename Disegno): the neutral op family every input dialect lowers into
Documentation
//! The indentation frame stack of the folio parser: which containers
//! are open, which grouped position each is in, and how a finished frame
//! closes back into its owner. Split from [`parse`](super) when the slot
//! outlet gained its props surface (a third phased frame) pushed the
//! parser past the source budget.

use vize_l0::cstr;
use vize_l0::dump::Error as DumpError;

use crate::dump::owned::{
    Attribute, Binding, Branch, Component, Element, For, If, Model, Op, OriginalFor, Slot,
};
use crate::dump::parse::Parser;
use crate::dump::parse::line::err;

/// Which grouped position an open element/component frame is in.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Phase {
    Attrs,
    Bindings,
    Children,
}

/// One open container on the indentation stack.
#[derive(Debug)]
pub(super) enum Frame {
    Element(Element, Phase),
    Component(Component, Phase),
    Model(Model),
    If(If),
    Branch(Branch),
    For(For),
    OriginalFor(OriginalFor),
    Slot(Slot, Phase),
}

impl Parser {
    pub(super) fn push_attr(
        &mut self,
        attribute: Attribute,
        line_no: usize,
    ) -> Result<(), DumpError> {
        match self.stack.last_mut() {
            Some(Frame::Element(element, phase)) => match phase {
                Phase::Attrs => {
                    element.attributes.push(attribute);
                    Ok(())
                }
                Phase::Bindings => Err(err(line_no, cstr!("attribute after a binding"))),
                Phase::Children => Err(err(line_no, cstr!("attribute after a child"))),
            },
            Some(Frame::Component(component, phase)) => match phase {
                Phase::Attrs => {
                    component.attributes.push(attribute);
                    Ok(())
                }
                Phase::Bindings => Err(err(line_no, cstr!("attribute after a binding"))),
                Phase::Children => Err(err(line_no, cstr!("attribute after a child"))),
            },
            Some(Frame::Slot(slot, phase)) => match phase {
                Phase::Attrs => {
                    slot.attributes.push(attribute);
                    Ok(())
                }
                Phase::Bindings => Err(err(line_no, cstr!("attribute after a binding"))),
                Phase::Children => Err(err(line_no, cstr!("attribute after a child"))),
            },
            Some(Frame::Model(model)) => {
                model.attributes.push(attribute);
                Ok(())
            }
            Some(Frame::If(_)) => Err(err(line_no, cstr!("expected `branch` under `ui.if`"))),
            None | Some(Frame::Branch(_) | Frame::For(_) | Frame::OriginalFor(_)) => {
                Err(err(line_no, cstr!("`attr` outside an element")))
            }
        }
    }

    /// Attach a completed leaf binding (`ui.slot-content` /
    /// `vue.directive`) to the owner `guard_binding` admitted.
    pub(super) fn push_leaf_binding(
        &mut self,
        binding: Binding,
        line_no: usize,
    ) -> Result<(), DumpError> {
        match self.stack.last_mut() {
            Some(Frame::Element(element, _)) => element.bindings.push(binding),
            Some(Frame::Component(component, _)) => component.bindings.push(binding),
            Some(Frame::Slot(slot, _)) => slot.bindings.push(binding),
            // `guard_binding` admitted the owner before the binding was parsed.
            _ => return Err(err(line_no, cstr!("binding without an element-like owner"))),
        }
        Ok(())
    }

    /// A binding line (`ui.bind` / `ui.on` / `ui.model` /
    /// `ui.slot-content` / `vue.directive`) needs an open element,
    /// component, or slot outlet whose children have not started.
    pub(super) fn guard_binding(&mut self, line_no: usize) -> Result<(), DumpError> {
        match self.stack.last_mut() {
            Some(Frame::Element(_, phase) | Frame::Component(_, phase) | Frame::Slot(_, phase)) => {
                if *phase == Phase::Children {
                    return Err(err(line_no, cstr!("binding after a child")));
                }
                *phase = Phase::Bindings;
                Ok(())
            }
            Some(Frame::Model(_)) => Err(err(line_no, cstr!("expected `attr` under `ui.model`"))),
            Some(Frame::If(_)) => Err(err(line_no, cstr!("expected `branch` under `ui.if`"))),
            None | Some(Frame::Branch(_) | Frame::For(_) | Frame::OriginalFor(_)) => {
                Err(err(line_no, cstr!("binding outside an element")))
            }
        }
    }

    /// A region-op line needs a child position: the root, an element or
    /// component body, a branch, a `ui.for` region, or a slot fallback.
    pub(super) fn guard_child(&self, line_no: usize) -> Result<(), DumpError> {
        match self.stack.last() {
            Some(Frame::If(_)) => Err(err(line_no, cstr!("expected `branch` under `ui.if`"))),
            Some(Frame::Model(_)) => Err(err(line_no, cstr!("expected `attr` under `ui.model`"))),
            None
            | Some(
                Frame::Element(..)
                | Frame::Component(..)
                | Frame::Branch(_)
                | Frame::For(_)
                | Frame::OriginalFor(_)
                | Frame::Slot(..),
            ) => Ok(()),
        }
    }

    /// Attach a finished op to the innermost open child position.
    pub(super) fn attach_op(&mut self, op: Op, line_no: usize) -> Result<(), DumpError> {
        match self.stack.last_mut() {
            None => self.root.push(op),
            Some(Frame::Element(element, phase)) => {
                *phase = Phase::Children;
                element.children.push(op);
            }
            Some(Frame::Component(component, phase)) => {
                *phase = Phase::Children;
                component.children.push(op);
            }
            Some(Frame::Branch(branch)) => branch.ops.push(op),
            Some(Frame::For(for_op)) => for_op.ops.push(op),
            Some(Frame::OriginalFor(for_op)) => for_op.ops.push(op),
            Some(Frame::Slot(slot, phase)) => {
                *phase = Phase::Children;
                slot.fallback.push(op);
            }
            // `guard_child` rejects these parents before the op is parsed.
            Some(Frame::If(_) | Frame::Model(_)) => {
                return Err(err(line_no, cstr!("child under a `ui.if` or model frame")));
            }
        }
        Ok(())
    }

    /// Close the innermost frame back into its owner.
    pub(super) fn close_top(&mut self, line_no: usize) -> Result<(), DumpError> {
        let Some(frame) = self.stack.pop() else {
            return Ok(());
        };
        match frame {
            Frame::Element(element, _) => self.attach_op(Op::Element(element), line_no)?,
            Frame::Component(component, _) => {
                self.attach_op(Op::Component(component), line_no)?;
            }
            Frame::If(if_op) => self.attach_op(Op::If(if_op), line_no)?,
            Frame::For(for_op) => self.attach_op(Op::For(for_op), line_no)?,
            Frame::OriginalFor(for_op) => self.attach_op(Op::OriginalFor(for_op), line_no)?,
            Frame::Slot(slot, _) => self.attach_op(Op::Slot(slot), line_no)?,
            Frame::Branch(branch) => match self.stack.last_mut() {
                Some(Frame::If(if_op)) => if_op.branches.push(branch),
                // Branch frames only open under `ui.if`.
                _ => return Err(err(line_no, cstr!("`branch` outside `ui.if`"))),
            },
            Frame::Model(model) => match self.stack.last_mut() {
                Some(Frame::Element(element, _)) => {
                    element.bindings.push(Binding::Model(model));
                }
                Some(Frame::Component(component, _)) => {
                    component.bindings.push(Binding::Model(model));
                }
                Some(Frame::Slot(slot, _)) => {
                    slot.bindings.push(Binding::Model(model));
                }
                // Model frames only open under an element-like owner.
                _ => return Err(err(line_no, cstr!("model outside an element"))),
            },
        }
        Ok(())
    }
}