geam 0.1.2

Experimental Rust-embedded execution runtime for typed Gleam programs
mod bit_array;
mod block;
mod exit;
mod value;

pub(crate) use bit_array::{Endianness, FloatBitSize, StringEncoding};
pub(in crate::plan::execution::graph) use bit_array::{endianness, float_size, string_encoding};
pub(crate) use block::{
    BitArrayBindingPattern, BitArrayBitsSize, BitArrayEvaluatedSize, BitArrayInstruction,
    BitArrayPattern, BitArrayPatternSegment, BitArrayPatternSize, BitArrayPatternSizeExpr,
    BitArrayPatternValue, BitArraySegment, BitArrayStringPattern, Block, BlockId, BoolBranch,
    BoolInstruction, CustomInstruction, Echo, Edge, ExternalFunctionCallTarget,
    ExternalFunctionInstruction, ExternalFunctionInstructionKind, ExternalFunctionInstructionView,
    ExternalFunctionTarget, ExternalInstruction, ExternalInstructionRef, ExternalInstructionView,
    ExternalListInstruction, ExternalListInstructionView, FloatInstruction, FloatSwitch,
    FunctionCapture, FunctionInstruction, FunctionInstructionKind, FunctionTarget, Instruction,
    InstructionKind, IntInstruction, IntSwitch, Jump, LetAssertPanic, ListInstruction, Match,
    MatchEdge, MatchEdgeArgument, MatchIntBindingId, MatchPattern, MatchPatternBinding,
    MatchPatternList, MatchPatternListTail, NeverCall, NeverCallTarget, NilInstruction,
    ParameterListInstruction, ProfiledBlock, ProfiledInstruction, ProfiledInstructionKind,
    Signedness, SourceStop, SourceStopKind, StringInstruction, StringSwitch, Terminator,
    TupleInstruction, TypedListInstruction, UtfCodepointInstruction,
};
pub(crate) use exit::BlockGraphExitId;
pub(crate) use value::{
    BitArrayFunctionLocalId, BitArrayListFunctionLocalId, BitArrayListLocalId, BitArrayLocalId,
    BoolFunctionLocalId, BoolListFunctionLocalId, BoolListLocalId, BoolLocalId,
    CoreFunctionFunctionLocal, CoreFunctionFunctionLocalId, CustomFunctionLocal,
    CustomFunctionLocalId, CustomListFunctionLocalId, CustomListLocalId, CustomLocal,
    CustomLocalId, ExternalFunctionFunctionLocal, ExternalFunctionFunctionLocalId,
    ExternalFunctionLocal, ExternalFunctionLocalId, ExternalListFunctionLocalId,
    ExternalListLocalId, ExternalLocal, ExternalLocalId, FloatFunctionLocalId,
    FloatListFunctionLocalId, FloatListLocalId, FloatLocalId, FunctionFunctionLocal,
    FunctionListFunctionLocalId, FunctionListLocalId, FunctionLocal, GenericFunctionLocal,
    GenericFunctionLocalId, IntFunctionLocalId, IntListFunctionLocalId, IntListLocalId, IntLocalId,
    ListFunctionLocal, ListListFunctionLocalId, ListListLocalId, ListLocal, NeverFunctionLocal,
    NeverFunctionLocalId, NilFunctionLocalId, NilListFunctionLocalId, NilListLocalId, NilLocalId,
    ParamLocal, ParamSlot, ParameterListFunctionLocalId, ParameterListListFunctionLocalId,
    ParameterListListLocalId, ParameterListLocalId, StoredListLocal, StringFunctionLocalId,
    StringListFunctionLocalId, StringListLocalId, StringLocalId, TupleFunctionLocalId,
    TupleListFunctionLocalId, TupleListLocalId, TupleLocalId, UtfCodepointFunctionLocalId,
    UtfCodepointListFunctionLocalId, UtfCodepointListLocalId, UtfCodepointLocalId,
};
pub(in crate::plan::execution) use value::{LocalLabel, write_local_labels};

use crate::plan::execution::explain::{Explain, ExplainContext};
use crate::plan::execution::function::{
    ExecutionGraphProfile, FunctionLabelSource, HostedExecutionGraph,
};

pub(crate) struct ProfiledBlockGraph<Graph: ExecutionGraphProfile> {
    entry: BlockId,
    blocks: Box<[ProfiledBlock<Graph>]>,
}

pub(crate) type BlockGraph = ProfiledBlockGraph<HostedExecutionGraph>;

pub(in crate::plan::execution) trait BlockGraphExitExplanation {
    fn write_exit(&self, context: &mut ExplainContext<'_, '_>, exit: BlockGraphExitId);
}

pub(in crate::plan::execution::graph) struct BlockGraphExplainContext<'a, 'plan, 'output> {
    context: &'a mut ExplainContext<'plan, 'output>,
    exits: &'a dyn BlockGraphExitExplanation,
}

impl<Graph: ExecutionGraphProfile> ProfiledBlockGraph<Graph> {
    pub(in crate::plan::execution) fn from_parts(
        entry: BlockId,
        blocks: Vec<ProfiledBlock<Graph>>,
    ) -> Self {
        Self {
            entry,
            blocks: blocks.into_boxed_slice(),
        }
    }

    pub(crate) fn entry(&self) -> BlockId {
        self.entry
    }

    pub(crate) fn blocks(&self) -> &[ProfiledBlock<Graph>] {
        &self.blocks
    }

    pub(crate) fn block(&self, id: BlockId) -> &ProfiledBlock<Graph> {
        &self.blocks[id.index()]
    }

    pub(in crate::plan::execution) fn into_parts(self) -> (BlockId, Box<[ProfiledBlock<Graph>]>) {
        (self.entry, self.blocks)
    }

    pub(in crate::plan::execution) fn write_explanation(
        &self,
        context: &mut ExplainContext<'_, '_>,
        entry_params: &[ParamSlot],
        entry_captures: &[ParamSlot],
        exits: &dyn BlockGraphExitExplanation,
    ) where
        Graph::ExternalFunctionId: FunctionLabelSource,
        Graph::ExternalListFunctionId: FunctionLabelSource,
        Graph::ExternalFunctionFunctionId: FunctionLabelSource,
        Graph::ExternalListFunctionFunctionId: FunctionLabelSource,
        Graph::ExternalInstruction: Explain,
        Graph::ExternalListInstruction: Explain,
        Graph::ExternalFunctionInstruction: Explain,
    {
        context.push_str("  entry b");
        context.push_str(&self.entry().index().to_string());
        context.push_str(" params=");
        context.write_list(entry_params, |context, slot| context.write(slot));
        context.push_str(" captures=");
        context.write_list(entry_captures, |context, slot| context.write(slot));
        context.push('\n');

        let mut graph_context = BlockGraphExplainContext { context, exits };
        for (index, block) in self.blocks().iter().enumerate() {
            block.write_explanation(&mut graph_context, index);
        }
    }
}

impl BlockGraphExplainContext<'_, '_, '_> {
    pub(in crate::plan::execution::graph) fn push(&mut self, character: char) {
        self.context.push(character);
    }

    pub(in crate::plan::execution::graph) fn push_str(&mut self, text: &str) {
        self.context.push_str(text);
    }

    pub(in crate::plan::execution::graph) fn write<Value>(&mut self, value: &Value)
    where
        Value: Explain + ?Sized,
    {
        self.context.write(value);
    }

    pub(in crate::plan::execution::graph) fn write_list<Value>(
        &mut self,
        values: &[Value],
        mut write_value: impl FnMut(&mut Self, &Value),
    ) {
        self.push('[');
        for (index, value) in values.iter().enumerate() {
            if index > 0 {
                self.push_str(", ");
            }
            write_value(self, value);
        }
        self.push(']');
    }

    pub(in crate::plan::execution::graph) fn write_exit(&mut self, exit: BlockGraphExitId) {
        self.exits.write_exit(self.context, exit);
    }
}

#[cfg(test)]
mod explain_tests {
    use crate::plan::execution::explain;
    use crate::plan::execution::function::IntFunctionId;

    #[test]
    fn writes_complete_graph_entry_and_block_order() {
        let source = r#"
fn choose(flag: Bool) { case flag { True -> 1 False -> 0 } }
pub fn main() { choose(True) }
"#;
        let expected = concat!(
            "  entry b0 params=[%bool#0:shape#0(Bool)] captures=[]\n",
            "  block b0 params=[%bool#0:shape#0(Bool)]\n",
            "    branch %bool#0 true=b1() false=b2()\n",
            "  block b1 params=[]\n",
            "    %int#0:shape#1(Int) = int.value 1\n",
            "    return %int#0\n",
            "  block b2 params=[]\n",
            "    %int#0:shape#1(Int) = int.value 0\n",
            "    return %int#0\n",
        );

        assert_explanation(source, expected);
    }

    #[test]
    fn writes_echo_terminator_through_block_graph() {
        let source = r#"
fn emit(value: Int) {
  echo value
}

pub fn main() {
  emit(1)
}
"#;
        let expected = concat!(
            "  entry b0 params=[%int#0:shape#0(Int)] captures=[]\n",
            "  block b0 params=[%int#0:shape#0(Int)]\n",
            "    echo subject=%int#0 message=none site=main::emit@25..35 next=b1(%int#0)\n",
            "  block b1 params=[%int#0:shape#0(Int)]\n",
            "    return %int#0\n",
        );

        assert_explanation(source, expected);
    }

    fn assert_explanation(source: &str, expected: &str) {
        explain::assert_rendered(source, expected, |plan, output| {
            let function = plan.int_function(IntFunctionId(1));
            let body = function.body();
            let mut context = explain::ExplainContext::new(plan, output);
            body.write_explanation(
                &mut context,
                "int",
                function.entry().params(body),
                function.entry().captures(body),
            );
        });
    }
}