clue_core/
preprocessor.rs

1//! The preprocessor is the first step in the compilation process.
2//! It is responsible for removing comments and expanding macros and directives.
3//!
4//! It exposes three functions: [`preprocess_code`], [`preprocess_codes`] and [`preprocess_variables`]
5
6use crate::{
7	check,
8	code::{Code, CodeChar},
9	env::Options,
10	format_clue,
11};
12use ahash::AHashMap;
13use std::{
14	cmp,
15	collections::VecDeque,
16	env,
17	fs,
18	iter::{Peekable, Rev},
19	path::PathBuf,
20	str::{self, Split},
21	u8::{self, MAX},
22};
23use utf8_decode::decode;
24
25#[cfg(feature = "lsp")]
26use serde_json::json;
27
28macro_rules! pp_if {
29	($code:ident, $ifname:ident, $prev:ident) => {{
30		let check = $code.$ifname(b'{')?;
31		$code.keep_block($prev && check)?;
32	}};
33}
34
35/// A HashMap of preprocessor variables.
36pub type PPVars = AHashMap<Code, PPVar>;
37/// A list of code segments and its size.
38pub type PPCode = (VecDeque<(Code, bool)>, usize);
39
40#[derive(Debug, Clone)]
41/// A preprocessor variable or macro.
42pub enum PPVar {
43	/// A simple variable.
44	Simple(Code),
45
46	/// A variable that has to be processed before expansion.
47	ToProcess(Code),
48
49	/// A macro.
50	Macro {
51		/// The code of the macro.
52		code: PPCode,
53
54		/// The arguments of the macro.
55		args: Vec<Code>,
56
57		/// The preprocessor variables of the macro.
58		ppvars: PPVars,
59
60		/// Whether the macro is variadic.
61		vararg: bool,
62	},
63
64	/// Variadic arguments variable in a macro.
65	VarArgs(PPCode),
66}
67
68fn error(msg: impl Into<String>, line: usize, column: usize, filename: &String) -> String {
69	eprintln!("Error in {filename}:{line}:{column}!");
70	msg.into()
71}
72
73fn expected(expected: &str, got: &str, line: usize, column: usize, filename: &String) -> String {
74	error(
75		format_clue!("Expected '", expected, "', got '", got, "'"),
76		line,
77		column,
78		filename,
79	)
80}
81
82fn expected_before(
83	expected: &str,
84	before: &str,
85	line: usize,
86	column: usize,
87	filename: &String,
88) -> String {
89	error(
90		format_clue!("Expected '", expected, "' before '", before, "'"),
91		line,
92		column,
93		filename,
94	)
95}
96
97#[derive(PartialEq, Eq, PartialOrd, Ord)]
98enum CommentState {
99	String,
100	None,
101	Single,
102	Multi,
103	Ended,
104}
105
106struct CodeFile<'a> {
107	options: &'a Options,
108	code: &'a mut [u8],
109	comment: CommentState,
110	checked: usize,
111	read: usize,
112	peeked: Option<CodeChar>,
113	line: usize,
114	column: usize,
115	filename: &'a String,
116	last_if: bool,
117	cscope: u8,
118	ends: Vec<u8>,
119}
120
121impl<'a> CodeFile<'a> {
122	fn new(
123		code: &'a mut [u8],
124		line: usize,
125		filename: &'a String,
126		cscope: u8,
127		options: &'a Options,
128	) -> Self {
129		Self {
130			options,
131			code,
132			comment: CommentState::None,
133			checked: 0,
134			read: 0,
135			peeked: None,
136			line,
137			column: 1,
138			filename,
139			last_if: true,
140			cscope,
141			ends: Vec::new(),
142		}
143	}
144
145	fn is_ascii(&mut self, c: Option<CodeChar>) -> Result<Option<CodeChar>, String> {
146		match c {
147			None => Ok(None),
148			Some(c) if c.0.is_ascii() => Ok(Some(c)),
149			Some((_, line, column)) => {
150				let c = check!(decode(
151					&mut self.code[self.read - 1..cmp::min(self.read + 3, self.code.len())].iter().copied()
152				)
153				.unwrap());
154				Err(error(
155					format!("Invalid character '{c}'"),
156					line,
157					column,
158					self.filename,
159				))
160			}
161		}
162	}
163
164	fn skip_whitespace(&mut self) {
165		while let Some((c, ..)) = self.peek_char_unchecked() {
166			if c.is_ascii_whitespace() {
167				self.read_char_unchecked();
168			} else {
169				break;
170			}
171		}
172	}
173
174	fn read_char_unchecked(&mut self) -> Option<CodeChar> {
175		if self.peeked.is_some() {
176			let peeked = self.peeked;
177			self.peeked = None;
178			peeked
179		} else {
180			let next = self.code.get(self.read + 1).copied();
181			let current = self.code.get_mut(self.read);
182			if let Some(current) = current {
183				let c = *current;
184				self.read += 1;
185				let line = self.line;
186				let column = self.column;
187				self.column += 1;
188				if self.comment > CommentState::None && *current != b'\n' {
189					*current = b' ';
190				}
191				match c {
192					b'\n' => {
193						self.line += 1;
194						self.column = 1;
195						if self.comment == CommentState::Single {
196							self.comment = CommentState::None;
197						}
198					}
199					b'/' if self.comment == CommentState::None => {
200						if let Some(next) = next {
201							self.comment = match next {
202								b'/' => {
203									*current = b' ';
204									CommentState::Single
205								}
206								b'*' => {
207									*current = b' ';
208									CommentState::Multi
209								}
210								_ => CommentState::None,
211							}
212						}
213					}
214					b'*' if self.comment == CommentState::Multi => {
215						if let Some(next) = next {
216							if next == b'/' {
217								self.comment = CommentState::Ended;
218							}
219						}
220					}
221					_ if self.comment == CommentState::Ended => {
222						self.comment = CommentState::None;
223					}
224					_ => {}
225				}
226				Some((*current, line, column))
227			} else {
228				None
229			}
230		}
231	}
232
233	fn read_char(&mut self) -> Result<Option<CodeChar>, String> {
234		let c = self.read_char_unchecked();
235		self.is_ascii(c)
236	}
237
238	fn peek_char_unchecked(&mut self) -> Option<CodeChar> {
239		if self.peeked.is_none() {
240			self.peeked = self.read_char_unchecked();
241		}
242		self.peeked
243	}
244
245	fn peek_char(&mut self) -> Result<Option<CodeChar>, String> {
246		let c = self.peek_char_unchecked();
247		self.is_ascii(c)
248	}
249
250	fn assert_char(&mut self, wanted_c: u8) -> Result<(), String> {
251		match self.read_char()? {
252			None => {
253				return Err(expected_before(
254					&String::from_utf8_lossy(&[wanted_c]),
255					"<end>",
256					self.line,
257					self.column,
258					self.filename,
259				))
260			}
261			Some((c, line, column)) if c != wanted_c => {
262				return Err(expected(
263					&String::from_utf8_lossy(&[wanted_c]),
264					&String::from_utf8_lossy(&[c]),
265					line,
266					column,
267					self.filename,
268				))
269			}
270			_ => Ok(()),
271		}
272	}
273
274	fn assert_reach(&mut self, wanted_c: u8) -> Result<(), String> {
275		self.skip_whitespace();
276		self.assert_char(wanted_c)
277	}
278
279	fn read(
280		&mut self,
281		mut get: impl FnMut(&mut Self) -> Result<Option<CodeChar>, String>,
282		mut check: impl FnMut(&mut Self, CodeChar) -> bool,
283	) -> Result<Code, String> {
284		let mut code = Code::new();
285		while let Some(c) = get(self)? {
286			if check(self, c) {
287				break;
288			}
289			code.push(c)
290		}
291		Ok(code)
292	}
293
294	fn read_line(&mut self) -> String {
295		self.read(
296			|code| Ok(code.read_char_unchecked()),
297			|_, (c, ..)| c == b'\n',
298		)
299		.unwrap()
300		.to_string()
301	}
302
303	fn read_identifier(&mut self) -> Result<Code, String> {
304		self.read(Self::peek_char, |code, (c, ..)| {
305			if c.is_ascii_alphanumeric() || c == b'_' {
306				code.read_char_unchecked().unwrap();
307				false
308			} else {
309				true
310			}
311		})
312	}
313
314	fn read_string(&mut self, c: CodeChar) -> Result<Code, String> {
315		self.comment = CommentState::String;
316		let mut skip_next = false;
317		self.read(
318			|code| {
319				let stringc = code.read_char_unchecked();
320				if stringc.is_none() {
321					Err(error("Unterminated string", c.1, c.2, self.filename))
322				} else {
323					Ok(stringc)
324				}
325			},
326			|code, (stringc, ..)| {
327				if stringc == b'\n' {
328					false
329				} else if skip_next {
330					skip_next = false;
331					false
332				} else if stringc == c.0 {
333					code.comment = CommentState::None;
334					true
335				} else {
336					if stringc == b'\\' {
337						skip_next = true;
338					}
339					false
340				}
341			},
342		)
343	}
344
345	fn read_until_with(
346		&mut self,
347		end: u8,
348		f: impl FnMut(&mut Self) -> Result<Option<CodeChar>, String>,
349	) -> Result<Option<Code>, String> {
350		let mut reached = false;
351		let result = self.read(f, |_, (c, ..)| {
352			if c == end {
353				reached = true;
354				true
355			} else {
356				false
357			}
358		})?;
359		Ok(reached.then_some(result))
360	}
361
362	fn read_until(&mut self, end: u8) -> Result<Code, String> {
363		self.read_until_with(end, Self::read_char)?.ok_or_else(|| {
364			expected_before(
365				&(end as char).to_string(),
366				"<end>",
367				self.line,
368				self.column,
369				self.filename,
370			)
371		})
372	}
373
374	fn read_macro_args(&mut self) -> Result<Code, String> {
375		let mut args = Code::new();
376		args.push(self.read_char_unchecked().unwrap());
377		while let Some(c) = self.peek_char()? {
378			match c.0 {
379				b'(' => args.append(self.read_macro_args()?),
380				b')' => {
381					args.push(self.read_char_unchecked().unwrap());
382					return Ok(args);
383				}
384				b'\'' | b'"' | b'`' => {
385					args.push(self.read_char_unchecked().unwrap());
386					args.append(self.read_string(c)?);
387					args.push(c);
388				}
389				_ => args.push(self.read_char_unchecked().unwrap()),
390			}
391		}
392		Err(expected_before(
393			")",
394			"<end>",
395			self.line,
396			self.column,
397			self.filename,
398		))
399	}
400
401	fn read_macro_block(&mut self) -> Result<(PPCode, PPVars), String> {
402		let line = self.line;
403		let len = self.code.len();
404		let block = &mut self.code[self.read..len];
405		let (block, ppvars, line, read) =
406			preprocess_code(block, line, true, self.filename, &Options::default())?;
407		self.line = line;
408		self.read += read;
409		Ok((block, ppvars))
410	}
411
412	fn skip_block(&mut self) -> Result<(), String> {
413		while let Some(c) = self.read_char()? {
414			match c.0 {
415				b'{' => self.skip_block()?,
416				b'}' => return Ok(()),
417				b'\'' | b'"' | b'`' => {
418					self.read_string(c)?;
419				}
420				_ => {}
421			}
422		}
423		Err(expected_before(
424			"}",
425			"<end>",
426			self.line,
427			self.column,
428			self.filename,
429		))
430	}
431
432	fn keep_block(&mut self, to_keep: bool) -> Result<(), String> {
433		self.last_if = to_keep;
434		if to_keep {
435			self.ends.push(self.cscope);
436			self.cscope += 1;
437			Ok(())
438		} else {
439			self.skip_block()
440		}
441	}
442
443	fn ifos(&mut self, end: u8) -> Result<bool, String> {
444		let checked_os = self.read_until(end)?.trim();
445		Ok(checked_os == self.options.env_targetos)
446	}
447
448	fn iflua(&mut self, end: u8) -> Result<bool, String> {
449		use crate::env::LuaVersion::*;
450		let checked_lua_version = self.read_until(end)?.trim();
451		let Some(target) = self.options.env_target else {
452			return Ok(false);
453		};
454		Ok(
455			match checked_lua_version.to_string().to_lowercase().as_str() {
456				"luajit" | "jit" => target == LuaJIT,
457				"lua54" | "lua5.4" | "lua 54" | "lua 5.4" | "54" | "5.4" => target == Lua54,
458				"blua" => target == BLUA,
459				_ => false,
460			},
461		)
462	}
463
464	fn ifdef(&mut self, end: u8) -> Result<bool, String> {
465		let to_check = self.read_until(end)?.trim();
466		Ok(env::var_os(to_check.to_string()).is_some())
467	}
468
469	fn ifndef(&mut self, end: u8) -> Result<bool, String> {
470		self.ifdef(end).map(|ok| !ok)
471	}
472
473	fn ifcmp(&mut self, end: u8) -> Result<bool, String> {
474		let Some(to_compare1) = env::var_os(self.read_identifier()?.to_string()) else {
475			self.read_until(end)?;
476			return Ok(false)
477		};
478		self.skip_whitespace();
479		let comparison = [
480			self.read_char_unchecked()
481				.ok_or_else(|| {
482					expected("==' or '!=", "<end>", self.line, self.column, self.filename)
483				})?
484				.0,
485			self.read_char_unchecked()
486				.ok_or_else(|| {
487					expected("==' or '!=", "<end>", self.line, self.column, self.filename)
488				})?
489				.0,
490		];
491		let to_compare2 = self.read_until(end)?.trim();
492		Ok(match &comparison {
493			b"==" => to_compare2 == to_compare1,
494			b"!=" => to_compare2 != to_compare1,
495			_ => {
496				return Err(expected(
497					"==' or '!=",
498					&String::from_utf8_lossy(&comparison),
499					self.line,
500					self.column,
501					self.filename,
502				))
503			}
504		})
505	}
506
507	fn bool_op(&mut self, b: bool) -> Result<bool, String> {
508		let mut result = !b;
509		loop {
510			if self.r#if()? == b {
511				result = b;
512			}
513			self.skip_whitespace();
514			if let Some((b')', ..)) = self.peek_char_unchecked() {
515				self.read_char_unchecked();
516				break Ok(result);
517			}
518			self.assert_char(b',')?;
519			self.skip_whitespace();
520		}
521	}
522
523	fn r#if(&mut self) -> Result<bool, String> {
524		let check = {
525			let function = self.read_identifier()?.to_string();
526			self.assert_char(b'(')?;
527			if function.is_empty() {
528				return Err(expected_before(
529					"<name>",
530					"(",
531					self.line,
532					self.column,
533					self.filename,
534				));
535			}
536			self.skip_whitespace();
537			match function.as_str() {
538				"all" => self.bool_op(false)?,
539				"any" => self.bool_op(true)?,
540				"os" => self.ifos(b')')?,
541				"lua" => self.iflua(b')')?,
542				"def" => self.ifdef(b')')?,
543				"ndef" => self.ifndef(b')')?,
544				"cmp" => self.ifcmp(b')')?,
545				"not" => {
546					let result = self.r#if()?;
547					self.assert_char(b')')?;
548					!result
549				}
550				_ => {
551					return Err(error(
552						format!("Unknown function '{function}'"),
553						self.line,
554						self.column,
555						self.filename,
556					))
557				}
558			}
559		};
560		self.skip_whitespace();
561		Ok(check)
562	}
563
564	fn get_version_number(&self, version: &mut Split<char>, default : &str) -> Result<u8, String> {
565		let num = match version.next() {
566			None => {
567				return Err(error(
568					"Incomplete version (must be 'X.Y.Z')",
569					self.line,
570					self.column,
571					self.filename,
572				))
573			}
574			Some("*") => default,
575			Some(num) => num,
576		};
577		match num.parse::<u8>() {
578			Ok(num) => Ok(num),
579			Err(_) => Err(error(
580				"Invalid version (must be 'X.Y.Z')",
581				self.line,
582				self.column,
583				self.filename,
584			)),
585		}
586	}
587}
588
589/// Reads a file and gives back the a list of preprocessed code blocks and the variables
590///
591/// # Errors
592/// If the file cannot be read or the code cannot be preprocessed it will return an [`Err`] with the error message
593///
594/// # Examples
595/// ```
596/// use clue_core::{env::Options, preprocessor::read_file};
597///
598/// fn main() -> Result<(), String> {
599///     let options = Options::default();
600///     let (code, vars) = read_file(
601///         "../examples/macro.clue",
602///         &String::from("macro.clue"),
603///         &options,
604///     )?;
605///
606///     Ok(())
607/// }
608/// ```
609pub fn read_file(
610	path: impl Into<PathBuf>,
611	filename: &String,
612	options: &Options,
613) -> Result<(PPCode, PPVars), String> {
614	let result = preprocess_code(&mut check!(fs::read(path.into())), 1, false, filename, options)?;
615	Ok((result.0, result.1))
616}
617
618/// Preprocesses code and gives back the a list of preprocessed code blocks and the variable
619///
620/// # Errors
621/// If the code cannot be preprocessed it will return an [`Err`] with the error message
622///
623/// # Examples
624/// ```
625/// use clue_core::{preprocessor::preprocess_code, env::Options};
626///
627/// fn main() -> Result<(), String> {
628///   let options = Options::default();
629///   let mut code = include_str!("../../examples/macro.clue").to_owned();
630///
631///   let (code, vars, ..) = preprocess_code(&mut code.into_bytes(), 1, false, &String::from("macro.clue"), &options)?;
632///
633///   Ok(())
634/// }
635#[allow(clippy::blocks_in_conditions)]
636pub fn preprocess_code(
637	code: &mut [u8],
638	line: usize,
639	is_block: bool,
640	filename: &String,
641	options: &Options,
642) -> Result<(PPCode, PPVars, usize, usize), String> {
643	let mut output_dir: Option<PathBuf> = None;
644	let mut finalcode = VecDeque::new();
645	let mut currentcode = Code::with_capacity(code.len());
646	let mut size = 0;
647	let mut code = CodeFile::new(code, line, filename, is_block as u8, options);
648	let mut variables = PPVars::new();
649	let mut pseudos: Option<VecDeque<Code>> = None;
650	let mut bitwise = false;
651	while let Some(c) = code.read_char()? {
652		if match c.0 {
653			b'@' => {
654				let directive_name = code.read_identifier()?.to_string();
655				code.skip_whitespace();
656				let else_if = directive_name.starts_with("else_if");
657				let skip = else_if && code.last_if;
658				let (directive, prev) = if else_if {
659					(
660						directive_name
661							.strip_prefix("else_")
662							.expect("else_if should start with else_"),
663						!code.last_if,
664					)
665				} else {
666					(directive_name.as_str(), true)
667				};
668				match directive {
669					"ifos" => pp_if!(code, ifos, prev),
670					"iflua" => pp_if!(code, iflua, prev),
671					"ifdef" => pp_if!(code, ifdef, prev),
672					"ifndef" => pp_if!(code, ifndef, prev),
673					"ifcmp" => pp_if!(code, ifcmp, prev),
674					"if" => {
675						let check = code.r#if()?;
676						code.assert_char(b'{')?;
677						code.keep_block(prev && check)?;
678					}
679					"else" => {
680						code.assert_reach(b'{')?;
681						code.keep_block(!code.last_if)?;
682					}
683					"import" => {
684						if output_dir.is_none() {
685							output_dir = Some(match options.env_outputname.as_ref() {
686								Some(output_dir) => output_dir
687									.parent()
688									.map_or_else(
689										|| output_dir.to_path_buf(),
690										|output_dir| output_dir.to_path_buf()
691									),
692								None => check!(env::current_dir())
693							})
694						}
695						let output_dir = output_dir.as_ref().unwrap();
696						let str_start = code.read_char_unchecked();
697						let module = match str_start {
698							Some((b'\'' | b'"' | b'`', ..)) => {
699								code.read_string(str_start.expect("character should not be None"))?
700							}
701							_ => {
702								return Err(expected_before("<path>", "<end>", c.1, c.2, filename))
703							}
704						}.to_string();
705						let name = code.read_line();
706						let name = name.trim();
707						let mut dirs = module.split('.');
708						let mut module_path = output_dir.join(dirs.next().unwrap());
709						for dir in dirs {
710							module_path.push(dir);
711						}
712						module_path.set_extension("lua");
713						let function = if module_path.exists() {
714							"require"
715						} else {
716							"import"
717						};
718						let (name, start) = match name.strip_prefix("=>") {
719							Some(name) =>{
720								let mut trimmed_name = name.trim_start().to_owned();
721								if trimmed_name.is_empty() {
722									return Err(expected(
723										"<name>",
724										"<empty>",
725										code.line,
726										code.column,
727										filename
728									))
729								}
730								if trimmed_name.contains(|c| matches!(c, '$' | '@')) {
731									let (codes, new_variables, ..) = preprocess_code(
732										unsafe { trimmed_name.as_bytes_mut() },
733										code.line,
734										false,
735										filename,
736										options
737									)?;
738									for (key, value) in new_variables {
739										variables.insert(key, value);
740									}
741									trimmed_name = preprocess_codes(
742										0,
743										codes,
744										&variables,
745										filename
746									)?.to_string();
747								}
748								let start = if trimmed_name.contains(|c| matches!(c, '.' | '[')) {
749									""
750								} else {
751									"local "
752								};
753								(trimmed_name, start)
754							},
755							None => (match module.rsplit_once('.') {
756								Some((_, name)) => name,
757								None => &module,
758							}.trim().to_string(), "local ")
759						};
760						if name.is_empty() {
761							return Err(expected(
762								"<file name>",
763								"<empty>",
764								c.1,
765								c.2,
766								filename
767							))
768						}
769						currentcode.append(Code::from((
770							format_clue!(start, name, " = ", function, "(\"", module, "\")"),
771							c.1,
772							c.2,
773						)));
774					}
775					"version" => {
776						let full_wanted_version = code.read_line();
777						let full_wanted_version = full_wanted_version.trim();
778						#[allow(clippy::type_complexity)]
779						let (mut wanted_version, check): (&str, &dyn Fn(&u8, &u8) -> bool) =
780							match full_wanted_version.strip_prefix('=') {
781								Some(wanted_version) => (wanted_version, &u8::ne),
782								None => (full_wanted_version, &u8::lt),
783							};
784						if let Some(v) = full_wanted_version.strip_prefix(">=") {
785							wanted_version = v;
786							println!(
787								"Note: \"@version directives should no longer start with '>='\""
788							);
789						}
790						let wanted_version_iter = &mut wanted_version.split('.');
791						const CURRENT_MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
792						const CURRENT_MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
793						const CURRENT_PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
794						let wanted_major = code.get_version_number(wanted_version_iter, CURRENT_MAJOR)?;
795						let wanted_minor = code.get_version_number(wanted_version_iter, CURRENT_MINOR)?;
796						let wanted_patch = code.get_version_number(wanted_version_iter, CURRENT_PATCH)?;
797						let current_major: u8 = CURRENT_MAJOR.parse().unwrap();
798						let current_minor: u8 = CURRENT_MINOR.parse().unwrap();
799						let current_patch: u8 = CURRENT_PATCH.parse().unwrap();
800						if check(&current_major, &wanted_major)
801						|| check(&current_minor, &wanted_minor)
802						|| check(&current_patch, &wanted_patch) {
803							return Err(error(
804								if full_wanted_version.starts_with('=') {
805									format_clue!(
806										"This code is only compatible with version '",
807										wanted_version,
808										"'"
809									)
810								} else {
811									format_clue!(
812										"This code is only compatible with versions not older than '",
813										wanted_version,
814										"'"
815									)
816								},
817								c.1,
818								c.2,
819								filename
820							));
821						}
822					}
823					"define" => {
824						let name = code.read_identifier()?;
825						let mut has_values = false;
826						let mut string_char = 0u8; //basically recreating read_string...
827						let mut skip_next = false; //refactor in 4.0?
828						let value = code.read(
829							|code| Ok(code.read_char_unchecked()),
830							|code, (c, ..)| {
831								match c {
832									b'$' => {
833										has_values = true;
834										false
835									}
836									_ if skip_next && code.comment == CommentState::String => {
837										skip_next = false;
838										false
839									}
840									b'\n' => code.comment != CommentState::String,
841									_ if c == string_char && code.comment == CommentState::String => {
842										code.comment = CommentState::None;
843										false
844									}
845									b'\'' | b'"' | b'`' if code.comment != CommentState::String => {
846										code.comment = CommentState::String;
847										string_char = c;
848										false
849									}
850									b'\\' if code.comment == CommentState::String => {
851										skip_next = true;
852										false
853									}
854									_ => false
855								}
856							},
857						)?;
858						if code.comment == CommentState::String {
859							return Err(
860								error("Unterminated string", code.line, code.column, code.filename)
861							);
862						}
863						let value = value.trim();
864						variables.insert(
865							name,
866							if has_values {
867								PPVar::ToProcess(value)
868							} else {
869								PPVar::Simple(value)
870							},
871						);
872					}
873					"macro" => {
874						let name = code.read_identifier()?;
875						code.assert_reach(b'(')?;
876						let (vararg, args) = {
877							let mut args = Vec::new();
878							loop {
879								code.skip_whitespace();
880								if let Some((b'.', line, column)) = code.peek_char_unchecked() {
881									if code.read(CodeFile::peek_char, |code, (c, ..)| {
882										if c == b'.' {
883											code.read_char_unchecked();
884											false
885										} else {
886											true
887										}
888									})? == "..."
889									{
890										code.skip_whitespace();
891										code.assert_char(b')')?;
892										break (true, args);
893									} else {
894										return Err(expected(",", ".", line, column, filename));
895									}
896								}
897								let arg = code.read_identifier()?;
898								code.skip_whitespace();
899								if arg.is_empty() {
900									if args.is_empty() {
901										code.assert_char(b')')?;
902										break (false, args);
903									}
904									let (got, line, column) = match code.read_char_unchecked() {
905										Some((c, line, column)) => {
906											((c as char).to_string(), line, column)
907										}
908										None => (String::from("<end>"), code.line, code.column),
909									};
910									return Err(expected("<name>", &got, line, column, filename));
911								}
912								args.push(arg);
913								if let Some((b')', ..)) = code.peek_char_unchecked() {
914									code.read_char_unchecked();
915									break (false, args);
916								}
917								code.assert_char(b',')?;
918							}
919						};
920						code.assert_reach(b'{')?;
921						let (code, ppvars) = code.read_macro_block()?;
922						variables.insert(
923							name,
924							PPVar::Macro {
925								code,
926								args,
927								ppvars,
928								vararg,
929							},
930						);
931					}
932					"error" => return Err(error(code.read_line(), c.1, c.2, filename)),
933					"print" => println!("{}", code.read_line()),
934					_ => {
935						return Err(error(
936							format!("Unknown directive '{directive_name}'"),
937							c.1,
938							c.2,
939							filename,
940						))
941					}
942				}
943				if skip {
944					code.last_if = true;
945				}
946				false
947			}
948			b'$' if is_block && matches!(code.peek_char_unchecked(), Some((b'{', ..))) => {
949				size += currentcode.len() + 8;
950				finalcode.push_back((currentcode, false));
951				let name = format_clue!("_vararg", variables.len().to_string());
952				finalcode.push_back((Code::from((format_clue!("$", name), c.1, c.2)), true));
953				code.read_char_unchecked();
954				let (vararg_code, ppvars) = code.read_macro_block()?;
955				variables.extend(ppvars);
956				variables.insert(Code::from((name, c.1, c.2)), PPVar::VarArgs(vararg_code));
957				currentcode = Code::with_capacity(code.code.len() - code.read);
958				false
959			}
960			b'$' => {
961				let mut name = code.read_identifier()?;
962				if name.len() <= 1 && matches!(name.last(), Some((b'1'..=b'9', ..)) | None) {
963					let n = match name.pop() {
964						Some((c, ..)) => (c - b'0') as usize,
965						None => 1,
966					};
967					if pseudos.is_none() {
968						let tocheck = code.code[code.checked..code.read].iter().rev().peekable();
969						pseudos = Some(read_pseudos(tocheck, c.1, c.2));
970						code.checked = code.read;
971					}
972					match pseudos.as_ref().unwrap().get(n - 1) {
973						Some(name) => currentcode.append(name.clone()),
974						None => currentcode.append(name.clone()),
975					}
976				} else {
977					size += currentcode.len();
978					finalcode.push_back((currentcode, false));
979					name.push_start(c);
980					if {
981						if matches!(code.peek_char_unchecked(), Some((b'!', ..))) {
982							name.push(code.read_char_unchecked().unwrap());
983							matches!(code.peek_char_unchecked(), Some((b'(', ..)))
984						} else {
985							false
986						}
987					} {
988						name.append(code.read_macro_args()?)
989					}
990					size += name.len();
991					finalcode.push_back((name, true));
992					currentcode = Code::with_capacity(code.code.len() - code.read);
993				}
994				false
995			}
996			b'\'' | b'"' | b'`' => {
997				currentcode.push(c);
998				currentcode.append(code.read_string(c)?);
999				true
1000			}
1001			b'&' | b'|' => {
1002				if code.peek_char_unchecked().unwrap_or((b'\0', 0, 0)).0 == c.0 {
1003					currentcode.push(code.read_char_unchecked().unwrap());
1004				} else {
1005					bitwise = true;
1006				}
1007				true
1008			}
1009			b'^' => {
1010				let nextc = code.peek_char_unchecked();
1011				if nextc.is_some() && nextc.unwrap().0 == b'^' {
1012					bitwise = true;
1013					currentcode.push(code.read_char_unchecked().unwrap());
1014				}
1015				true
1016			}
1017			b'~' => {
1018				bitwise = true;
1019				true
1020			}
1021			b'>' | b'<' => {
1022				currentcode.push(c);
1023				if let Some((nc, ..)) = code.peek_char_unchecked() {
1024					match nc {
1025						b'=' => {
1026							currentcode.push(code.read_char_unchecked().unwrap());
1027						}
1028						nc if nc == c.0 => {
1029							currentcode.push(code.read_char_unchecked().unwrap());
1030							bitwise = true;
1031						}
1032						_ => {}
1033					}
1034				}
1035				false
1036			}
1037			b'=' => {
1038				currentcode.push(c);
1039				if let Some((nc, ..)) = code.peek_char_unchecked() {
1040					if matches!(nc, b'=' | b'>') {
1041						currentcode.push(code.read_char_unchecked().unwrap());
1042					} else {
1043						pseudos = None;
1044					}
1045				}
1046				false
1047			}
1048			b'!' => {
1049				currentcode.push(c);
1050				if let Some((nc, ..)) = code.peek_char_unchecked() {
1051					if nc == b'=' {
1052						currentcode.push(code.read_char_unchecked().unwrap());
1053					}
1054				}
1055				false
1056			}
1057			b'{' if code.cscope > 0 || is_block => {
1058				code.cscope += 1;
1059				true
1060			}
1061			b'}' if code.cscope > 0 => {
1062				code.cscope -= 1;
1063				if is_block && code.cscope == 0 {
1064					break;
1065				}
1066				if let Some(end) = code.ends.last() {
1067					if code.cscope != *end {
1068						true
1069					} else {
1070						code.ends.pop().unwrap();
1071						false
1072					}
1073				} else {
1074					true
1075				}
1076			}
1077			_ => true,
1078		} {
1079			currentcode.push(c)
1080		}
1081	}
1082	if code.cscope > 0 {
1083		return Err(expected_before(
1084			"}",
1085			"<end>",
1086			code.line,
1087			code.column,
1088			filename,
1089		));
1090	}
1091	if !currentcode.is_empty() {
1092		size += currentcode.len();
1093		finalcode.push_back((currentcode, false))
1094	}
1095	if bitwise && options.env_jitbit.is_some() {
1096		let bit = options.env_jitbit.as_ref().unwrap();
1097		let mut loader = Code::from((format_clue!("local ", bit, " = require(\"", bit, "\");"), 1, 1));
1098		let first = finalcode.pop_front().unwrap();
1099		loader.append(first.0);
1100		finalcode.push_front((loader, first.1));
1101	}
1102	#[cfg(feature = "lsp")]
1103	if options.env_symbols {
1104		use PPVar::*;
1105		use std::collections::HashMap;
1106		let mut str_variables = HashMap::new();
1107		for (name, variable) in &variables {
1108			let (name, value) = match variable {
1109				Simple(value) | ToProcess(value) => (format_clue!('$', name), value.to_string()),
1110				_ => unimplemented!()
1111			};
1112			str_variables.insert(name, value);
1113		}
1114		println!("{}", json!({
1115			"type": "PPVars",
1116			"value": str_variables
1117		}));
1118	}
1119	Ok(((finalcode, size), variables, code.line, code.read))
1120}
1121
1122fn skip_whitespace_backwards(code: &mut Peekable<Rev<std::slice::Iter<u8>>>) {
1123	while let Some(c) = code.peek() {
1124		if c.is_ascii_whitespace() {
1125			code.next();
1126		} else {
1127			break;
1128		}
1129	}
1130}
1131
1132fn read_pseudos(
1133	mut code: Peekable<Rev<std::slice::Iter<u8>>>,
1134	line: usize,
1135	column: usize,
1136) -> VecDeque<Code> {
1137	let mut newpseudos = VecDeque::new();
1138	while {
1139		let Some(c) = code.next() else {
1140			return newpseudos;
1141		};
1142		match c {
1143			b'=' => {
1144				let Some(c) = code.next() else {
1145					return newpseudos;
1146				};
1147				match c {
1148					b'!' | b'=' | b'>' | b'<' => true,
1149					b'.' | b'&' | b'|' | b'?' => code.next().unwrap_or(&b'\0') != c,
1150					_ => false,
1151				}
1152			}
1153			b'>' if matches!(code.peek(), Some(b'=')) => {
1154				code.next().unwrap();
1155				true
1156			}
1157			b'\'' | b'"' | b'`' => {
1158				while {
1159					let Some(nextc) = code.next() else {
1160						return newpseudos;
1161					};
1162					if nextc == c {
1163						matches!(code.peek(), Some(b'\\'))
1164					} else {
1165						true
1166					}
1167				} {}
1168				true
1169			}
1170			_ => true,
1171		}
1172	} {}
1173	skip_whitespace_backwards(&mut code);
1174	while {
1175		let mut name = Code::new();
1176		let mut qscope = 0u8;
1177		let mut in_string = false;
1178		while {
1179			if let Some(c) = code.peek() {
1180				match c {
1181					b'\'' | b'"' | b'`' => {
1182						name.push_start((*code.next().unwrap(), line, column));
1183						if !matches!(code.peek(), Some(b'\\')) {
1184							in_string = !in_string;
1185						}
1186						true
1187					}
1188					_ if in_string => true,
1189					b'[' => {
1190						qscope = qscope.saturating_sub(1);
1191						true
1192					}
1193					_ if qscope > 0 => true,
1194					b']' => {
1195						qscope += 1;
1196						true
1197					}
1198					b'_' | b'.' | b':' => true,
1199					_ => c.is_ascii_alphanumeric(),
1200				}
1201			} else {
1202				false
1203			}
1204		} {
1205			name.push_start((*code.next().unwrap(), line, column))
1206		}
1207		newpseudos.push_front(name);
1208		skip_whitespace_backwards(&mut code);
1209		if let Some(c) = code.next() {
1210			*c == b','
1211		} else {
1212			false
1213		}
1214	} {}
1215	newpseudos
1216}
1217
1218/// Preprocesses the list code segments, expands the variables and returns the final code.
1219/// Take a stacklevel, a list of code segments, the variables, and a filename.
1220///
1221/// # Errors
1222/// Returns an error if a variable is not found.
1223///
1224/// # Examples
1225/// ```
1226/// use clue_core::{env::Options, preprocessor::*};
1227///
1228/// fn main() -> Result<(), String> {
1229///     let options = Options::default();
1230///     let filename = String::from("macro.clue");
1231///     let mut code = include_str!("../../examples/macro.clue").to_owned();
1232///
1233///     let (codes, variables, ..) = preprocess_code(
1234///         unsafe { code.as_bytes_mut() },
1235///         1,
1236///         false,
1237///         &filename,
1238///         &options,
1239///     )?;
1240///     let codes = preprocess_codes(0, codes, &variables, &filename)?;
1241///
1242///     Ok(())
1243/// }
1244/// ```
1245pub fn preprocess_codes(
1246	stacklevel: u8,
1247	codes: PPCode,
1248	variables: &PPVars,
1249	filename: &String,
1250) -> Result<Code, String> {
1251	let (mut codes, size) = codes;
1252	if codes.len() == 1 {
1253		Ok(codes.pop_back().unwrap().0)
1254	} else {
1255		let mut code = Code::with_capacity(size);
1256		for (codepart, uses_vars) in codes {
1257			code.append(if uses_vars {
1258				preprocess_variables(stacklevel, &codepart, codepart.len(), variables, filename)?
1259			} else {
1260				codepart
1261			})
1262		}
1263		Ok(code)
1264	}
1265}
1266
1267/// Expand the variables in a [`Code`]
1268/// Takes a stacklevel, a code segment, the variables, and a filename.
1269///
1270/// # Errors
1271/// Returns an error if a variable is not found, if macro expansion is too deep,
1272/// if a macro is not found, or if a macro receives too many or too little arguments.
1273///
1274/// # Examples
1275/// See [`preprocess_codes`]
1276///
1277/// ```
1278/// use clue_core::{code::Code, env::Options, preprocessor::*};
1279///
1280/// fn main() -> Result<(), String> {
1281///     let options = Options::default();
1282///     let filename = String::from("macro.clue");
1283///     let mut code = include_str!("../../examples/macro.clue").to_owned();
1284///
1285///     let (codes, variables, ..) = preprocess_code(
1286///         unsafe { code.as_bytes_mut() },
1287///         1,
1288///         false,
1289///         &filename,
1290///         &options,
1291///     )?;
1292///     let codes: Code = codes
1293///         .0
1294///         .iter()
1295///         .flat_map(|code| preprocess_variables(0, &code.0, codes.1, &variables, &filename))
1296///         .fold(Code::new(), |mut acc, code| {
1297///             acc.append(code);
1298///             acc
1299///         });
1300///
1301///     Ok(())
1302/// }
1303/// ```
1304pub fn preprocess_variables(
1305	stacklevel: u8,
1306	code: &Code,
1307	size: usize,
1308	//mut chars: Peekable<Iter<CodeChar>>,
1309	variables: &PPVars,
1310	filename: &String,
1311) -> Result<Code, String> {
1312	let mut result = Code::with_capacity(size);
1313	let mut chars = code.iter().peekable();
1314	while let Some(c) = chars.next() {
1315		match c.0 {
1316			b'$' => {
1317				let name = {
1318					let mut name = Code::with_capacity(cmp::min(size - 1, 8));
1319					while let Some((c, ..)) = chars.peek() {
1320						if !(c.is_ascii_alphanumeric() || *c == b'_') {
1321							break;
1322						}
1323						name.push(*chars.next().unwrap())
1324					}
1325					name
1326				};
1327				if let Ok(value) = env::var(name.to_string()) {
1328					result.push((b'"', c.1, c.2));
1329					for strc in value.as_bytes() {
1330						result.push((*strc, c.1, c.2));
1331					}
1332					result.push((b'"', c.1, c.2));
1333				} else if let Some(value) = variables.get(&name) {
1334					if stacklevel == MAX {
1335						return Err(error(
1336							"Too many variables called (likely recursive)",
1337							c.1,
1338							c.2,
1339							filename,
1340						));
1341					}
1342					result.append(match value {
1343						PPVar::Simple(value) => value.clone(),
1344						PPVar::ToProcess(value) => preprocess_variables(
1345							stacklevel + 1,
1346							value,
1347							value.len(),
1348							variables,
1349							filename,
1350						)?,
1351						PPVar::Macro {
1352							code,
1353							args,
1354							ppvars,
1355							vararg,
1356						} => {
1357							// TODO: See issue #87
1358							let macro_variables = {
1359								let mut macro_variables = variables.clone();
1360								macro_variables.extend(ppvars.clone());
1361								let is_called = matches!(chars.next(), Some((b'!', ..)));
1362								if !is_called || !matches!(chars.next(), Some((b'(', ..))) {
1363									let name = name.to_string();
1364									return Err(error(
1365										format!(
1366											"Macro not called (replace '${name}{}' with '${name}!()')",
1367											if is_called {
1368												"!"
1369											} else {
1370												""
1371											}
1372										),
1373										c.1,
1374										c.2,
1375										filename,
1376									));
1377								}
1378								let mut args = args.iter();
1379								let mut varargs = 0;
1380								let len = macro_variables.len();
1381								loop {
1382									let mut value = Code::new();
1383									let mut cscope = 1u8;
1384									let end = loop {
1385										let Some(c) = chars.next() else {
1386											return Err(expected_before(")", "<end>", c.1, c.2, filename))
1387										};
1388										match c.0 {
1389											b'\'' | b'"' | b'`' => {
1390												value.push(*c);
1391												while let Some(stringc) = chars.next() {
1392													value.push(*stringc);
1393													match stringc.0 {
1394														b'\\' => value.push(*chars.next().unwrap()),
1395														stringc if stringc == c.0 => break,
1396														_ => {}
1397													}
1398												}
1399												continue
1400											}
1401											b'(' | b'{' => cscope += 1,
1402											b',' if cscope == 1 => break b',',
1403											b'}' if cscope > 1 => cscope -= 1,
1404											b')' => {
1405												cscope -= 1;
1406												if cscope == 0 {
1407													break b')';
1408												}
1409											}
1410											_ => {}
1411										}
1412										value.push(*c)
1413									};
1414									let value = value.trim();
1415									if value.is_empty() {
1416										if len == macro_variables.len() && end == b')' {
1417											break;
1418										} else {
1419											let end = (end as char).to_string();
1420											return Err(expected_before(
1421												"<name>", &end, c.1, c.2, filename,
1422											));
1423										}
1424									}
1425									let value = PPVar::Simple(preprocess_variables(
1426										stacklevel + 1,
1427										&value,
1428										value.len(),
1429										variables,
1430										filename,
1431									)?);
1432									if let Some(arg_name) = args.next() {
1433										macro_variables.insert(arg_name.clone(), value);
1434									} else if *vararg {
1435										varargs += 1;
1436										let mut arg_name = Code::with_capacity(varargs + 1);
1437										arg_name.push((b'_', c.1, c.2));
1438										for _ in 0..varargs {
1439											arg_name.push((b'v', c.1, c.2));
1440										}
1441										macro_variables.insert(arg_name, value);
1442									} else {
1443										return Err(error(
1444											"Too many arguments passed to macro",
1445											c.1,
1446											c.2,
1447											filename,
1448										));
1449									}
1450									if end == b')' {
1451										break;
1452									}
1453								}
1454								if let Some(missed) = args.next() {
1455									return Err(error(
1456										format!(
1457											"Missing argument '{}' for macro",
1458											missed.to_string()
1459										),
1460										c.1,
1461										c.2,
1462										filename,
1463									));
1464								}
1465								macro_variables
1466							};
1467							preprocess_codes(
1468								stacklevel + 1,
1469								code.clone(),
1470								&macro_variables,
1471								filename,
1472							)?
1473						}
1474						PPVar::VarArgs((codes, size)) => {
1475							let mut result = Code::with_capacity(size * 3);
1476							let mut variables = variables.clone();
1477							let mut name = Code::from((b"_v", c.1, c.2));
1478							while let Some(vararg) = variables.remove(&name) {
1479								variables.insert(Code::from((b"vararg", c.1, c.2)), vararg);
1480								result.append(preprocess_codes(
1481									stacklevel + 1,
1482									(codes.clone(), *size),
1483									&variables,
1484									filename,
1485								)?);
1486								name.push(*name.last().unwrap());
1487							}
1488							result
1489						}
1490					});
1491				} else {
1492					return Err(error(
1493						format_clue!("Value '", name.to_string(), "' not found"),
1494						c.1,
1495						c.2,
1496						filename,
1497					));
1498				};
1499			}
1500			b'\'' | b'"' | b'`' => {
1501				result.push(*c);
1502				while let Some(stringc) = chars.next() {
1503					result.push(*stringc);
1504					let stringc = stringc.0;
1505					if stringc == b'\\' {
1506						if let Some(nextc) = chars.next() {
1507							result.push(*nextc)
1508						}
1509					} else if stringc == c.0 {
1510						break;
1511					}
1512				}
1513			}
1514			_ => result.push(*c),
1515		}
1516	}
1517	Ok(result)
1518}