mod check;
pub mod fmt;
#[cfg(feature = "script")]
mod misp;
pub mod opt;
pub mod stack;
mod viz;
use fmt::{Annotation, FmtNode};
use crate::parse::Term;
use ::id_arena::{Arena, Id};
#[derive(Debug, ::mbot_proc_macro_helpers::Bytecode)]
enum Sign {
Positive,
Negative,
}
#[derive(Debug, Clone)]
enum Filter {
Simple(FilterKind),
SatisfiesPredicate,
}
#[derive(Debug, Copy, Clone, ::mbot_proc_macro_helpers::Bytecode)]
enum FilterKind {
KeepHigh,
KeepLow,
DropHigh,
DropLow,
}
impl FilterKind {
#[allow(dead_code)]
fn sign(&self) -> Sign {
use FilterKind::*;
match self {
KeepHigh => Sign::Positive,
KeepLow => Sign::Positive,
DropHigh => Sign::Negative,
DropLow => Sign::Negative,
}
}
}
#[derive(Debug, Clone)]
enum Coercion {
FromOutputToInt,
}
#[derive(Debug, Clone)]
enum Type {
Output,
Integer,
Boolean,
}
#[derive(Debug, Clone)]
enum BinaryOp {
Add,
Subtract,
#[allow(dead_code)]
LogicalAnd,
}
#[derive(Copy, Clone, Debug, ::mbot_proc_macro_helpers::Bytecode)]
enum Comparison {
Equal,
GreaterThan,
}
#[derive(Debug, Clone)]
struct Region {
graph: MirGraph,
end: NodeIndex,
}
#[derive(Debug)]
pub struct Mir {
graph: MirGraph,
top: NodeIndex,
}
impl Mir {
pub fn size(&self) -> usize {
use ::core::mem::size_of;
(self.graph.node_count() * size_of::<MirNode>())
+ (self.graph.edge_count() * size_of::<MirEdge>())
}
pub fn print_externals(&self) {
for node in self.graph.externals(::petgraph::Direction::Incoming) {
println!("Incoming: {:?}", self.graph[node]);
}
for node in self.graph.externals(::petgraph::Direction::Outgoing) {
println!("Outgoing: {:?}", self.graph[node]);
}
}
}
fn mir_type_of(graph: &MirGraph, node: NodeIndex) -> Type {
match graph[node] {
MirNode::Integer(_) => Type::Integer,
MirNode::Coerce(Coercion::FromOutputToInt) => Type::Integer,
MirNode::Roll => Type::Output,
MirNode::BinOp(BinaryOp::Add | BinaryOp::Subtract) => Type::Integer,
MirNode::BinOp(BinaryOp::LogicalAnd) => Type::Boolean,
MirNode::Filter(_) => Type::Output,
MirNode::Compare(_) => Type::Boolean,
MirNode::Count => Type::Integer,
MirNode::Apply => todo!("type of function application"),
MirNode::PartialApply => todo!("type of partial function application"),
MirNode::Loop(_, ref ty) => ty.clone(),
MirNode::Decision(_) => todo!("type of if/switch expressions"),
MirNode::FunctionDefinition(_) => todo!("type of function declaration"),
MirNode::RecursiveEnvironment(_) => todo!("type of recursive environment"),
MirNode::RegionArgument(_) => todo!("type of region argument"),
MirNode::End => todo!("type of region end"),
MirNode::Fmt(_) => todo!("type of fmt node"),
MirNode::UseFuel(_) => todo!("type of UseFuel"),
}
}
#[derive(Debug, Clone)]
enum MirNode {
Integer(i64),
Coerce(Coercion),
Roll,
BinOp(BinaryOp),
Filter(Filter),
Compare(Comparison),
Count,
#[allow(dead_code)]
Apply,
PartialApply,
Loop(Region, Type),
#[allow(dead_code)]
Decision(Vec<Region>),
FunctionDefinition(Region),
#[allow(dead_code)]
RecursiveEnvironment(Region),
RegionArgument(u8),
End,
Fmt(FmtNode),
UseFuel(u64),
}
impl MirNode {
fn produces_value(&self) -> bool {
match self {
MirNode::FunctionDefinition(_)
| MirNode::RecursiveEnvironment(_)
| MirNode::End
| MirNode::Fmt(_)
| MirNode::PartialApply => false,
MirNode::Integer(_)
| MirNode::Coerce(_)
| MirNode::Roll
| MirNode::BinOp(_)
| MirNode::Filter(_)
| MirNode::Compare(_)
| MirNode::Count
| MirNode::Apply
| MirNode::Loop(_, _)
| MirNode::Decision(_)
| MirNode::RegionArgument(_)
| MirNode::UseFuel(_) => true,
}
}
fn produces_output(&self) -> bool {
use ::petgraph::Direction;
use MirNode::*;
match self {
Fmt(_) => true,
Loop(region, _ty) => region
.graph
.edges_directed(region.end, Direction::Outgoing)
.any(|edge| matches!(edge.weight(), MirEdge::IntermediateResultDependency { .. })),
Decision(_regions) => todo!("output effect inference for decision points"),
_ => false,
}
}
#[allow(dead_code)]
fn is_structural(&self) -> bool {
use MirNode::*;
matches!(
self,
Loop(_, _) | Decision(_) | FunctionDefinition(_) | RecursiveEnvironment(_)
)
}
}
macro_rules! mir {
($($t:tt)*) => {};
}
mir! {
let mut results = Stack::new();
let mut to_roll = (2, 6);
loop {
let result = roll(..to_roll);
results.push(result);
to_roll = (count((== 6), result), to_roll.1);
} while (to_roll.0 != 0);
}
#[derive(Debug, Clone)]
enum MirEdge {
DataDependency {
port: u8,
},
FuelDependency,
IntermediateResultDependency {
port: u8,
},
FunctionDependency {
port: u8,
},
}
use ::petgraph::{graph::NodeIndex, Directed, Graph};
type MirGraph = Graph<MirNode, MirEdge, Directed>;
#[derive(Debug)]
pub enum InvalidProgram {
BooleanAsInteger,
}
struct BooleanAsInteger;
impl From<BooleanAsInteger> for InvalidProgram {
fn from(BooleanAsInteger: BooleanAsInteger) -> Self {
Self::BooleanAsInteger
}
}
pub fn lower(ast: &crate::parse::Program) -> Result<Mir, InvalidProgram> {
let mut graph = MirGraph::new();
struct State {
last_fuel_use: Option<NodeIndex>,
}
impl State {
fn new() -> Self {
Self {
last_fuel_use: None,
}
}
#[allow(dead_code)]
fn add_fuel_use(&mut self, graph: &mut MirGraph, node: NodeIndex) {
if let Some(last_fuel_use) = self.last_fuel_use {
graph.add_edge(node, last_fuel_use, MirEdge::FuelDependency);
}
self.last_fuel_use = Some(node);
}
}
let mut state = State::new();
struct Lowered {
data: NodeIndex,
intermediates: NodeIndex,
}
fn lower_node(
terms: &Arena<Term>,
graph: &mut MirGraph,
state: &mut State,
current: Id<Term>,
) -> Result<Lowered, InvalidProgram> {
fn coerce_to_int(
graph: &mut MirGraph,
term: NodeIndex,
) -> Result<NodeIndex, BooleanAsInteger> {
match mir_type_of(&*graph, term) {
Type::Output => {
let coercion = graph.add_node(MirNode::Coerce(Coercion::FromOutputToInt));
graph.add_edge(coercion, term, MirEdge::DataDependency { port: 0 });
Ok(coercion)
}
Type::Integer => Ok(term),
Type::Boolean => Err(BooleanAsInteger),
}
}
Ok(match &terms[current] {
Term::Constant(val) => {
let data = graph.add_node(MirNode::Integer(*val));
let record = graph.add_node(MirNode::Fmt(FmtNode::Record));
graph.add_edge(
record,
data,
MirEdge::IntermediateResultDependency { port: 0 },
);
let annotate =
graph.add_node(MirNode::Fmt(FmtNode::Annotate(Annotation::Constant)));
graph.add_edge(
annotate,
record,
MirEdge::IntermediateResultDependency { port: 0 },
);
Lowered {
data,
intermediates: annotate,
}
}
Term::DiceRoll(count, sides) => {
let annotation = Annotation::Roll {
count: *count,
sides: *sides,
};
let count = graph.add_node(MirNode::Integer(*count));
let sides = graph.add_node(MirNode::Integer(*sides));
let roll = graph.add_node(MirNode::Roll);
graph.add_edge(roll, count, MirEdge::DataDependency { port: 0 });
graph.add_edge(roll, sides, MirEdge::DataDependency { port: 1 });
let output = graph.add_node(MirNode::Fmt(FmtNode::Record));
graph.add_edge(
output,
roll,
MirEdge::IntermediateResultDependency { port: 0 },
);
let annotate = graph.add_node(MirNode::Fmt(FmtNode::Annotate(annotation)));
graph.add_edge(
annotate,
output,
MirEdge::IntermediateResultDependency { port: 0 },
);
Lowered {
data: roll,
intermediates: annotate,
}
}
Term::KeepHigh(term, keep_count) | Term::KeepLow(term, keep_count) => {
assert!(matches!(&terms[*term], Term::DiceRoll(_, _)));
let annotation = match &terms[current] {
Term::KeepHigh(_, _) => Annotation::KeepHigh {
keep_count: *keep_count,
},
Term::KeepLow(_, _) => Annotation::KeepLow {
keep_count: *keep_count,
},
_ => unreachable!(),
};
let Lowered {
data: roll,
intermediates: roll_intermediates,
} = lower_node(terms, &mut *graph, &mut *state, *term)?;
let keep_count = graph.add_node(MirNode::Integer(*keep_count));
let keep_by =
graph.add_node(MirNode::Filter(Filter::Simple(match &terms[current] {
Term::KeepHigh(_, _) => FilterKind::KeepHigh,
Term::KeepLow(_, _) => FilterKind::KeepLow,
_ => unreachable!(),
})));
graph.add_edge(keep_by, roll, MirEdge::DataDependency { port: 0 });
graph.add_edge(keep_by, keep_count, MirEdge::DataDependency { port: 1 });
let output = graph.add_node(MirNode::Fmt(FmtNode::Record));
graph.add_edge(
output,
keep_by,
MirEdge::IntermediateResultDependency { port: 0 },
);
let list = graph.add_node(MirNode::Fmt(FmtNode::MakeList));
let add_roll_intermediates = graph.add_node(MirNode::Fmt(FmtNode::PushToList));
graph.add_edge(
add_roll_intermediates,
list,
MirEdge::IntermediateResultDependency { port: 0 },
);
graph.add_edge(
add_roll_intermediates,
roll_intermediates,
MirEdge::IntermediateResultDependency { port: 1 },
);
let add_filtered_intermediates = graph.add_node(MirNode::Fmt(FmtNode::PushToList));
graph.add_edge(
add_filtered_intermediates,
add_roll_intermediates,
MirEdge::IntermediateResultDependency { port: 0 },
);
graph.add_edge(
add_filtered_intermediates,
output,
MirEdge::IntermediateResultDependency { port: 1 },
);
let annotate = graph.add_node(MirNode::Fmt(FmtNode::Annotate(annotation)));
graph.add_edge(
annotate,
add_filtered_intermediates,
MirEdge::IntermediateResultDependency { port: 0 },
);
Lowered {
data: keep_by,
intermediates: annotate,
}
}
Term::Explode(roll) => {
let (start_count, side_count): (_, i64) = match terms[*roll] {
Term::DiceRoll(count, sides) => (count, sides),
_ => unreachable!("the parser wouldn't accept explosion in invalid position"),
};
let annotate =
graph.add_node(MirNode::Fmt(FmtNode::Annotate(Annotation::Explode {
count: start_count,
sides: side_count,
})));
let mut explosion = MirGraph::new();
let count = explosion.add_node(MirNode::RegionArgument(0));
let sum = explosion.add_node(MirNode::RegionArgument(1));
let iterations = explosion.add_node(MirNode::Fmt(FmtNode::RegionArgument(0)));
let fueled_count =
explosion.add_node(MirNode::UseFuel((start_count as u64).saturating_mul(4)));
explosion.add_edge(fueled_count, count, MirEdge::DataDependency { port: 0 });
let sides = explosion.add_node(MirNode::Integer(side_count));
let roll = explosion.add_node(MirNode::Roll);
explosion.add_edge(roll, fueled_count, MirEdge::DataDependency { port: 0 });
explosion.add_edge(roll, sides, MirEdge::DataDependency { port: 1 });
let iteration = explosion.add_node(MirNode::Fmt(FmtNode::Record));
explosion.add_edge(
iteration,
roll,
MirEdge::IntermediateResultDependency { port: 0 },
);
let iterations = {
let out = explosion.add_node(MirNode::Fmt(FmtNode::PushToList));
explosion.add_edge(
out,
iterations,
MirEdge::IntermediateResultDependency { port: 0 },
);
explosion.add_edge(
out,
iteration,
MirEdge::IntermediateResultDependency { port: 1 },
);
out
};
let filter = explosion.add_node({
let mut func = MirGraph::new();
let die_result = func.add_node(MirNode::RegionArgument(0));
let sides = func.add_node(MirNode::Integer(side_count));
let cmp = func.add_node(MirNode::Compare(Comparison::Equal));
func.add_edge(cmp, die_result, MirEdge::DataDependency { port: 0 });
func.add_edge(cmp, sides, MirEdge::DataDependency { port: 1 });
let end = func.add_node(MirNode::End);
func.add_edge(end, cmp, MirEdge::DataDependency { port: 0 });
MirNode::FunctionDefinition(Region { graph: func, end })
});
let remaining = explosion.add_node(MirNode::Filter(Filter::SatisfiesPredicate));
explosion.add_edge(remaining, roll, MirEdge::DataDependency { port: 0 });
explosion.add_edge(remaining, filter, MirEdge::FunctionDependency { port: 0 });
let roll_sum = explosion.add_node(MirNode::Coerce(Coercion::FromOutputToInt));
explosion.add_edge(roll_sum, roll, MirEdge::DataDependency { port: 0 });
let next_sum = explosion.add_node(MirNode::BinOp(BinaryOp::Add));
explosion.add_edge(next_sum, sum, MirEdge::DataDependency { port: 0 });
explosion.add_edge(next_sum, roll_sum, MirEdge::DataDependency { port: 1 });
let remaining_count = explosion.add_node(MirNode::Count);
explosion.add_edge(
remaining_count,
remaining,
MirEdge::DataDependency { port: 0 },
);
let not_empty = explosion.add_node(MirNode::Compare(Comparison::GreaterThan));
explosion.add_edge(
not_empty,
remaining_count,
MirEdge::DataDependency { port: 0 },
);
let zero = explosion.add_node(MirNode::Integer(0));
explosion.add_edge(not_empty, zero, MirEdge::DataDependency { port: 1 });
let end = explosion.add_node(MirNode::End);
explosion.add_edge(end, next_sum, MirEdge::DataDependency { port: 1 });
explosion.add_edge(end, remaining_count, MirEdge::DataDependency { port: 0 });
explosion.add_edge(end, not_empty, MirEdge::DataDependency { port: 2 });
explosion.add_edge(
end,
iterations,
MirEdge::IntermediateResultDependency { port: 0 },
);
let explosion = graph.add_node(MirNode::Loop(
Region {
graph: explosion,
end,
},
Type::Integer,
));
let start_count = graph.add_node(MirNode::Integer(start_count));
graph.add_edge(explosion, start_count, MirEdge::DataDependency { port: 0 });
let zero = graph.add_node(MirNode::Integer(0));
graph.add_edge(explosion, zero, MirEdge::DataDependency { port: 1 });
let start_iterations = graph.add_node(MirNode::Fmt(FmtNode::MakeList));
graph.add_edge(
explosion,
start_iterations,
MirEdge::IntermediateResultDependency { port: 0 },
);
graph.add_edge(
annotate,
explosion,
MirEdge::IntermediateResultDependency { port: 0 },
);
Lowered {
data: explosion,
intermediates: annotate,
}
}
Term::Add(left, right) | Term::Subtract(left, right) => {
let left = lower_node(terms, &mut *graph, &mut *state, *left)?;
let right = lower_node(terms, &mut *graph, &mut *state, *right)?;
let input_list = graph.add_node(MirNode::Fmt(FmtNode::MakeList));
let add_left_intermediates = graph.add_node(MirNode::Fmt(FmtNode::PushToList));
graph.add_edge(
add_left_intermediates,
input_list,
MirEdge::IntermediateResultDependency { port: 0 },
);
graph.add_edge(
add_left_intermediates,
left.intermediates,
MirEdge::IntermediateResultDependency { port: 1 },
);
let add_right_intermediates = graph.add_node(MirNode::Fmt(FmtNode::PushToList));
graph.add_edge(
add_right_intermediates,
add_left_intermediates,
MirEdge::IntermediateResultDependency { port: 0 },
);
graph.add_edge(
add_right_intermediates,
right.intermediates,
MirEdge::IntermediateResultDependency { port: 1 },
);
let annotate;
let left = coerce_to_int(&mut *graph, left.data)?;
let right = coerce_to_int(&mut *graph, right.data)?;
let total = match &terms[current] {
Term::Add(_, _) => {
annotate = graph.add_node(MirNode::Fmt(FmtNode::Annotate(Annotation::Add)));
BinaryOp::Add
}
Term::Subtract(_, _) => {
annotate =
graph.add_node(MirNode::Fmt(FmtNode::Annotate(Annotation::Subtract)));
BinaryOp::Subtract
}
_ => unreachable!(),
};
graph.add_edge(
annotate,
add_right_intermediates,
MirEdge::IntermediateResultDependency { port: 0 },
);
let total = graph.add_node(MirNode::BinOp(total));
graph.add_edge(total, left, MirEdge::DataDependency { port: 0 });
graph.add_edge(total, right, MirEdge::DataDependency { port: 1 });
Lowered {
data: total,
intermediates: annotate,
}
}
Term::UnarySubtract(term) => {
let zero = graph.add_node(MirNode::Integer(0));
let term = lower_node(terms, &mut *graph, &mut *state, *term)?;
let annotate =
graph.add_node(MirNode::Fmt(FmtNode::Annotate(Annotation::UnarySubtract)));
graph.add_edge(
annotate,
term.intermediates,
MirEdge::IntermediateResultDependency { port: 0 },
);
let term = coerce_to_int(&mut *graph, term.data)?;
let total = graph.add_node(MirNode::BinOp(BinaryOp::Subtract));
graph.add_edge(total, zero, MirEdge::DataDependency { port: 0 });
graph.add_edge(total, term, MirEdge::DataDependency { port: 1 });
Lowered {
data: total,
intermediates: annotate,
}
}
Term::UnaryAdd(term) => {
let term = lower_node(terms, &mut *graph, &mut *state, *term)?;
term
}
})
}
let end = lower_node(ast.terms(), &mut graph, &mut state, ast.top)?;
let top = graph.add_node(MirNode::End);
graph.add_edge(top, end.data, MirEdge::DataDependency { port: 0 });
graph.add_edge(
top,
end.intermediates,
MirEdge::IntermediateResultDependency { port: 0 },
);
Ok(Mir { graph, top })
}
pub fn dot(mir: &Mir) -> String {
viz::dot(mir)
}