1use 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
35pub type PPVars = AHashMap<Code, PPVar>;
37pub type PPCode = (VecDeque<(Code, bool)>, usize);
39
40#[derive(Debug, Clone)]
41pub enum PPVar {
43 Simple(Code),
45
46 ToProcess(Code),
48
49 Macro {
51 code: PPCode,
53
54 args: Vec<Code>,
56
57 ppvars: PPVars,
59
60 vararg: bool,
62 },
63
64 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
589pub 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#[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(¤t_major, &wanted_major)
801 || check(¤t_minor, &wanted_minor)
802 || check(¤t_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; let mut skip_next = false; 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
1218pub 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
1267pub fn preprocess_variables(
1305 stacklevel: u8,
1306 code: &Code,
1307 size: usize,
1308 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 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 ¯o_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}