1mod bind;
10mod builtin;
11mod compiler;
12mod json;
13mod name;
14mod object;
15mod parser;
16mod scope_info;
17mod utility;
18use core::error::Error;
19use std::{
20 collections::{BTreeMap, HashMap, HashSet},
21 env, fs,
22 path::Path,
23 process::{Command, ExitCode},
24};
25#[macro_export]
27macro_rules! err {
28 ($self:ident, $pos:expr, $($arg:tt)*) => {Err($self.fmt_err(&format!($($arg)*), &$pos).into())};
29 ($self:ident, $($arg:tt)*) => {Err($self.fmt_err(&format!($($arg)*), &$self.pos).into())};
30}
31macro_rules! exit {($($arg: tt)*) =>{{eprintln!($($arg)*);return ExitCode::FAILURE;}}}
33#[macro_export]
35macro_rules! include_once {
36 ($self:ident, $dest:expr, $name:literal) => {
37 if !$self.include_flag.contains($name) {
38 $self.include_flag.insert($name.into());
39 $dest.push(include_str!(concat!("asm/", $name, ".s")).into());
40 }
41 };
42}
43#[derive(Clone)]
45struct AsmBool {
46 bit: u8,
48 name: Name,
50}
51#[derive(Clone)]
53struct AsmFunc {
54 name: usize,
56 params: Vec<JsonWithPos>,
58 ret: Box<Json>,
60}
61#[derive(Clone)]
63enum Bind<T> {
64 Lit(T),
66 Var(Name),
68}
69#[derive(Clone)]
71struct Builtin {
72 func: JFunc,
74 scoped: bool,
76 skip_eval: bool,
78}
79type ErrOR<T> = Result<T, Box<dyn Error>>;
81#[derive(Clone)]
83struct FuncInfo {
84 args: Vec<JsonWithPos>,
86 name: String,
88 pos: Position,
90}
91enum GlobalKind {
93 Bss,
95 Float,
97 Func,
99 Int,
101 Str,
103}
104type JFunc = fn(&mut Jsonpiler, FuncInfo, &mut ScopeInfo) -> ErrOR<Json>;
106#[derive(Clone, Default)]
108pub(crate) struct JObject {
109 entries: Vec<(String, JsonWithPos)>,
111}
112#[derive(Clone, Default)]
114enum Json {
115 Array(Bind<Vec<JsonWithPos>>),
117 Float(Bind<f64>),
119 Function(AsmFunc),
121 Int(Bind<i64>),
123 LBool(bool),
125 #[default]
127 Null,
128 Object(Bind<JObject>),
130 String(Bind<String>),
132 #[expect(dead_code, reason = "todo")]
134 VBool(AsmBool),
135}
136#[derive(Clone, Default)]
138struct JsonWithPos {
139 pos: Position,
141 value: Json,
143}
144#[derive(Clone, Default)]
146pub struct Jsonpiler {
147 bss: Vec<String>,
149 builtin: HashMap<String, Builtin>,
151 data: Vec<String>,
153 global_seed: usize,
155 include_flag: HashSet<String>,
157 pos: Position,
159 source: Vec<u8>,
161 str_cache: HashMap<String, usize>,
163 text: Vec<String>,
165 vars: Vec<HashMap<String, Json>>,
167}
168#[derive(Clone)]
170struct Name {
171 seed: usize,
173 var: VarKind,
175}
176#[derive(Clone, Default)]
178struct Position {
179 line: usize,
181 offset: usize,
183 size: usize,
185}
186#[derive(Clone, Default)]
188struct ScopeInfo {
189 args_slots: usize,
191 body: Vec<String>,
193 bool_map: BTreeMap<usize, u8>,
195 free_map: BTreeMap<usize, usize>,
197 reg_used: HashSet<String>,
199 scope_align: usize,
201 stack_size: usize,
203}
204#[derive(Clone, Copy, PartialEq)]
206enum VarKind {
207 Global,
209 Local,
211 Tmp,
213}
214fn add(op1: usize, op2: usize) -> ErrOR<usize> {
216 op1.checked_add(op2).ok_or("InternalError: Overflow".into())
217}
218#[inline]
244#[must_use]
245pub fn run() -> ExitCode {
246 #[cfg(all(not(doc), not(target_os = "windows")))]
247 compile_error!("This program is supported on Windows only.");
248 let args: Vec<String> = env::args().collect();
249 let Some(program_name) = args.first() else { exit!("Failed to get the program name.") };
250 let Some(input_file) = args.get(1) else {
251 exit!("Usage: {program_name} <input_json_file> [args for .exe]")
252 };
253 let source = match fs::read_to_string(input_file) {
254 Ok(content) => content,
255 Err(err) => exit!("Failed to read `{input_file}`: {err}"),
256 };
257 let mut jsonpiler = Jsonpiler::default();
258 let file = Path::new(input_file);
259 let with_ext = |ext: &str| -> String { file.with_extension(ext).to_string_lossy().to_string() };
260 let asm = with_ext("s");
261 let obj = with_ext("obj");
262 let exe = with_ext("exe");
263 if let Err(err) = jsonpiler.build(source, &asm) {
264 exit!("Compilation error: {err}");
265 }
266 macro_rules! invoke {
267 ($cmd:literal, $list:expr,$name:literal) => {
268 match Command::new($cmd).args($list).status() {
269 Ok(status) if status.success() => (),
270 Ok(_) => exit!("{} returned a non-zero exit status.", $name),
271 Err(err) => exit!("Failed to invoke {}: {err}", $name),
272 };
273 };
274 }
275 invoke!("as", &[&asm, "-o", &obj], "assembler");
276 #[cfg(not(debug_assertions))]
277 if let Err(err) = fs::remove_file(&asm) {
278 exit!("Failed to delete `{asm}`: {err}")
279 }
280 invoke!(
281 "ld",
282 [&obj, "-o", &exe, "-LC:/Windows/System32", "-luser32", "-lkernel32", "-lucrtbase", "-emain"],
283 "linker"
284 );
285 if let Err(err) = fs::remove_file(&obj) {
286 exit!("Failed to delete `{obj}`: {err}")
287 }
288 let cwd = match env::current_dir() {
289 Ok(dir) => dir,
290 Err(err) => exit!("Failed to get current directory: {err}"),
291 }
292 .join(&exe);
293 let exe_status = match Command::new(cwd).args(args.get(2..).unwrap_or(&[])).status() {
294 Ok(status) => status,
295 Err(err) => exit!("Failed to execute compiled program: {err}"),
296 };
297 let Some(exit_code) = exe_status.code() else {
298 exit!("Could not get the exit code of the compiled program.")
299 };
300 let Ok(code) = u8::try_from(exit_code.rem_euclid(256)) else {
301 exit!("Internal error: Unexpected error in exit code conversion.")
302 };
303 ExitCode::from(code)
304}