use std::collections::BTreeSet;
#[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,
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]); 26] = [
(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::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 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); 25] = [
(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::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(text.trim_end().to_string());
}
}
sections.join("\n\n")
}
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)"));
}
}