knave 0.12.7

A Lisp programming language for humans and agents that infers types and ownership and compiles to readable Rust
Documentation
use std::collections::BTreeSet;

use crate::language::Language;

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum RuntimePart {
    Number,
    FromLiteral,
    FromFloatLiteral,
    Signed,
    Float,
    Integer,
    Sequence,
    MapSequence,
    Io,
    Exit,
    Source,
    Lines,
    Signal,
    Show,
    UnwrapOk,
    UnwrapErr,
    ExpectOk,
    FailedRecord,
    CheckFailure,
    CheckTrue,
    CheckEqual,
    CheckTrueWith,
    CheckEqualWith,
    NoteUntested,
    RunMarker,
    TestRun,
    RunTest,
    Max,
    Min,
}

impl RuntimePart {
    const STANDARD_STREAM_MARKERS: &'static [&'static str] = &[
        "print!(",
        "println!(",
        "std::io::stdout(",
        "eprint!(",
        "eprintln!(",
        "std::io::stderr(",
    ];
    const ALL: [(RuntimePart, &'static [&'static str]); 27] = [
        (RuntimePart::Number, &["crate::rt::Number"]),
        (RuntimePart::FromLiteral, &["::from_literal("]),
        (RuntimePart::FromFloatLiteral, &["::from_float_literal("]),
        (RuntimePart::Signed, &["crate::rt::Signed"]),
        (RuntimePart::Float, &["crate::rt::Float"]),
        (RuntimePart::Integer, &["crate::rt::Integer"]),
        (RuntimePart::Sequence, &["crate::rt::Sequence"]),
        (RuntimePart::MapSequence, &["std::collections::BTreeMap"]),
        (RuntimePart::Io, &["crate::rt::read_line"]),
        (RuntimePart::Exit, &["crate::rt::exit("]),
        (RuntimePart::Source, &["crate::rt::Source"]),
        (RuntimePart::Lines, &["crate::rt::lines("]),
        (RuntimePart::Signal, Self::STANDARD_STREAM_MARKERS),
        (RuntimePart::Show, &["crate::rt::Show"]),
        (RuntimePart::UnwrapOk, &["crate::rt::unwrap_ok("]),
        (RuntimePart::UnwrapErr, &["crate::rt::unwrap_err("]),
        (RuntimePart::ExpectOk, &["crate::rt::expect_ok("]),
        (RuntimePart::CheckTrue, &["crate::rt::check_true("]),
        (RuntimePart::CheckEqual, &["crate::rt::check_equal("]),
        (RuntimePart::CheckTrueWith, &["crate::rt::check_true_with("]),
        (
            RuntimePart::CheckEqualWith,
            &["crate::rt::check_equal_with("],
        ),
        (RuntimePart::NoteUntested, &["crate::rt::note_untested("]),
        (RuntimePart::RunMarker, &["crate::rt::run_marker("]),
        (RuntimePart::TestRun, &["crate::rt::start_run("]),
        (RuntimePart::RunTest, &["crate::rt::run_test("]),
        (RuntimePart::Max, &["crate::rt::max("]),
        (RuntimePart::Min, &["crate::rt::min("]),
    ];

    const REQUIRED: [(RuntimePart, &'static [RuntimePart]); 18] = [
        (RuntimePart::FromLiteral, &[RuntimePart::Number]),
        (RuntimePart::FromFloatLiteral, &[RuntimePart::Number]),
        (RuntimePart::Signed, &[RuntimePart::Number]),
        (RuntimePart::Integer, &[RuntimePart::Number]),
        (RuntimePart::Float, &[RuntimePart::Signed]),
        (RuntimePart::MapSequence, &[RuntimePart::Sequence]),
        (RuntimePart::Lines, &[RuntimePart::Io]),
        (RuntimePart::UnwrapOk, &[RuntimePart::Show]),
        (RuntimePart::UnwrapErr, &[RuntimePart::Show]),
        (RuntimePart::ExpectOk, &[RuntimePart::Show]),
        (
            RuntimePart::CheckFailure,
            &[RuntimePart::FailedRecord, RuntimePart::Show],
        ),
        (RuntimePart::CheckTrue, &[RuntimePart::CheckFailure]),
        (RuntimePart::CheckEqual, &[RuntimePart::CheckFailure]),
        (RuntimePart::CheckTrueWith, &[RuntimePart::CheckFailure]),
        (RuntimePart::CheckEqualWith, &[RuntimePart::CheckFailure]),
        (
            RuntimePart::NoteUntested,
            &[RuntimePart::Show, RuntimePart::Signal],
        ),
        (RuntimePart::TestRun, &[RuntimePart::Signal]),
        (
            RuntimePart::RunTest,
            &[
                RuntimePart::TestRun,
                RuntimePart::FailedRecord,
                RuntimePart::Show,
            ],
        ),
    ];

    fn present(self, code: &str) -> bool {
        let markers = Self::ALL
            .iter()
            .find(|(part, _)| *part == self)
            .map(|(_, markers)| *markers)
            .expect("every runtime part has markers");
        match self {
            Self::MapSequence => {
                (code.contains("std::collections::BTreeMap")
                    || code.contains("std::collections::HashMap"))
                    && code.contains("crate::rt::Sequence")
            }
            Self::Signal => markers
                .iter()
                .any(|marker| Self::contains_unprefixed(code, marker)),
            _ => markers.iter().any(|marker| code.contains(marker)),
        }
    }

    fn contains_unprefixed(code: &str, marker: &str) -> bool {
        code.match_indices(marker)
            .any(|(start, _)| !Self::continues_identifier(&code[..start]))
    }

    fn continues_identifier(preceding: &str) -> bool {
        preceding
            .chars()
            .next_back()
            .is_some_and(|character| character.is_alphanumeric() || character == '_')
    }

    fn required(self) -> &'static [RuntimePart] {
        Self::REQUIRED
            .iter()
            .find(|(part, _)| *part == self)
            .map_or(&[], |(_, required)| *required)
    }
}

struct Conversion {
    part: RuntimePart,
    name: &'static str,
    input: &'static str,
}

pub struct RuntimeModule {
    parts: BTreeSet<RuntimePart>,
}

impl RuntimeModule {
    const SIGNED: &'static str = include_str!("../../prelude/runtime/signed.rs");
    const FLOAT: &'static str = include_str!("../../prelude/runtime/float.rs");
    const INTEGER: &'static str = include_str!("../../prelude/runtime/integer.rs");
    const SEQUENCE: &'static str = include_str!("../../prelude/runtime/sequence.rs");
    const MAP_SEQUENCE: &'static str = include_str!("../../prelude/runtime/map_sequence.rs");
    const IO: &'static str = include_str!("../../prelude/runtime/io.rs");
    const EXIT: &'static str = include_str!("../../prelude/runtime/exit.rs");
    const SOURCE: &'static str = include_str!("../../prelude/runtime/source.rs");
    const LINES: &'static str = include_str!("../../prelude/runtime/lines.rs");
    const SIGNAL: &'static str = include_str!("../../prelude/runtime/signal.rs");
    const SHOW: &'static str = include_str!("../../prelude/runtime/show.rs");
    const UNWRAP_OK: &'static str = include_str!("../../prelude/runtime/unwrap_ok.rs");
    const UNWRAP_ERR: &'static str = include_str!("../../prelude/runtime/unwrap_err.rs");
    const EXPECT_OK: &'static str = include_str!("../../prelude/runtime/expect_ok.rs");
    const FAILED_RECORD: &'static str = include_str!("../../prelude/runtime/failed_record.rs");
    const CHECK_FAILURE: &'static str = include_str!("../../prelude/runtime/check_failure.rs");
    const CHECK_TRUE: &'static str = include_str!("../../prelude/runtime/check_true.rs");
    const CHECK_EQUAL: &'static str = include_str!("../../prelude/runtime/check_equal.rs");
    const CHECK_TRUE_WITH: &'static str = include_str!("../../prelude/runtime/check_true_with.rs");
    const CHECK_EQUAL_WITH: &'static str =
        include_str!("../../prelude/runtime/check_equal_with.rs");
    const NOTE_UNTESTED: &'static str = include_str!("../../prelude/runtime/note_untested.rs");
    const RUN_MARKER: &'static str = include_str!("../../prelude/runtime/run_marker.rs");
    const TEST_RUN: &'static str = include_str!("../../prelude/runtime/test_run.rs");
    const RUN_TEST: &'static str = include_str!("../../prelude/runtime/run_test.rs");
    const MAX: &'static str = include_str!("../../prelude/runtime/max.rs");
    const MIN: &'static str = include_str!("../../prelude/runtime/min.rs");
    const START_UP_CALL: &'static str = "crate::rt::restore_default_sigpipe();";
    const FIXED_PARTS: [(RuntimePart, &'static str); 26] = [
        (RuntimePart::Signed, Self::SIGNED),
        (RuntimePart::Float, Self::FLOAT),
        (RuntimePart::Integer, Self::INTEGER),
        (RuntimePart::Sequence, Self::SEQUENCE),
        (RuntimePart::MapSequence, Self::MAP_SEQUENCE),
        (RuntimePart::Io, Self::IO),
        (RuntimePart::Exit, Self::EXIT),
        (RuntimePart::Source, Self::SOURCE),
        (RuntimePart::Lines, Self::LINES),
        (RuntimePart::Signal, Self::SIGNAL),
        (RuntimePart::Show, Self::SHOW),
        (RuntimePart::UnwrapOk, Self::UNWRAP_OK),
        (RuntimePart::UnwrapErr, Self::UNWRAP_ERR),
        (RuntimePart::ExpectOk, Self::EXPECT_OK),
        (RuntimePart::FailedRecord, Self::FAILED_RECORD),
        (RuntimePart::CheckFailure, Self::CHECK_FAILURE),
        (RuntimePart::CheckTrue, Self::CHECK_TRUE),
        (RuntimePart::CheckEqual, Self::CHECK_EQUAL),
        (RuntimePart::CheckTrueWith, Self::CHECK_TRUE_WITH),
        (RuntimePart::CheckEqualWith, Self::CHECK_EQUAL_WITH),
        (RuntimePart::NoteUntested, Self::NOTE_UNTESTED),
        (RuntimePart::RunMarker, Self::RUN_MARKER),
        (RuntimePart::TestRun, Self::TEST_RUN),
        (RuntimePart::RunTest, Self::RUN_TEST),
        (RuntimePart::Max, Self::MAX),
        (RuntimePart::Min, Self::MIN),
    ];
    const CONVERSIONS: &'static [Conversion] = &[
        Conversion {
            part: RuntimePart::FromLiteral,
            name: "from_literal",
            input: "i128",
        },
        Conversion {
            part: RuntimePart::FromFloatLiteral,
            name: "from_float_literal",
            input: "f64",
        },
    ];
    const NUMBER_TRAIT: &'static str = "pub trait Number:\n    Copy + PartialOrd + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self> + Div<Output = Self> + Rem<Output = Self>\n    + AddAssign + SubAssign + MulAssign + DivAssign + RemAssign\n";
    const NUMBER_IMPLEMENTATIONS: &'static str = "macro_rules! numbers {\n    ($($number:ty),*) => {\n        $(\n            impl Number for $number ";
    const NUMBER_IMPLEMENTATIONS_END: &'static str = "\n        )*\n    };\n}\n\nnumbers!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64);";
    const IMPLEMENTATION_INDENT: &'static str = "            ";

    pub fn of(code: &str) -> Option<RuntimeModule> {
        let mut parts: BTreeSet<RuntimePart> = RuntimePart::ALL
            .into_iter()
            .map(|(part, _)| part)
            .filter(|part| part.present(code))
            .collect();
        let mut unchecked: Vec<RuntimePart> = parts.iter().copied().collect();
        while let Some(part) = unchecked.pop() {
            for required in part.required() {
                if parts.insert(*required) {
                    unchecked.push(*required);
                }
            }
        }
        (!parts.is_empty()).then_some(RuntimeModule { parts })
    }

    pub fn start_up_call(&self) -> Option<&'static str> {
        self.parts
            .contains(&RuntimePart::Signal)
            .then_some(Self::START_UP_CALL)
    }

    pub fn source(&self) -> String {
        let mut sections = Vec::new();
        if self.parts.contains(&RuntimePart::Number) {
            sections.push(self.arithmetic_imports());
            sections.push(self.number_trait());
            sections.push(self.number_implementations());
        }
        for (part, text) in Self::FIXED_PARTS {
            if self.parts.contains(&part) {
                sections.push(Self::rendered(part, text));
            }
        }
        sections.join("\n\n")
    }

    pub fn test_marker_variable() -> String {
        format!("{}_TEST_MARKER", Language::NAME.to_ascii_uppercase())
    }

    fn rendered(part: RuntimePart, text: &str) -> String {
        let text = text.trim_end();
        match part {
            RuntimePart::RunMarker => format!(
                "const TEST_MARKER_VARIABLE: &str = {:?};\n\n{text}",
                Self::test_marker_variable()
            ),
            _ => text.to_string(),
        }
    }

    fn arithmetic_imports(&self) -> String {
        let negation = if self.parts.contains(&RuntimePart::Signed) {
            "Neg, "
        } else {
            ""
        };
        format!(
            "use std::ops::{{Add, AddAssign, Div, DivAssign, Mul, MulAssign, {negation}Rem, RemAssign, Sub, SubAssign}};"
        )
    }

    fn number_trait(&self) -> String {
        let declarations: Vec<String> = self
            .conversions()
            .map(|conversion| {
                format!(
                    "    fn {}(value: {}) -> Self;",
                    conversion.name, conversion.input
                )
            })
            .collect();
        format!("{}{}", Self::NUMBER_TRAIT, Self::body(&declarations, ""))
    }

    fn number_implementations(&self) -> String {
        let indent = Self::IMPLEMENTATION_INDENT;
        let definitions: Vec<String> = self
            .conversions()
            .map(|conversion| {
                format!(
                    "{indent}    fn {}(value: {}) -> Self {{\n{indent}        value as Self\n{indent}    }}",
                    conversion.name, conversion.input
                )
            })
            .collect();
        format!(
            "{}{}{}",
            Self::NUMBER_IMPLEMENTATIONS,
            Self::body(&definitions, indent),
            Self::NUMBER_IMPLEMENTATIONS_END
        )
    }

    fn conversions(&self) -> impl Iterator<Item = &'static Conversion> {
        Self::CONVERSIONS
            .iter()
            .filter(|conversion| self.parts.contains(&conversion.part))
    }

    fn body(members: &[String], indent: &str) -> String {
        if members.is_empty() {
            return "{}".to_string();
        }
        format!("{{\n{}\n{indent}}}", members.join("\n\n"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn each_standard_stream_write_restores_the_default_sigpipe() {
        for code in [
            "print!(\"a\")",
            "println!(\"a\")",
            "std::io::stdout()",
            "eprint!(\"a\")",
            "eprintln!(\"a\")",
            "std::io::stderr()",
        ] {
            let module = RuntimeModule::of(code).expect("the code needs the runtime");
            assert!(module.start_up_call().is_some(), "{code}");
        }
    }

    #[test]
    fn an_exit_call_brings_the_exit_function() {
        let module =
            RuntimeModule::of("crate::rt::exit(3_i32)").expect("the code needs the runtime");
        assert!(module.source().contains("pub fn exit(code: i32)"));
    }
}