import yaml import re from pathlib import Path # python3 SLS_Tests/yaml_to_c_tests.py SLS_Tests/cases.yaml SLS_C/tests/lexer_tests.c file_headers = """\ // Kyler Olsen // YREA SLS // Lexer Tests // October 2025 #include #include #include #include #include #include "sls/errors.h" #include "sls/lexer.h" #include "sls/string.h" #include "tests/lexer_test_helpers.h" #include "tests/tests.h" """ main_header = """\ TestsReport run_lexer_tests() { TestsReport test_report = (TestsReport) { .section = SLS_STR("lexer_tests"), .count = NUM_OF_TESTS, .tests = (TestResult *)malloc(sizeof(TestResult) * NUM_OF_TESTS), }; size_t i = 0; """ # === Helper functions === def sanitize_name(name: str) -> str: """Convert test name into a valid C function name.""" name = re.sub(r"[^a-zA-Z0-9_]", "_", name) name = re.sub(r"_+", "_", name) return f"test_{name}" def c_string_literal(s: str) -> str: """Escape quotes for embedding in C string literals.""" return s.replace('"', '\\"') def _token_to_c_call(token: dict, idx_var="i") -> str: """Generate a C 'test_*_value' call based on token type.""" ttype = token.get("type") value = token.get("value") if ttype == "i64": return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_I64, {value}}})' elif ttype == "i32": return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_I32, {value}}})' elif ttype == "i16": return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_I16, {value}}})' elif ttype == "i8": return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_I8, {value}}})' elif ttype == "u64": return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U64, {value}}})' elif ttype == "u32": return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U32, {value}}})' elif ttype == "u16": return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U16, {value}}})' elif ttype == "u8": return f'test_integer_value(&test, result, {idx_var}++, &(TestIntegerValue){{INTEGER_U8, {value}}})' elif ttype == "identifier": return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{FALSE, {len(value)}, "{value}"}})' # type: ignore elif ttype == "identifier_literal": return f'test_identifier_value(&test, result, {idx_var}++, &(TestIdentifierValue){{TRUE, {len(value)}, "{value}"}})' # type: ignore elif ttype == "error": return f'test_for_error(&test, result, i++, SLS_STR("{c_string_literal(value)}"))' # type: ignore else: raise ValueError(f' Unhandled token type: {ttype}') def token_to_c_call(token: dict, idx_var="i") -> str: """Generate a C 'test_*_value' call based on token type.""" return f"if ({_token_to_c_call(token, idx_var)}) return test.result;" def generate_c_test(test: dict) -> str: """Convert a single YAML test entry to a C test function.""" name = sanitize_name(test["name"]) code = c_string_literal(test["code"]) tokens = test.get("tokens", []) # Function header c_code = [f"static TestResult {name}() {{", f' LexerTest test = start_up_test(SLS_STR("{name}"), SLS_STR("{code}"));', " LexerResult result = lexical_analysis(&test.lexer_info);", " if (result.type == SLS_ERROR) return error_fail_test(&test, result, result.error);", " size_t i = 0;"] # Token checks for token in tokens: c_code.append(" " + token_to_c_call(token)) # EOF check and return if 'error' not in [token.get("type") for token in tokens]: c_code.append(" if (test_eof_value(&test, result, i++, 0)) return test.result;") c_code.append(" return pass_test(&test, result);") c_code.append("}\n") return "\n".join(c_code) def yaml_to_c_tests(yaml_path: str, output_path: str): """Convert YAML test cases into C test code.""" with open(yaml_path, "r", encoding="utf-8") as f: tests = yaml.safe_load(f) # Ensure we have a list of tests if not isinstance(tests, list): raise ValueError("Expected a YAML list of test cases.") c_tests = [] for test in tests: c_tests.append(generate_c_test(test)) program = [ file_headers, f"static const size_t NUM_OF_TESTS = {len(tests)};\n", "\n".join(c_tests), main_header, ] + [f" test_report.tests[i++] = {sanitize_name(test['name'])}();" for test in tests] program.append("\n return test_report;\n}\n") output_code = "\n".join(program) Path(output_path).write_text(output_code, encoding="utf-8") print(f" Generated {len(c_tests)} C tests -> {output_path}") # === Example usage === if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Convert YAML SLS tests to C lexer tests.") parser.add_argument("input", help="Path to input YAML test file") parser.add_argument("output", help="Path to output C file") args = parser.parse_args() yaml_to_c_tests(args.input, args.output)