sofie-engine 0.4.0

Screens: the template parser and renderer, and the component catalog.
Documentation
use super::{FORMAT, MAGIC, tag};
use crate::language::{Argument, Call};
use crate::message::Recorded;
use crate::syntax::{Expression, Op, Position, Root, Written};

pub(crate) fn write(ops: &[Op<Written>]) -> Vec<u8> {
    let mut writer = Writer(MAGIC.to_vec());
    writer.0.extend_from_slice(&FORMAT.to_le_bytes());
    writer.ops(ops);
    writer.0
}

struct Writer(Vec<u8>);

impl Writer {
    fn ops(&mut self, ops: &[Op<Written>]) {
        self.number(ops.len());
        for op in ops {
            self.op(op);
        }
    }

    fn op(&mut self, op: &Op<Written>) {
        match op {
            Op::Markup(markup) => {
                self.0.push(tag::MARKUP);
                self.text(markup);
            }
            Op::Text(text) => {
                self.0.push(tag::TEXT);
                self.expression(text);
            }
            Op::Url(url) => {
                self.0.push(tag::URL);
                self.expression(url);
            }
            Op::Identified { id, body } => {
                self.0.push(tag::IDENTIFIED);
                self.expression(id);
                self.ops(body);
            }
            Op::Component(written) => self.component(written),
            Op::If(conditional) => {
                self.0.push(tag::IF);
                self.expression(&conditional.condition);
                self.ops(&conditional.then_branch);
                self.ops(&conditional.else_branch);
            }
            Op::Each(each) => {
                self.0.push(tag::EACH);
                self.expression(&each.list);
                self.0.push(u8::from(each.has_index));
                self.ops(&each.body);
            }
        }
    }

    fn component(&mut self, written: &Written) {
        self.0.push(tag::COMPONENT);
        self.text(written.kind.name());
        self.position(written.position);
        self.number(written.attributes.len());
        for attribute in &written.attributes {
            self.text(&attribute.name);
            self.expression(&attribute.value);
            self.position(attribute.position);
        }
        self.ops(&written.children);
    }

    fn expression(&mut self, expression: &Expression) {
        match expression {
            Expression::Path(path) => {
                self.0.push(tag::PATH);
                self.number(match path.root {
                    Root::Data => 0,
                    Root::Slot(slot) => slot + 1,
                });
                self.number(path.fields.len());
                for field in &path.fields {
                    self.text(field);
                }
            }
            Expression::Text(text) => {
                self.0.push(tag::QUOTED);
                self.text(text);
            }
            Expression::Integer(number) => {
                self.0.push(tag::INTEGER);
                self.0.extend_from_slice(&number.to_le_bytes());
            }
            Expression::Authored(text) => {
                self.0.push(tag::AUTHORED);
                self.text(text);
            }
            Expression::Interpolated(parts) => {
                self.0.push(tag::INTERPOLATED);
                self.number(parts.len());
                for part in parts {
                    self.expression(part);
                }
            }
            Expression::Call(call) => self.call(call),
            #[cfg(feature = "gates")]
            Expression::Gate(gate) => {
                self.0.push(tag::GATE);
                self.0.push(match gate.asks {
                    crate::rendering::Asks::Permission => 0,
                    crate::rendering::Asks::Flag => 1,
                    crate::rendering::Asks::Entitlement => 2,
                });
                self.text(&gate.name);
            }
        }
    }

    #[cfg(feature = "i18n")]
    fn call(&mut self, call: &Call) {
        use crate::translations::Format;
        match call {
            Call::Translate {
                key,
                arguments,
                recorded,
                ..
            } => self.translation(key, arguments, recorded.as_ref()),
            Call::Format { format, value, .. } => {
                self.0.push(tag::FORMAT_CALL);
                self.0.push(match format {
                    Format::Number => 0,
                    Format::Money => 1,
                    Format::Date => tag::DATE_FORMAT,
                });
                self.expression(value);
            }
            Call::Label { key, humanized, .. } => {
                self.0.push(tag::LABEL);
                self.text(key);
                self.text(humanized);
            }
        }
        self.position(call.position());
    }

    #[cfg(not(feature = "i18n"))]
    fn call(&mut self, call: &Call) {
        match call {
            Call::Translate {
                key,
                arguments,
                recorded,
                ..
            } => self.translation(key, arguments, recorded.as_ref()),
            Call::Date { value, .. } => {
                self.0.push(tag::FORMAT_CALL);
                self.0.push(tag::DATE_FORMAT);
                self.expression(value);
            }
        }
        self.position(call.position());
    }

    fn translation(&mut self, key: &str, arguments: &[Argument], recorded: Option<&Recorded>) {
        self.0.push(tag::TRANSLATE);
        self.text(key);
        self.number(arguments.len());
        for argument in arguments {
            self.text(&argument.name);
            self.expression(&argument.value);
        }
        let Some(recorded) = recorded else {
            self.0.push(0);
            return;
        };
        self.0.push(if recorded.one.is_some() { 2 } else { 1 });
        self.text(&recorded.other.written());
        if let Some(one) = &recorded.one {
            self.text(&one.written());
        }
    }

    fn position(&mut self, position: Position) {
        self.number(position.line);
        self.number(position.column);
    }

    fn text(&mut self, text: &str) {
        self.number(text.len());
        self.0.extend_from_slice(text.as_bytes());
    }

    fn number(&mut self, number: usize) {
        let mut rest = number;
        while rest >= 0x80 {
            self.0
                .push(u8::try_from(rest & 0x7f).expect("seven bits fit a byte") | 0x80);
            rest >>= 7;
        }
        self.0.push(u8::try_from(rest).expect("below 0x80"));
    }
}