pre.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. # today we're gonna write a preprocessor for TIC-80 shit
  2. # support: #include, #define, overloaded ops (+=, -=, *=, /=)
  3. # license: MIT
  4. import os
  5. import re
  6. from typing import Dict, Set
  7. class LuaPreprocessor:
  8. def __init__(self):
  9. self.defines: Dict[str, str] = {}
  10. self.included_files: Set[str] = set()
  11. def process_file(self, filename: str) -> str:
  12. if filename in self.included_files:
  13. raise RecursionError(f"Circular reference detected: {filename}")
  14. self.included_files.add(filename)
  15. try:
  16. with open(filename, 'r') as f:
  17. content = f.read()
  18. except FileNotFoundError:
  19. raise FileNotFoundError(f"Include file not found: {filename}")
  20. base_dir = os.path.dirname(os.path.abspath(filename))
  21. # Process lines
  22. lines = content.split('\n')
  23. processed_lines = []
  24. for line in lines:
  25. # Strip comments except TIC metadata
  26. tic_comment = re.match(r'-- \S+:\s+.+', line)
  27. if not tic_comment:
  28. if '--' in line:
  29. line = line[:line.index('--')]
  30. # Handle #defines and store for later macro replacement
  31. define_match = re.match(r'^\s*#define\s+(\w+)\s+(.+)$', line)
  32. if define_match:
  33. macro_name, macro_value = define_match.groups()
  34. self.defines[macro_name] = macro_value.strip()
  35. continue
  36. # Handle #includes
  37. include_match = re.match(r'^\s*#include\s+"([^"]+)"$', line)
  38. if include_match:
  39. include_path = include_match.group(1)
  40. abs_path = os.path.join(base_dir, include_path)
  41. processed_lines.append(self.process_file(abs_path))
  42. continue
  43. # Do the later macro replacement
  44. for macro_name, macro_value in self.defines.items():
  45. line = re.sub(r'\b' + macro_name + r'\b', macro_value, line)
  46. # Handle overload operators that don't exist in Lua
  47. operator_match = re.match(r'(\S+)\s?(\+|-)=\s?(\S+)', line)
  48. if operator_match:
  49. op_var, op_type, op_inc = operator_match.groups()
  50. replace_str = op_var + "=" + op_var + op_type + op_inc
  51. processed_lines.append(line.replace(operator_match[0], replace_str))
  52. continue
  53. if line.strip() == "":
  54. continue
  55. processed_lines.append(line)
  56. return '\n'.join(processed_lines)
  57. def preprocess_lua(source_file: str) -> str:
  58. """
  59. Parse a C-style Lua file #define and #include directives into an actual Lua file that doesn't barf.
  60. Also parses basic assigment operators, (+=, -=, *=, /=)
  61. Args:
  62. source_file (str): Path to the root Lua file
  63. Returns:
  64. str: Parsed Lua
  65. Raises:
  66. FileNotFoundError: If source file or included file is not found
  67. RecursionError: If you're dumb and make a circular reference
  68. """
  69. preprocessor = LuaPreprocessor()
  70. return preprocessor.process_file(source_file)
  71. lambda: os.system('cls')
  72. parseoutput = preprocess_lua("pre-tests.lua")
  73. print("Output:\n%s" % parseoutput)