summaryrefslogtreecommitdiff
path: root/Cool/compile.py
diff options
context:
space:
mode:
Diffstat (limited to 'Cool/compile.py')
-rw-r--r--Cool/compile.py153
1 files changed, 153 insertions, 0 deletions
diff --git a/Cool/compile.py b/Cool/compile.py
new file mode 100644
index 0000000..9c19403
--- /dev/null
+++ b/Cool/compile.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python3
+import sys
+import re
+import argparse
+
+TYPE_SPECIFIERS = {
+ 'int': '%d',
+ 'int32_t': '%d',
+ 'short': '%d',
+ 'long': '%ld',
+ 'unsigned': '%u',
+ 'unsigned int': '%u',
+ 'uint32_t': '%u',
+ 'size_t': '%zu',
+ 'float': '%f',
+ 'double': '%f',
+ 'char': '%c',
+ 'char*': '%s',
+ 'const char*': '%s',
+ 'char *': '%s',
+ 'const char *': '%s',
+}
+
+def parse_parameters(args_str: str) -> dict:
+ param_map = {}
+ if not args_str or args_str == 'void':
+ return param_map
+
+ params = args_str.split(',')
+ for param in params:
+ param = param.strip()
+ if not param:
+ continue
+
+ match = re.match(r'^(.*?)\b([A-Za-z0-9_]+)$', param)
+ if match:
+ type_part = match.group(1).strip()
+ var_name = match.group(2).strip()
+
+ type_clean = re.sub(r'\s*\*\s*', '*', type_part)
+ specifier = TYPE_SPECIFIERS.get(type_clean, '%d')
+ param_map[var_name] = (type_clean, specifier)
+
+ return param_map
+
+def escape_c_string(text: str) -> str:
+ return (text.replace('\\', '\\\\')
+ .replace('"', '\\"')
+ .replace('\r', '')
+ .replace('\n', '\\n'))
+
+def transpile_cool(cool_code: str, filename: str = "<stdin>") -> str:
+ lines = cool_code.splitlines(keepends=True)
+ c_output = []
+
+ cool_func_re = re.compile(r'^\s*COOL\s+void\s+([A-Za-z0-9_]+)\s*\((.*?)\)\s*\{\s*$')
+
+ in_cool_func = False
+ current_func = ""
+ raw_buffer = []
+ local_scope = {}
+
+ def flush_raw_buffer():
+ nonlocal raw_buffer
+ if raw_buffer:
+ combined = "".join(raw_buffer)
+ if combined:
+ escaped = escape_c_string(combined)
+ c_output.append(f' cool_html_raw(COOL_SV("{escaped}"));\n')
+ raw_buffer = []
+
+ for line_num, line in enumerate(lines, start=1):
+ match = cool_func_re.match(line)
+
+ if match and not in_cool_func:
+ in_cool_func = True
+ current_func, args = match.groups()
+ args_str = args.strip() if args.strip() else "void"
+
+ local_scope = parse_parameters(args_str)
+ c_output.append(f"void {current_func}({args_str}) {{\n")
+ continue
+
+ if in_cool_func:
+ if line.strip() == "}":
+ flush_raw_buffer()
+ c_output.append("}\n\n")
+ in_cool_func = False
+ current_func = ""
+ local_scope = {}
+ continue
+
+ # Tokenize line into @Component(), { expression }, and plain HTML
+ tokens = re.split(r'(@[A-Za-z0-9_]+\s*\([^)]*\)|\{[^{}]+\})', line)
+
+ for token in tokens:
+ if not token:
+ continue
+
+ # @Component()
+ if token.startswith('@') and '(' in token:
+ flush_raw_buffer()
+ call_code = token[1:].strip()
+ c_output.append(f" {call_code};\n")
+
+ # { expression }
+ elif token.startswith('{') and token.endswith('}'):
+ flush_raw_buffer()
+ expr = token[1:-1].strip()
+
+ if expr in local_scope:
+ type_clean, specifier = local_scope[expr]
+
+ if 'char*' in type_clean:
+ c_output.append(f" cool_html_txt({expr}, strlen({expr}));\n")
+ else:
+ c_output.append(f' cool_htmlf_raw("{specifier}", {expr});\n')
+ else:
+ available = ", ".join(f"'{p}'" for p in local_scope.keys()) if local_scope else "none"
+ sys.stderr.write(
+ f"\n[Transpiler Error] {filename}:{line_num}: "
+ f"Expression '{{{expr}}}' in function '{current_func}' does not match any parameter.\n"
+ f" -> Available parameter(s): {available}\n\n"
+ )
+ sys.exit(1)
+
+ # Raw HTML text
+ else:
+ raw_buffer.append(token)
+ else:
+ c_output.append(line)
+
+ return "".join(c_output)
+
+def main():
+ parser = argparse.ArgumentParser(description="Transpiles .cool template files to C source code.")
+ parser.add_argument("input", help="Input .cool file")
+ parser.add_argument("-o", "--output", help="Output .c file")
+ args = parser.parse_args()
+
+ with open(args.input, "r", encoding="utf-8") as f:
+ cool_code = f.read()
+
+ transpiled_c = transpile_cool(cool_code, filename=args.input)
+
+ if args.output:
+ with open(args.output, "w", encoding="utf-8") as f:
+ f.write(transpiled_c)
+ else:
+ sys.stdout.write(transpiled_c)
+
+if __name__ == "__main__":
+ main()