geam 0.1.1

Experimental Rust-embedded execution runtime for typed Gleam programs
use super::super::super::{Endianness, FloatBitSize, StringEncoding};
use super::{write_call, write_constant, write_function_call, write_projection};
use crate::plan::PanicSite;
use crate::plan::execution::constant::ConstantId;
use crate::plan::execution::explain::{Explain, ExplainContext};
use crate::plan::execution::function::BitArrayFunctionId;
use crate::plan::execution::graph::{
    BitArrayListLocalId, CustomLocal, FloatLocalId, IntLocalId, ParamLocal, StringLocalId,
    TupleLocalId, UtfCodepointLocalId,
};
use crate::plan::execution::graph::{LocalLabel, endianness, float_size, string_encoding};

pub(crate) struct BitArrayEvaluatedSize {
    value: IntLocalId,
    unit: u8,
}

pub(crate) enum BitArrayBitsSize {
    Fixed(usize),
    Evaluated(BitArrayEvaluatedSize),
}

pub(crate) enum BitArraySegment {
    Int {
        value: IntLocalId,
        bit_size: usize,
        endianness: Endianness,
    },
    EvaluatedInt {
        value: IntLocalId,
        size: BitArrayEvaluatedSize,
        endianness: Endianness,
        site: PanicSite,
    },
    Float {
        value: FloatLocalId,
        bit_size: FloatBitSize,
        endianness: Endianness,
    },
    EvaluatedFloat {
        value: FloatLocalId,
        size: BitArrayEvaluatedSize,
        endianness: Endianness,
        site: PanicSite,
    },
    String {
        value: StringLocalId,
        encoding: StringEncoding,
    },
    UtfCodepoint {
        value: UtfCodepointLocalId,
        encoding: StringEncoding,
    },
    Bits(crate::plan::execution::graph::BitArrayLocalId),
    SizedBits {
        value: crate::plan::execution::graph::BitArrayLocalId,
        size: BitArrayBitsSize,
        site: PanicSite,
    },
}

pub(crate) enum BitArrayInstruction {
    Value(Box<[BitArraySegment]>),
    Constant(ConstantId<crate::plan::execution::graph::BitArrayLocalId>),
    Call {
        function: BitArrayFunctionId,
        args: Box<[ParamLocal]>,
        site: crate::plan::HostCallSite,
    },
    FunctionCall {
        function: crate::plan::execution::graph::BitArrayFunctionLocalId,
        args: Box<[ParamLocal]>,
        site: crate::plan::HostCallSite,
    },
    TupleIndex {
        tuple: TupleLocalId,
        index: usize,
    },
    CustomField {
        source: CustomLocal,
        index: usize,
    },
    ListIndex {
        list: BitArrayListLocalId,
        index: usize,
    },
}

impl BitArrayEvaluatedSize {
    pub(in crate::plan::execution) fn new(value: IntLocalId, unit: u8) -> Self {
        Self { value, unit }
    }

    pub(crate) fn value(&self) -> IntLocalId {
        self.value
    }

    pub(crate) fn unit(&self) -> u8 {
        self.unit
    }
}

impl Explain for BitArrayInstruction {
    fn write_explanation(&self, context: &mut ExplainContext<'_, '_>) {
        match self {
            Self::Value(segments) => {
                context.push_str("bit_array.value ");
                context.write_list(segments, |context, segment| context.write(segment));
            }
            Self::Constant(id) => write_constant(context.output(), "bit_array", *id),
            Self::Call { function, args, .. } => {
                write_call(context.output(), "bit_array.call", function, args);
            }
            Self::FunctionCall { function, args, .. } => {
                write_function_call(context.output(), "bit_array.function_call", function, args);
            }
            Self::TupleIndex { tuple, index } => {
                write_projection(context.output(), "bit_array.tuple_index", tuple, *index);
            }
            Self::CustomField { source, index } => {
                write_projection(context.output(), "bit_array.custom_field", source, *index);
            }
            Self::ListIndex { list, index } => {
                write_projection(context.output(), "bit_array.list_index", list, *index);
            }
        }
    }
}

impl Explain for BitArrayEvaluatedSize {
    fn write_explanation(&self, context: &mut ExplainContext<'_, '_>) {
        self.value().write_local_label(context.output());
        context.push('*');
        context.push_str(&self.unit().to_string());
    }
}

impl Explain for BitArrayBitsSize {
    fn write_explanation(&self, context: &mut ExplainContext<'_, '_>) {
        match self {
            Self::Fixed(size) => context.push_str(&size.to_string()),
            Self::Evaluated(size) => context.write(size),
        }
    }
}

impl Explain for BitArraySegment {
    fn write_explanation(&self, context: &mut ExplainContext<'_, '_>) {
        match self {
            Self::Int {
                value,
                bit_size,
                endianness: order,
            } => {
                context.push_str("int(");
                value.write_local_label(context.output());
                context.push_str(", bits=");
                context.push_str(&bit_size.to_string());
                context.push_str(", ");
                context.push_str(endianness(*order));
                context.push(')');
            }
            Self::EvaluatedInt {
                value,
                size,
                endianness: order,
                ..
            } => {
                context.push_str("int(");
                value.write_local_label(context.output());
                context.push_str(", bits=");
                context.write(size);
                context.push_str(", ");
                context.push_str(endianness(*order));
                context.push(')');
            }
            Self::Float {
                value,
                bit_size,
                endianness: order,
            } => {
                context.push_str("float(");
                value.write_local_label(context.output());
                context.push_str(", bits=");
                context.push_str(&float_size(*bit_size).to_string());
                context.push_str(", ");
                context.push_str(endianness(*order));
                context.push(')');
            }
            Self::EvaluatedFloat {
                value,
                size,
                endianness: order,
                ..
            } => {
                context.push_str("float(");
                value.write_local_label(context.output());
                context.push_str(", bits=");
                context.write(size);
                context.push_str(", ");
                context.push_str(endianness(*order));
                context.push(')');
            }
            Self::String { value, encoding } => {
                context.push_str("string(");
                value.write_local_label(context.output());
                context.push_str(", ");
                context.push_str(string_encoding(*encoding));
                context.push(')');
            }
            Self::UtfCodepoint { value, encoding } => {
                context.push_str("utf_codepoint(");
                value.write_local_label(context.output());
                context.push_str(", ");
                context.push_str(string_encoding(*encoding));
                context.push(')');
            }
            Self::Bits(value) => {
                context.push_str("bits(");
                value.write_local_label(context.output());
                context.push(')');
            }
            Self::SizedBits { value, size, .. } => {
                context.push_str("bits(");
                value.write_local_label(context.output());
                context.push_str(", bits=");
                context.write(size);
                context.push(')');
            }
        }
    }
}

#[cfg(test)]
mod explain_tests {
    use super::{BitArrayBitsSize, BitArrayEvaluatedSize, BitArraySegment};
    use crate::plan::execution::explain;
    use crate::plan::execution::function::BitArrayFunctionId;
    use crate::plan::execution::graph::IntLocalId;

    #[test]
    fn writes_bit_array_instruction_and_segment_grammar() {
        let source = r#"
pub fn main() {
  let size = 8
  let bits = <<1, 2>>
  <<
    1:4-big,
    2:size(size)-little,
    1.5:float-size(16)-big,
    2.5:float-size(size * 4)-little,
    "a":utf8,
    bits:bits-size(size),
  >>
}
"#;
        let expected = concat!(
            "    %int#0:shape#0(Int) = int.value 8\n",
            "    %int#1:shape#0(Int) = int.value 1\n",
            "    %int#2:shape#0(Int) = int.value 2\n",
            "    %bit_array#0:shape#1(BitArray) = bit_array.value ",
            "[int(%int#1, bits=8, big), int(%int#2, bits=8, big)]\n",
            "    %int#3:shape#0(Int) = int.value 1\n",
            "    %int#4:shape#0(Int) = int.value 2\n",
            "    %float#0:shape#2(Float) = float.value 1.5\n",
            "    %float#1:shape#2(Float) = float.value 2.5\n",
            "    %int#5:shape#0(Int) = int.value 4\n",
            "    %int#6:shape#0(Int) = int.mult %int#0 %int#5\n",
            "    %string#0:shape#3(String) = string.value \"a\"\n",
            "    %bit_array#1:shape#1(BitArray) = bit_array.value ",
            "[int(%int#3, bits=4, big), int(%int#4, bits=%int#0*1, little), ",
            "float(%float#0, bits=16, big), float(%float#1, bits=%int#6*1, little), ",
            "string(%string#0, utf8), bits(%bit_array#0, bits=%int#0*1)]\n",
        );

        assert_explanation(source, expected);
    }

    #[test]
    fn writes_bit_array_evaluated_size() {
        let source = "pub fn main() { <<>> }";
        let expected = "%int#2*4";

        explain::assert_rendered(source, expected, |plan, output| {
            let mut context = explain::ExplainContext::new(plan, output);
            context.write(&BitArrayEvaluatedSize::new(IntLocalId(2), 4));
        });
    }

    #[test]
    fn writes_fixed_and_evaluated_bit_array_bits_size() {
        let source = "pub fn main() { <<>> }";
        let expected = "8 | %int#2*4";

        explain::assert_rendered(source, expected, |plan, output| {
            let mut context = explain::ExplainContext::new(plan, output);
            context.write(&BitArrayBitsSize::Fixed(8));
            context.push_str(" | ");
            context.write(&BitArrayBitsSize::Evaluated(BitArrayEvaluatedSize::new(
                IntLocalId(2),
                4,
            )));
        });
    }

    #[test]
    fn writes_bit_array_segment() {
        let source = "pub fn main() { <<1>> }";
        let expected = "int(%int#2, bits=4, big)";

        explain::assert_rendered(source, expected, |plan, output| {
            let mut context = explain::ExplainContext::new(plan, output);
            context.write(&BitArraySegment::Int {
                value: IntLocalId(2),
                bit_size: 4,
                endianness: crate::plan::execution::graph::Endianness::Big,
            });
        });
    }

    fn assert_explanation(source: &str, expected: &str) {
        explain::assert_rendered(source, expected, |plan, output| {
            let graph = plan
                .bit_array_function(BitArrayFunctionId(0))
                .body()
                .block_graph();
            let mut context = explain::ExplainContext::new(plan, output);
            for instruction in graph.blocks()[0].instructions() {
                context.write(instruction);
            }
        });
    }
}