jsonpiler/
lib.rs

1//! (main.rs)
2//! ```
3//! use jsonpiler::run;
4//! use std::process::ExitCode;
5//! fn main() -> ExitCode {
6//!   run()
7//! }
8//! ```
9mod builtin;
10mod compiler;
11mod func_info;
12mod json;
13mod object;
14mod parser;
15use core::error::Error;
16use std::{
17  collections::{HashMap, HashSet},
18  env, fs,
19  path::Path,
20  process::{Command, ExitCode},
21};
22/// Generates an error.
23#[macro_export]
24macro_rules! err {
25  ($self:ident, $pos:expr, $($arg:tt)*) => {
26    Err($self.fmt_err(&format!($($arg)*), &$pos).into())
27  };
28  ($self:ident, $($arg:tt)*) => {
29    Err($self.fmt_err(&format!($($arg)*), &$self.pos).into())
30  };
31}
32/// Return `ExitCode`.
33macro_rules! exit {($($arg: tt)*) =>{{eprintln!($($arg)*);return ExitCode::FAILURE;}}}
34/// Arguments.
35type Args = [JsonWithPos];
36#[derive(Debug, Clone)]
37/// Assembly function representation.
38struct AsmFunc {
39  /// Name of function.
40  name: String,
41  /// Parameters of function.
42  params: Vec<JsonWithPos>,
43  /// Return type of function.
44  ret: Box<Json>,
45}
46/// Built-in function.
47#[derive(Debug, Clone)]
48struct Builtin {
49  /// Pointer of function.
50  func: JFunc,
51  /// If it is true, function introduces a new scope.
52  scoped: bool,
53  /// Should arguments already be evaluated.
54  skip_eval: bool,
55}
56type ErrOR<T> = Result<T, Box<dyn Error>>;
57/// Contain `JValue` or `Box<dyn Error>`.
58type FResult = ErrOR<Json>;
59/// Information of Function.
60#[derive(Debug, Clone, Default)]
61struct FuncInfo {
62  /// Size of arguments.
63  args_slots: usize,
64  /// Body of function.
65  body: Vec<String>,
66  /// Size of local variable.
67  local_size: usize,
68  /// Registers used.
69  reg_used: HashSet<String>,
70}
71/// Type of built-in function.
72type JFunc = fn(&mut Jsonpiler, &JsonWithPos, &Args, &mut FuncInfo) -> FResult;
73/// Represents a JSON object with key-value pairs.
74#[derive(Debug, Clone, Default)]
75struct JObject {
76  /// Stores the key-value pairs in insertion order.
77  entries: Vec<(String, JsonWithPos)>,
78  /// Maps keys to their index in the entries vector for quick lookup.
79  index: HashMap<String, usize>,
80}
81/// Contain `Json` or `Box<dyn Error>`.
82type JResult = ErrOR<JsonWithPos>;
83/// Type and value information.
84#[derive(Debug, Clone, Default)]
85enum Json {
86  /// Function.
87  Function(AsmFunc),
88  /// Array.
89  LArray(Vec<JsonWithPos>),
90  /// Bool.
91  LBool(bool),
92  /// Float.
93  LFloat(f64),
94  /// Integer.
95  LInt(i64),
96  /// Object.
97  LObject(JObject),
98  /// String.
99  LString(String),
100  /// Null.
101  #[default]
102  Null,
103  /// Array variable.
104  #[expect(dead_code, reason = "todo")]
105  VArray(String),
106  /// Bool variable.
107  #[expect(dead_code, reason = "todo")]
108  VBool(String, usize),
109  /// Float variable.
110  #[expect(dead_code, reason = "todo")]
111  VFloat(String),
112  /// Integer variable.
113  VInt(String),
114  /// Object variable.
115  #[expect(dead_code, reason = "todo")]
116  VObject(String),
117  /// String variable.
118  VString(String),
119}
120/// Json object.
121#[derive(Debug, Clone, Default)]
122struct JsonWithPos {
123  /// Line number of objects in the source code.
124  pos: Position,
125  /// Type and value information.
126  value: Json,
127}
128/// Parser and compiler.
129#[derive(Debug, Clone, Default)]
130pub struct Jsonpiler {
131  /// Built-in function table.
132  builtin: HashMap<String, Builtin>,
133  /// Flag to avoid including the same file twice.
134  include_flag: HashSet<String>,
135  /// Information to be used during parsing.
136  pos: Position,
137  /// Section of the assembly.
138  sect: Section,
139  /// Source code.
140  source: String,
141  /// Cache of the string.
142  str_cache: HashMap<String, usize>,
143  /// Seed to generate names.
144  symbol_seeds: HashMap<String, usize>,
145  /// Variable table.
146  vars: Vec<HashMap<String, Json>>,
147}
148impl Jsonpiler {
149  /// Format error.
150  #[must_use]
151  pub(crate) fn fmt_err(&self, err: &str, pos: &Position) -> String {
152    let gen_err = |msg: &str| -> String {
153      format!("{err}\nError occurred on line: {}\nError position:\n{msg}", pos.line)
154    };
155    if self.source.is_empty() {
156      return gen_err("\n^");
157    }
158    let len = self.source.len();
159    let idx = pos.offset.min(len.saturating_sub(1));
160    let start = if idx == 0 {
161      0
162    } else {
163      let Some(left) = self.source.get(..idx) else {
164        return gen_err("Error: Failed to get substring");
165      };
166      match left.rfind('\n') {
167        None => 0,
168        Some(start_offset) => {
169          let Some(res) = start_offset.checked_add(1) else {
170            return gen_err("Error: Overflow");
171          };
172          res
173        }
174      }
175    };
176    let Some(right) = self.source.get(idx..) else {
177      return gen_err("Error: Failed to get substring");
178    };
179    let end = match right.find('\n') {
180      None => len,
181      Some(end_offset) => {
182        let Some(res) = idx.checked_add(end_offset) else {
183          return gen_err("Error: Overflow");
184        };
185        res
186      }
187    };
188    let ws = " ".repeat(idx.saturating_sub(start));
189    let Some(result) = self.source.get(start..end) else {
190      return gen_err("Error: Failed to get substring");
191    };
192    gen_err(&format!("{result}\n{ws}^"))
193  }
194}
195/// line and pos in source code.
196#[derive(Debug, Clone, Default)]
197struct Position {
198  /// Line number of the part being parsed.
199  line: usize,
200  /// Byte offset of the part being parsed.
201  offset: usize,
202}
203/// Section of the assembly.
204#[derive(Debug, Clone, Default)]
205pub(crate) struct Section {
206  /// Buffer to store the contents of the bss section of the assembly.
207  bss: Vec<String>,
208  /// Buffer to store the contents of the data section of the assembly.
209  data: Vec<String>,
210  /// Buffer to store the contents of the text section of the assembly.
211  text: Vec<String>,
212}
213/// Compiles and executes a JSON-based program using the Jsonpiler.
214/// This function performs the following steps:
215/// 1. Parses the first CLI argument as the input JSON file path.
216/// 2. Reads the file content into a string.
217/// 3. Parses the string into a `Json` structure.
218/// 4. Compiles the structure into assembly code.
219/// 5. Assembles it into an `.obj` file.
220/// 6. Links it into an `.exe`.
221/// 7. Executes the resulting binary.
222/// 8. Returns its exit code.
223/// # Panics
224/// This function will panic if:
225/// - The platform is not Windows.
226/// - CLI arguments are invalid.
227/// - File reading, parsing, compilation, assembling, linking, or execution fails.
228/// - The working directory or executable filename is invalid.
229/// # Requirements
230/// - `as` and `ld` must be available in the system PATH.
231/// - On failure, exits with code 1 using `error_exit`.
232/// # Example
233/// ```sh
234/// ./jsonpiler test.json
235/// ```
236/// # Platform
237/// Windows only.
238#[inline]
239#[must_use]
240pub fn run() -> ExitCode {
241  #[cfg(all(not(doc), not(target_os = "windows")))]
242  compile_error!("This program is supported on Windows only.");
243  let args: Vec<String> = env::args().collect();
244  let Some(program_name) = args.first() else { exit!("Failed to get the program name.") };
245  let Some(input_file) = args.get(1) else {
246    exit!("Usage: {program_name} <input_json_file> [args for .exe]")
247  };
248  let source = match fs::read_to_string(input_file) {
249    Ok(content) => content,
250    Err(err) => exit!("Failed to read `{input_file}`: {err}"),
251  };
252  let mut jsonpiler = Jsonpiler::default();
253  let file = Path::new(input_file);
254  let with_ext = |ext: &str| -> String { file.with_extension(ext).to_string_lossy().to_string() };
255  let asm = with_ext("s");
256  let obj = with_ext("obj");
257  let exe = with_ext("exe");
258  if let Err(err) = jsonpiler.build(source, input_file, &asm) {
259    exit!("Compilation error: {err}");
260  }
261  macro_rules! invoke {
262    ($cmd:literal, $list:expr,$name:literal) => {
263      match Command::new($cmd).args($list).status() {
264        Ok(status) if status.success() => (),
265        Ok(_) => exit!("{} returned a non-zero exit status.", $name),
266        Err(err) => exit!("Failed to invoke {}: {err}", $name),
267      };
268    };
269  }
270  invoke!("as", &[&asm, "-o", &obj], "assembler");
271  #[cfg(not(debug_assertions))]
272  if let Err(err) = fs::remove_file(&asm) {
273    exit!("Failed to delete `{asm}`: {err}")
274  }
275  invoke!(
276    "ld",
277    [&obj, "-o", &exe, "-LC:/Windows/System32", "-luser32", "-lkernel32", "-lucrtbase", "-e_start"],
278    "linker"
279  );
280  if let Err(err) = fs::remove_file(&obj) {
281    exit!("Failed to delete `{obj}`: {err}")
282  }
283  let cwd = match env::current_dir() {
284    Ok(dir) => dir,
285    Err(err) => exit!("Failed to get current directory: {err}"),
286  }
287  .join(&exe);
288  let compiled_program_status = match Command::new(cwd).args(args.get(2..).unwrap_or(&[])).status()
289  {
290    Ok(status) => status,
291    Err(err) => exit!("Failed to execute compiled program: {err}"),
292  };
293  let Some(exit_code) = compiled_program_status.code() else {
294    exit!("Could not get the exit code of the compiled program.")
295  };
296  let Ok(code) = u8::try_from(exit_code.rem_euclid(256)) else {
297    exit!("Internal error: Unexpected error in exit code conversion.")
298  };
299  ExitCode::from(code)
300}