nova-interpreter 0.1.1

An interpreter library for the nova language
Documentation
use itertools::Itertools;
use ruleset::{Rule, RuleSet};

use crate::utils::{interner::Interner, Result};

pub mod ruleset;

#[derive(PartialEq, Eq)]
pub enum State {
    Left,
    Right,
}

/// Build a ruleset from the given code, interning counter names in the interner
pub fn parse(code: &str, interner: &mut Interner<String>) -> Result<RuleSet> {
    let mut buffer = String::new();
    let mut left: Vec<(usize, usize)> = vec![];
    let mut right: Vec<(usize, usize)> = vec![];
    let mut state = State::Right;

    let hash = interner.intern("#");
    let comment = interner.intern("--");
    let string = interner.intern("string");
    let variables = interner.intern("variables");

    // Default the delimiter to '|' to prevent the compiler from complaining
    // the only time it will not be overwritten is if there is only whitespace
    // in a file, so this default won't affect behavior
    let mut delimiter = '|';

    let mut ruleset: Vec<Rule> = Vec::new();

    for c in code.chars() {
        if c.is_whitespace() {
            continue;
        }
        delimiter = c;
        break;
    }

    for c in code.chars() {
        if c == delimiter || c == ',' {
            add_tuple(
                &mut buffer,
                if state == State::Left {
                    &mut left
                } else {
                    &mut right
                },
                interner,
            );
            if c == delimiter {
                if state == State::Left {
                    state = State::Right
                } else {
                    // TODO: fix this
                    // Only required because we start the parser
                    // in the "right" state so it will create a single
                    // empty rule otherwise
                    if left.is_empty() && right.is_empty() {
                    } else {
                        if left == [(hash, 1)] {
                            make_variable_rules(
                                &right,
                                string,
                                interner,
                                delimiter,
                                &mut ruleset,
                                variables,
                            );
                        } else if left == [(comment, 1)] {
                        } else {
                            ruleset.push(Rule {
                                conditions: left.clone().into_boxed_slice(),
                                multiplicities: right.clone().into_boxed_slice(),
                            });
                        }

                        // Clear the lists of facts, leaving the memory behind
                        right.clear();
                        left.clear();
                    }

                    state = State::Left;
                }
            }
        } else {
            buffer.push(c);
        }
    }

    add_tuple(
        &mut buffer,
        if state == State::Left {
            &mut left
        } else {
            &mut right
        },
        interner,
    );

    ruleset.push(Rule {
        conditions: left.into_boxed_slice(),
        multiplicities: right.into_boxed_slice(),
    });

    let (facts, rules): (Vec<Rule>, Vec<Rule>) = ruleset.iter().cloned().partition(|r| r.is_fact());

    Ok(RuleSet { facts, rules })
}

fn make_variable_rules(
    right: &[(usize, usize)],
    string: usize,
    interner: &mut Interner<String>,
    delimiter: char,
    ruleset: &mut Vec<Rule>,
    variables: usize,
) {
    if let Some(&(tuple, _)) = right.first() {
        if tuple == string {
            let name_interned = right.get(1).unwrap().0;
            let name = interner.lookup(name_interned).unwrap().clone();

            let str = right
                .iter()
                .skip(2)
                .map(|s| interner.lookup(s.0).unwrap().clone())
                .map(|s| {
                    if let Some(s) = s.strip_prefix("#") {
                        match s {
                            "COMMA" | "comma" => ",".to_string(),
                            "SPACE" | "space" => " ".to_string(),
                            "DELIM" | "delim" => delimiter.to_string(),
                            "HASH" | "hash" => "#".to_string(),
                            "CR" | "cr" => "\r".to_string(),
                            "NL" | "nl" => "\n".to_string(),
                            other => format!("#{other}"),
                        }
                    } else {
                        s
                    }
                })
                .collect::<Vec<String>>()
                .join("");

            let name_load = interner.intern(format!("{name}.load"));
            let name_pointer = interner.intern(format!("{name}.pointer"));
            let name_length = interner.intern(format!("{name}.length"));

            let length = str.len();
            let str_interned = interner.intern(str);
            let ptr = interner.lookup(str_interned).unwrap().as_ptr() as usize;

            ruleset.push(Rule {
                conditions: vec![(name_load, 1)].into_boxed_slice(),
                multiplicities: vec![(name_pointer, ptr), (name_length, length)].into_boxed_slice(),
            });
        } else if tuple == variables {
            for (a, b) in right.iter().tuple_combinations().unique() {
                make_copy(a, interner, b, ruleset);
                make_copy(b, interner, a, ruleset);
            }
        }
    }
}

fn make_copy(
    left: &(usize, usize),
    interner: &mut Interner<String>,
    right: &(usize, usize),
    ruleset: &mut Vec<Rule>,
) {
    let src_i = right.0;
    let src = interner.lookup(src_i).unwrap().clone();
    let dest_i = left.0;
    let dest = interner.lookup(dest_i).unwrap().clone();

    let copy = interner.intern(format!("{dest} = {src}"));
    let r#move = interner.intern(format!("{src} -> {dest}"));
    let src_backup = interner.intern(format!("*{dest} = {src} src backup"));

    ruleset.push(Rule {
        conditions: vec![(copy, 1), (src_i, 1)].into_boxed_slice(),
        multiplicities: vec![(src_backup, 1), (dest_i, 1), (copy, usize::MAX)].into_boxed_slice(),
    });

    ruleset.push(Rule {
        conditions: vec![(copy, 1)].into_boxed_slice(),
        multiplicities: vec![].into_boxed_slice(),
    });

    ruleset.push(Rule {
        conditions: vec![(src_backup, 1)].into_boxed_slice(),
        multiplicities: vec![(src_i, 1)].into_boxed_slice(),
    });

    ruleset.push(Rule {
        conditions: vec![(r#move, 1), (src_i, 1)].into_boxed_slice(),
        multiplicities: vec![(dest_i, 1), (r#move, usize::MAX)].into_boxed_slice(),
    });

    ruleset.push(Rule {
        conditions: vec![(r#move, 1)].into_boxed_slice(),
        multiplicities: vec![].into_boxed_slice(),
    });
}

#[inline]
fn add_tuple(buffer: &mut String, side: &mut Vec<(usize, usize)>, interner: &mut Interner<String>) {
    if !buffer.trim().is_empty() {
        let (fact, multiplicity) = parse_multiplicity(buffer).expect("there should be a fact");

        side.push((interner.intern(fact.trim().to_string()), multiplicity));
    }

    buffer.clear();
}

fn parse_multiplicity(buffer: &mut str) -> Option<(&str, usize)> {
    let mut split = buffer.split(':');

    let fact = split.next()?;

    split.next().map_or(Some((buffer, 1)), |multiplicity| {
        if let Ok(multiplicity) = str::parse(multiplicity.trim()) {
            Some((fact, multiplicity))
        } else {
            Some((buffer, 1))
        }
    })
}