# today we're gonna write a preprocessor for TIC-80 shit # support: #include, #define, overloaded ops (+=, -=, *=, /=) # license: MIT import os import re from typing import Dict, Set class LuaPreprocessor: def __init__(self): self.defines: Dict[str, str] = {} self.included_files: Set[str] = set() def process_file(self, filename: str) -> str: if filename in self.included_files: raise RecursionError(f"Circular reference detected: {filename}") self.included_files.add(filename) try: with open(filename, 'r') as f: content = f.read() except FileNotFoundError: raise FileNotFoundError(f"Include file not found: {filename}") base_dir = os.path.dirname(os.path.abspath(filename)) # Process lines lines = content.split('\n') processed_lines = [] for line in lines: # Strip comments except TIC metadata tic_comment = re.match(r'-- \S+:\s+.+', line) if not tic_comment: if '--' in line: line = line[:line.index('--')] # Handle #defines and store for later macro replacement define_match = re.match(r'^\s*#define\s+(\w+)\s+(.+)$', line) if define_match: macro_name, macro_value = define_match.groups() self.defines[macro_name] = macro_value.strip() continue # Handle #includes include_match = re.match(r'^\s*#include\s+"([^"]+)"$', line) if include_match: include_path = include_match.group(1) abs_path = os.path.join(base_dir, include_path) processed_lines.append(self.process_file(abs_path)) continue # Do the later macro replacement for macro_name, macro_value in self.defines.items(): line = re.sub(r'\b' + macro_name + r'\b', macro_value, line) # Handle overload operators that don't exist in Lua operator_match = re.match(r'(\S+)\s?(\+|-)=\s?(\S+)', line) if operator_match: op_var, op_type, op_inc = operator_match.groups() replace_str = op_var + "=" + op_var + op_type + op_inc processed_lines.append(line.replace(operator_match[0], replace_str)) continue if line.strip() == "": continue processed_lines.append(line) return '\n'.join(processed_lines) def preprocess_lua(source_file: str) -> str: """ Parse a C-style Lua file #define and #include directives into an actual Lua file that doesn't barf. Also parses basic assigment operators, (+=, -=, *=, /=) Args: source_file (str): Path to the root Lua file Returns: str: Parsed Lua Raises: FileNotFoundError: If source file or included file is not found RecursionError: If you're dumb and make a circular reference """ preprocessor = LuaPreprocessor() return preprocessor.process_file(source_file) lambda: os.system('cls') parseoutput = preprocess_lua("pre-tests.lua") print("Output:\n%s" % parseoutput)