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 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/// Generates an error.
26#[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}
31/// Return `ExitCode`.
32macro_rules! exit {($($arg: tt)*) =>{{eprintln!($($arg)*);return ExitCode::FAILURE;}}}
33/// Macro to include assembly files only once.
34#[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/// Assembly boolean representation.
44#[derive(Clone)]
45struct AsmBool {
46  /// bit offset.
47  bit: u8,
48  /// Name of function.
49  name: Name,
50}
51/// Assembly function representation.
52#[derive(Clone)]
53struct AsmFunc {
54  /// Name of function.
55  name: usize,
56  /// Parameters of function.
57  params: Vec<JsonWithPos>,
58  /// Return type of function.
59  ret: Box<Json>,
60}
61/// Binding.
62#[derive(Clone)]
63enum Bind<T> {
64  /// Literal.
65  Lit(T),
66  /// Global variable.
67  Var(Name),
68}
69/// Built-in function.
70#[derive(Clone)]
71struct Builtin {
72  /// Pointer of function.
73  func: JFunc,
74  /// If it is true, function introduces a new scope.
75  scoped: bool,
76  /// Should arguments already be evaluated.
77  skip_eval: bool,
78}
79/// Contain `T` or `Box<dyn Error>`.
80type ErrOR<T> = Result<T, Box<dyn Error>>;
81/// Information of arguments.
82#[derive(Clone)]
83struct FuncInfo {
84  /// Arguments.
85  args: Vec<JsonWithPos>,
86  /// Function Name.
87  name: String,
88  /// Position of function call.
89  pos: Position,
90}
91/// Type of global variable.
92enum GlobalKind {
93  /// BSS variable.
94  Bss,
95  /// Global float.
96  Float,
97  /// Global function.
98  Func,
99  /// Global integer.
100  Int,
101  /// Global string.
102  Str,
103}
104/// Type of built-in function.
105type JFunc = fn(&mut Jsonpiler, FuncInfo, &mut ScopeInfo) -> ErrOR<Json>;
106/// Represents a JSON object with key-value pairs.
107#[derive(Clone, Default)]
108pub(crate) struct JObject {
109  /// Stores key-value pairs in the order they were inserted.
110  entries: Vec<(String, JsonWithPos)>,
111}
112/// Type and value information.
113#[derive(Clone, Default)]
114enum Json {
115  /// Array.
116  Array(Bind<Vec<JsonWithPos>>),
117  /// Float.
118  Float(Bind<f64>),
119  /// Function.
120  Function(AsmFunc),
121  /// Integer.
122  Int(Bind<i64>),
123  /// Bool.
124  LBool(bool),
125  /// Null.
126  #[default]
127  Null,
128  /// Object.
129  Object(Bind<JObject>),
130  /// String.
131  String(Bind<String>),
132  /// Bool variable.
133  #[expect(dead_code, reason = "todo")]
134  VBool(AsmBool),
135}
136/// Json object.
137#[derive(Clone, Default)]
138struct JsonWithPos {
139  /// Line number of objects in the source code.
140  pos: Position,
141  /// Type and value information.
142  value: Json,
143}
144/// Parser and compiler.
145#[derive(Clone, Default)]
146pub struct Jsonpiler {
147  /// Buffer to store the contents of the bss section of the assembly.
148  bss: Vec<String>,
149  /// Built-in function table.
150  builtin: HashMap<String, Builtin>,
151  /// Buffer to store the contents of the data section of the assembly.
152  data: Vec<String>,
153  /// Seed to generate names.
154  global_seed: usize,
155  /// Flag to avoid including the same file twice.
156  include_flag: HashSet<String>,
157  /// Information to be used during parsing.
158  pos: Position,
159  /// Source code.
160  source: Vec<u8>,
161  /// Cache of the string.
162  str_cache: HashMap<String, usize>,
163  /// Buffer to store the contents of the text section of the assembly.
164  text: Vec<String>,
165  /// Variable table.
166  vars: Vec<HashMap<String, Json>>,
167}
168/// Variable name.
169#[derive(Clone)]
170struct Name {
171  /// Variable seed.
172  seed: usize,
173  /// Variable type.
174  var: VarKind,
175}
176/// line and pos in source code.
177#[derive(Clone, Default)]
178struct Position {
179  /// Line number of the part being parsed.
180  line: usize,
181  /// Byte offset of the part being parsed.
182  offset: usize,
183  /// Size of the part being parsed.
184  size: usize,
185}
186/// Information of Scope.
187#[derive(Clone, Default)]
188struct ScopeInfo {
189  /// Size of arguments.
190  args_slots: usize,
191  /// Body of function.
192  body: Vec<String>,
193  /// Bit-level allocation for bools.
194  bool_map: BTreeMap<usize, u8>,
195  /// Free memory list.
196  free_map: BTreeMap<usize, usize>,
197  /// Registers used.
198  reg_used: HashSet<String>,
199  /// Scope align.
200  scope_align: usize,
201  /// Stack size.
202  stack_size: usize,
203}
204/// Variable.
205#[derive(Clone, Copy, PartialEq)]
206enum VarKind {
207  /// Global variable.
208  Global,
209  /// Local variable.
210  Local,
211  /// Temporary local variable.
212  Tmp,
213}
214/// Safe addition.
215fn add(op1: usize, op2: usize) -> ErrOR<usize> {
216  op1.checked_add(op2).ok_or("InternalError: Overflow".into())
217}
218/// Compiles and executes a JSON-based program using the Jsonpiler.
219/// This function performs the following steps:
220/// 1. Parses the first CLI argument as the input JSON file path.
221/// 2. Reads the file content into a string.
222/// 3. Parses the string into a `Json` structure.
223/// 4. Compiles the structure into assembly code.
224/// 5. Assembles it into an `.obj` file.
225/// 6. Links it into an `.exe`.
226/// 7. Executes the resulting binary.
227/// 8. Returns its exit code.
228/// # Panics
229/// This function will panic if:
230/// - The platform is not Windows.
231/// - CLI arguments are invalid.
232/// - File reading, parsing, compilation, assembling, linking, or execution fails.
233/// - The working directory or executable filename is invalid.
234/// # Requirements
235/// - `as` and `ld` must be available in the system PATH.
236/// - On failure, exits with code 1 using `error_exit`.
237/// # Example
238/// ```sh
239/// ./jsonpiler test.json
240/// ```
241/// # Platform
242/// Windows only.
243#[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}