🐛 Integration tests using the interpreter

main
Shad Amethyst 1 year ago
parent bfeb70dd5a
commit abe785cd89

@ -149,7 +149,6 @@ pub fn run(program: &MindustryProgram, stop_condition: StopCondition) -> HashMap
let mut steps = 0;
while !stop_condition.should_stop(steps, &state) {
println!("{:?}", state);
let _ = step(&compiled, &mut state);
steps = steps.saturating_add(1);
}
@ -193,15 +192,15 @@ pub fn step(
}
MindustryOperation::Operator(out_name, operation, lhs, rhs) => {
let result = eval_operation(*operation, lhs, rhs, state);
state.variables.insert(out_name.clone(), result);
store_value(out_name, result, state);
}
MindustryOperation::UnaryOperator(out_name, operation, value) => {
let result = eval_unary_operation(*operation, value, state);
state.variables.insert(out_name.clone(), result);
store_value(out_name, result, state);
}
MindustryOperation::Set(out_name, value) => {
let value = eval_operand(value, state);
state.variables.insert(out_name.clone(), value);
store_value(out_name, value, state);
}
MindustryOperation::End => {
state.counter = Counter(0);
@ -260,13 +259,13 @@ pub fn step(
fn eval_operand(operand: &Operand, state: &ProgramState<impl Rng>) -> Value {
match operand {
Operand::Variable(name) => {
if name == "@counter" {
Value::Number(state.counter.0 as f64)
} else {
state.variables.get(name).cloned().unwrap_or_default()
}
}
Operand::Variable(name) => match name.as_str() {
"@counter" => Value::Number(state.counter.0 as f64),
"true" => Value::Number(1.0),
"false" => Value::Number(0.0),
"null" => Value::Null,
other => state.variables.get(other).cloned().unwrap_or_default(),
},
Operand::String(string) => Value::String(string.clone()),
Operand::Integer(int) => Value::Number(*int as f64),
Operand::Float(float) => Value::Number(*float),
@ -321,3 +320,16 @@ fn eval_unary_operation(
UnaryOperator::Not => todo!(),
})
}
// TODO: implement as method?
fn store_value(name: &str, value: Value, state: &mut ProgramState<impl Rng>) {
match name {
"@counter" => {
let value: f64 = value.into();
state.counter = Counter(value as usize);
}
other => {
state.variables.insert(other.to_string(), value);
}
}
}

@ -1,7 +1,7 @@
use super::*;
#[test]
fn test_interpret_operation() {
fn test_interprete_operation() {
fn run_test(
instructions: impl IntoIterator<Item = MindustryOperation>,
) -> HashMap<String, Value> {

@ -6,6 +6,17 @@ pub mod parse;
pub mod repr;
pub mod translate;
pub fn parse_and_translate(
raw: &str,
config: &config::Config,
) -> Result<repr::mlog::MindustryProgram, parse::ParseError> {
let tokens = parse::tokenize(raw)?;
let ast = parse::build_ast(&tokens, config)?;
let translated = translate::translate_ast(&ast, &mut Default::default(), config);
Ok(translated)
}
pub mod prelude {
pub use crate::config::Config;
pub use crate::cursor::Cursor;

@ -0,0 +1,24 @@
use std::path::Path;
pub fn read_basic_files(path: impl AsRef<Path>) -> impl Iterator<Item = (String, String)> {
path.as_ref().read_dir().unwrap().filter_map(|entry| {
let Ok(entry) = entry else {
return None;
};
if entry
.file_name()
.into_string()
.map(|name| name.ends_with(".mbas"))
.unwrap_or(false)
{
let file_name = entry.file_name().into_string().unwrap();
let file = std::fs::read_to_string(entry.path()).unwrap_or_else(|e| {
panic!("Error opening {:?}: {:?}", file_name, e);
});
Some((file_name, file))
} else {
None
}
})
}

@ -1,43 +1,21 @@
#![feature(fs_try_exists)]
use std::path::{Path, PathBuf};
use std::path::PathBuf;
#[path = "./common.rs"]
mod common;
use common::*;
use basic_to_mindustry::optimize::{optimize_constant, optimize_jump_always, optimize_jump_op};
use basic_to_mindustry::parse::{build_ast, tokenize};
use basic_to_mindustry::prelude::*;
use basic_to_mindustry::translate::translate_ast;
fn read_basic_examples() -> impl Iterator<Item = (String, String)> {
Path::new("./examples/")
.read_dir()
.unwrap()
.filter_map(|entry| {
let Ok(entry) = entry else {
return None;
};
if entry
.file_name()
.into_string()
.map(|name| name.ends_with(".mbas"))
.unwrap_or(false)
{
let file_name = entry.file_name().into_string().unwrap();
let file = std::fs::read_to_string(entry.path()).unwrap_or_else(|e| {
panic!("Error opening {:?}: {:?}", file_name, e);
});
Some((file_name, file))
} else {
None
}
})
}
#[test]
fn test_examples() {
let config = Config::default();
for (file_name, file) in read_basic_examples() {
for (file_name, file) in read_basic_files("./examples/") {
let tokenized = tokenize(&file).unwrap_or_else(|e| {
panic!("Error tokenizing {:?}: {:?}", file_name, e);
});
@ -56,7 +34,7 @@ fn test_examples() {
fn test_examples_opt() {
let config = Config::default();
for (file_name, file) in read_basic_examples() {
for (file_name, file) in read_basic_files("./examples/") {
let Some(program_name) = PathBuf::from(file_name.clone())
.file_stem()
.and_then(|stem| stem.to_str())

@ -0,0 +1,11 @@
LET sum = 0
FOR i = 2 TO 5
sum = sum + i
NEXT i
IF sum == 14 THEN
PRINT "success"
ELSE
PRINT "fail"
END IF

@ -0,0 +1,26 @@
LET x = 0
GOSUB double
GOSUB add_one
GOSUB double
GOSUB double
GOSUB add_one
IF x == 5 THEN
PRINT "success"
ELSE
PRINT "fail"
END IF
END
PRINT "unreachable (after END)\n"
double:
x = x * 2
RETURN
PRINT "unreachable (after RETURN in double)\n"
add_one:
x = x + 1
RETURN
PRINT "unreachable (after RETURN in add_one)\n"

@ -0,0 +1,13 @@
LET x = 32
start:
x = x / 2
IF x > 1 THEN
GOTO start
END IF
IF x == 1 THEN
PRINT "success"
ELSE
PRINT "fail"
END IF

@ -0,0 +1,7 @@
LET x = 1
IF x > 0 THEN
PRINT "success"
ELSE
PRINT "fail"
END IF

@ -0,0 +1,11 @@
LET x = 32
WHILE x > 1
x = x / 2
END WHILE
IF x == 1 THEN
PRINT "success"
ELSE
PRINT "fail"
END IF

@ -0,0 +1,64 @@
#![feature(fs_try_exists)]
#[path = "./common.rs"]
mod common;
use common::*;
use basic_to_mindustry::{
interpreter::{run, StopCondition, Value},
optimize::{optimize_constant, optimize_jump_always, optimize_jump_op},
parse_and_translate,
prelude::MindustryProgram,
};
/// Verifies that each test file in the `tests/print-success` test suite return `success` in the `@print` buffer.
#[test]
fn test_interprete_success() {
fn test(program: &MindustryProgram, file_name: &str, opt_name: &str) {
let result = run(&program, StopCondition::End);
assert_eq!(
result.get("@print"),
Some(&Value::String(String::from("success"))),
"File {file_name} did not print 'success' in the {opt_name} configuration: {:?}",
result.get("@print").cloned().unwrap_or_default()
);
}
macro_rules! chain {
( $( $optimizer:expr ),* ) => {
Box::new(|prog| {
let res = prog;
$(
let res = $optimizer(res);
)*
res
})
}
}
let optimizations: Vec<(
&'static str,
Box<dyn Fn(MindustryProgram) -> MindustryProgram>,
)> = vec![
("unoptimized", chain!()),
("constant", chain!(optimize_constant)),
(
"constant+jump_op",
chain!(optimize_constant, optimize_jump_op),
),
(
"level-1",
chain!(optimize_constant, optimize_jump_op, optimize_jump_always),
),
];
for (file_name, file) in read_basic_files("./tests/print-success/") {
let unoptimized = parse_and_translate(&file, &Default::default()).unwrap();
for (opt_name, optimizer) in optimizations.iter() {
let optimized = optimizer(unoptimized.clone());
test(&optimized, &file_name, opt_name);
}
}
}
Loading…
Cancel
Save