1use crate::utils::iterate_files;
4use clap::Parser;
5use cranelift_interpreter::environment::FunctionStore;
6use cranelift_interpreter::interpreter::{Interpreter, InterpreterState};
7use cranelift_interpreter::step::ControlFlow;
8use cranelift_reader::{parse_run_command, parse_test, ParseError, ParseOptions};
9use std::path::PathBuf;
10use std::{fs, io};
11use thiserror::Error;
12
13#[derive(Parser)]
15pub struct Options {
16 #[arg(required = true)]
18 files: Vec<PathBuf>,
19
20 #[arg(short, long)]
22 verbose: bool,
23}
24
25pub fn run(options: &Options) -> anyhow::Result<()> {
27 let mut total = 0;
28 let mut errors = 0;
29 for file in iterate_files(&options.files) {
30 total += 1;
31 let runner = FileInterpreter::from_path(file)?;
32 match runner.run() {
33 Ok(_) => {
34 if options.verbose {
35 println!("{}", runner.path());
36 }
37 }
38 Err(e) => {
39 if options.verbose {
40 println!("{}: {}", runner.path(), e.to_string());
41 }
42 errors += 1;
43 }
44 }
45 }
46
47 if options.verbose {
48 match total {
49 0 => println!("0 files"),
50 1 => println!("1 file"),
51 n => println!("{n} files"),
52 }
53 }
54
55 match errors {
56 0 => Ok(()),
57 1 => anyhow::bail!("1 failure"),
58 n => anyhow::bail!("{} failures", n),
59 }
60}
61
62pub struct FileInterpreter {
64 path: Option<PathBuf>,
65 contents: String,
66}
67
68impl FileInterpreter {
69 pub fn from_path(path: impl Into<PathBuf>) -> Result<Self, io::Error> {
71 let path = path.into();
72 log::trace!("New file runner from path: {}:", path.to_string_lossy());
73 let contents = fs::read_to_string(&path)?;
74 Ok(Self {
75 path: Some(path),
76 contents,
77 })
78 }
79
80 #[cfg(test)]
82 pub fn from_inline_code(contents: String) -> Self {
83 log::trace!("New file runner from inline code: {}:", &contents[..20]);
84 Self {
85 path: None,
86 contents,
87 }
88 }
89
90 pub fn path(&self) -> String {
92 match self.path {
93 None => "[inline code]".to_string(),
94 Some(ref p) => p.to_string_lossy().to_string(),
95 }
96 }
97
98 pub fn run(&self) -> Result<(), FileInterpreterFailure> {
101 let test = parse_test(&self.contents, ParseOptions::default())
103 .map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?;
104
105 let mut env = FunctionStore::default();
107 let mut commands = vec![];
108 for (func, details) in test.functions.iter() {
109 for comment in &details.comments {
110 if let Some(command) = parse_run_command(comment.text, &func.signature)
111 .map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?
112 {
113 commands.push(command);
114 }
115 }
116 env.add(func.name.to_string(), func);
118 }
119
120 for command in commands {
122 command
123 .run(|func_name, args| {
124 let func_name = &format!("%{func_name}");
126 let state = InterpreterState::default().with_function_store(env.clone());
127 match Interpreter::new(state).call_by_name(func_name, args) {
128 Ok(ControlFlow::Return(results)) => Ok(results.to_vec()),
129 Ok(_) => panic!("Unexpected returned control flow--this is likely a bug."),
130 Err(t) => Err(t.to_string()),
131 }
132 })
133 .map_err(|s| FileInterpreterFailure::FailedExecution(s))?;
134 }
135
136 Ok(())
137 }
138}
139
140#[derive(Error, Debug)]
142pub enum FileInterpreterFailure {
143 #[error("failure reading file")]
144 Io(#[from] io::Error),
145 #[error("failure parsing file {0}: {1}")]
146 ParsingClif(String, ParseError),
147 #[error("failed to run function: {0}")]
148 FailedExecution(String),
149}
150
151#[cfg(test)]
152mod test {
153 use super::*;
154
155 #[test]
156 fn nop() {
157 let code = String::from(
158 "
159 function %test() -> i8 {
160 block0:
161 nop
162 v1 = iconst.i8 -1
163 v2 = iconst.i8 42
164 return v1
165 }
166 ; run: %test() == -1
167 ",
168 );
169 FileInterpreter::from_inline_code(code).run().unwrap()
170 }
171
172 #[test]
173 fn filetests() {
174 run(&Options {
175 files: vec![PathBuf::from("../filetests/filetests/interpreter")],
176 verbose: true,
177 })
178 .unwrap()
179 }
180}