1use jzero_ast::tree::reset_ids;
37
38pub use jzero_semantic::SemanticResult;
41pub use jzero_codegen::pipeline::BytecodeOutput;
42pub use jzero_codegen::CodegenContext;
43
44#[derive(Debug, Clone)]
48pub struct RunOutput {
49 pub stdout: String,
51}
52
53#[derive(Debug)]
55pub struct CompileOutput {
56 pub binary: Vec<u8>,
58 pub text: String,
60 pub tac: String,
62}
63
64#[derive(Debug, Clone)]
68pub struct JzeroError(pub String);
69
70impl std::fmt::Display for JzeroError {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 write!(f, "{}", self.0)
73 }
74}
75
76impl std::error::Error for JzeroError {}
77
78#[derive(Default)]
85pub struct Compiler {
86 source: String,
87}
88
89impl Compiler {
90 pub fn new() -> Self {
92 Compiler::default()
93 }
94
95 pub fn source(mut self, src: &str) -> Self {
97 self.source = src.to_string();
98 self
99 }
100
101 fn analyse(&self) -> Result<(jzero_ast::tree::Tree, SemanticResult), JzeroError> {
106 reset_ids();
107 let mut tree = jzero_parser::parse_tree(&self.source)
108 .map_err(|e| JzeroError(e.to_string()))?;
109 let sem = jzero_semantic::analyze(&mut tree);
110 if !sem.errors.is_empty() {
111 let msg = sem.errors.iter()
112 .map(|e| e.to_string())
113 .collect::<Vec<_>>()
114 .join("\n");
115 return Err(JzeroError(msg));
116 }
117 Ok((tree, sem))
118 }
119
120 pub fn tac(&self) -> Result<String, JzeroError> {
125 let (tree, sem) = self.analyse()?;
126 let ctx = jzero_codegen::generate(&tree, &sem);
127 Ok(jzero_codegen::emit::emit(&tree, &ctx))
128 }
129
130 pub fn compile(&self, argc: i64) -> Result<CompileOutput, JzeroError> {
137 let (tree, sem) = self.analyse()?;
138 let ctx = jzero_codegen::generate(&tree, &sem);
139 let tac = jzero_codegen::emit::emit(&tree, &ctx);
140 let output = jzero_codegen::pipeline::compile_bytecode(&tree, &ctx, argc);
141 Ok(CompileOutput {
142 binary: output.binary,
143 text: output.text,
144 tac,
145 })
146 }
147
148 pub fn run(&self, args: &[&str]) -> Result<RunOutput, JzeroError> {
156 let owned: Vec<String> = args.iter().map(|s| s.to_string()).collect();
157 let argc = owned.len() as i64;
158 let (tree, sem) = self.analyse()?;
159 let ctx = jzero_codegen::generate(&tree, &sem);
160 let output = jzero_codegen::pipeline::compile_bytecode(&tree, &ctx, argc);
161 let stdout = jzero_vm::run(&output.binary, &owned)
162 .map_err(|e| JzeroError(e))?;
163 Ok(RunOutput { stdout })
164 }
165}
166
167#[cfg(test)]
170mod tests {
171 use super::*;
172
173 const HELLO: &str = r#"
174 public class hello {
175 public static void main(String argv[]) {
176 System.out.println("hello, jzero!");
177 }
178 }
179 "#;
180
181 const HELLO_LOOP: &str = r#"
182 public class hello_loop {
183 public static void main(String argv[]) {
184 int x;
185 x = argv.length;
186 x = x + 2;
187 while (x > 3) {
188 System.out.println("hello, jzero!");
189 x = x - 1;
190 }
191 }
192 }
193 "#;
194
195 const CONCAT: &str = r#"
196 public class concat {
197 public static void main(String argv[]) {
198 String s;
199 s = "hello, " + "jzero!";
200 System.out.println(s);
201 }
202 }
203 "#;
204
205 #[test]
206 fn hello_world_runs() {
207 let out = Compiler::new().source(HELLO).run(&[]).unwrap();
208 assert_eq!(out.stdout, "hello, jzero!\n");
209 }
210
211 #[test]
212 fn hello_loop_runs_four_times() {
213 let out = Compiler::new()
214 .source(HELLO_LOOP)
215 .run(&["a", "b", "c", "d", "e"])
216 .unwrap();
217 assert_eq!(out.stdout, "hello, jzero!\n".repeat(4));
218 }
219
220 #[test]
221 fn string_concat_runs() {
222 let out = Compiler::new().source(CONCAT).run(&[]).unwrap();
223 assert_eq!(out.stdout, "hello, jzero!\n");
224 }
225
226 #[test]
227 fn tac_contains_proc_main() {
228 let tac = Compiler::new().source(HELLO).tac().unwrap();
229 assert!(tac.contains("proc main"));
230 }
231
232 #[test]
233 fn compile_binary_has_magic() {
234 let out = Compiler::new().source(HELLO).compile(0).unwrap();
235 assert_eq!(&out.binary[0..8], b"Jzero!!\0");
236 }
237
238 #[test]
239 fn semantic_error_is_reported() {
240 let src = r#"
241 public class bad {
242 public static void main(String argv[]) {
243 int x;
244 x = "not an int";
245 }
246 }
247 "#;
248 let _ = Compiler::new().source(src).tac();
252 }
253
254 #[test]
255 fn parse_error_returns_err() {
256 let result = Compiler::new().source("this is not valid jzero").run(&[]);
257 assert!(result.is_err());
258 }
259}