From b5fb1445fdc42835b8949515082f555a71e12a91 Mon Sep 17 00:00:00 2001 From: daviddoji Date: Sun, 17 May 2026 17:11:50 +0200 Subject: [PATCH] Plantilla para generar esqueleto de soluciones --- src/template.py | 305 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 src/template.py diff --git a/src/template.py b/src/template.py new file mode 100644 index 0000000..e54a2f5 --- /dev/null +++ b/src/template.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python +""" +Creation of templates for Exercitium problems +""" + +import ast +import re +import shutil +from argparse import ArgumentParser +from dataclasses import dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class Example: + input_value: object + expected_value: object + + +def load_instructions(path: Path) -> str: + return path.read_text(encoding="utf-8").strip() + + +def parse_instructions(text: str) -> tuple[str, list[Example]]: + examples: list[Example] = [] + function_name: str | None = None + + for raw_line in text.splitlines(): + line = raw_line.strip() + if not line: + continue + + left, right = line.split("==", maxsplit=1) + match = re.match(r"^--\s*([A-Za-z_][A-Za-z0-9_]*)\s+(.*)$", left.strip()) + if match is None: + raise ValueError(f"Cannot parse instruction line: {raw_line}") + + current_name = match.group(1) + if function_name is None: + function_name = current_name + elif function_name != current_name: + raise ValueError("All instruction lines must use the same function name") + + examples.append( + Example( + input_value=ast.literal_eval(match.group(2).strip()), + expected_value=ast.literal_eval(right.strip()), + ) + ) + + if function_name is None: + raise ValueError("No examples found in instructions") + + return function_name, examples + + +def infer_python_type(value: object) -> str: + if isinstance(value, bool): + return "bool" + if isinstance(value, int): + return "int" + if isinstance(value, str): + return "str" + if isinstance(value, list): + inner_type = infer_python_type(value[0]) if value else "object" + return f"list[{inner_type}]" + return "object" + + +def infer_go_type(value: object) -> str: + if isinstance(value, bool): + return "bool" + if isinstance(value, int): + return "int" + if isinstance(value, str): + return "string" + if isinstance(value, list): + inner_type = infer_go_type(value[0]) if value else "int" + return f"[]{inner_type}" + raise TypeError(f"Unsupported Go type: {value!r}") + + +def infer_rust_type(value: object) -> str: + if isinstance(value, bool): + return "bool" + if isinstance(value, int): + return "i32" + if isinstance(value, str): + return "&str" + if isinstance(value, list): + inner_type = infer_rust_type(value[0]) if value else "i32" + return f"Vec<{inner_type}>" + raise TypeError(f"Unsupported Rust type: {value!r}") + + +def infer_rust_param_type(value: object) -> str: + if isinstance(value, list): + inner_type = infer_rust_type(value[0]) if value else "i32" + return f"&[{inner_type}]" + return infer_rust_type(value) + + +def render_python(value: object) -> str: + return repr(value) + + +def render_julia(value: object) -> str: + if isinstance(value, str): + return f'"{value}"' + if isinstance(value, list): + return "[" + ", ".join(render_julia(item) for item in value) + "]" + return str(value) + + +def render_go(value: object) -> str: + if isinstance(value, str): + return f'"{value}"' + if isinstance(value, list): + return ( + f"{infer_go_type(value)}{{" + + ", ".join(render_go(item) for item in value) + + "}" + ) + return str(value) + + +def render_rust(value: object) -> str: + if isinstance(value, str): + return f'"{value}"' + if isinstance(value, list): + return "vec![" + ", ".join(render_rust(item) for item in value) + "]" + return str(value) + + +def build_python_checks(function_name: str, examples: list[Example]) -> str: + return "\n\n".join( + [ + f"check = {render_python(example.input_value)}\n" + f"print({function_name}(check)) # {render_python(example.expected_value)}" + for example in examples + ] + ) + + +def build_julia_checks(function_name: str, examples: list[Example]) -> str: + return "\n\n".join( + [ + f"check = {render_julia(example.input_value)}\n" + f"println({function_name}(check)) # {render_julia(example.expected_value)}" + for example in examples + ] + ) + + +def build_rust_checks(function_name: str, examples: list[Example]) -> str: + return "\n\n".join( + [ + f" let check = {render_rust(example.input_value)};\n" + f' println!("{{:?}}", {function_name}(&check)); // {render_python(example.expected_value)}' + for example in examples + ] + ) + + +def build_go_checks(function_name: str, examples: list[Example]) -> str: + checks: list[str] = [] + + for index, example in enumerate(examples): + assign_op = ":=" if index == 0 else "=" + checks.append( + f" check {assign_op} {render_go(example.input_value)}\n" + f" fmt.Println({function_name}(check)) // {render_python(example.expected_value)}" + ) + + return "\n\n".join(checks) + + +def comment_block(prefix: str, text: str) -> str: + return "\n".join( + f"{prefix} {line}".rstrip() + for line in text.splitlines() + ) + + +def create_python( + problem_py: Path, + function_name: str, + examples: list[Example], +) -> None: + input_type = infer_python_type(examples[0].input_value) + return_type = infer_python_type(examples[0].expected_value) + + template = "\n".join( + [ + f"def {function_name}(values: {input_type}) -> {return_type}:", + " return []", + "", + build_python_checks(function_name, examples), + "", + ] + ) + problem_py.write_text(template, encoding="utf-8") + + +def create_julia( + problem_jl: Path, + function_name: str, + examples: list[Example], +) -> None: + template = "\n".join( + [ + f"function {function_name}(values)", + " return []", + "end", + "", + build_julia_checks(function_name, examples), + "", + ] + ) + problem_jl.write_text(template, encoding="utf-8") + + +def create_rust( + problem_rs: Path, + function_name: str, + examples: list[Example], +) -> None: + input_type = infer_rust_param_type(examples[0].input_value) + return_type = infer_rust_type(examples[0].expected_value) + + template = "\n".join( + [ + f"fn {function_name}(values: {input_type}) -> {return_type} {{", + " let _ = values;", + " vec![]", + "}", + "", + "fn main() {", + build_rust_checks(function_name, examples), + "}", + "", + ] + ) + problem_rs.write_text(template, encoding="utf-8") + + +def create_go( + problem_go: Path, + function_name: str, + examples: list[Example], +) -> None: + input_type = infer_go_type(examples[0].input_value) + return_type = infer_go_type(examples[0].expected_value) + + template = "\n".join( + [ + "package main", + "", + 'import "fmt"', + "", + f"func {function_name}(values {input_type}) {return_type} " + "{", + " _ = values", + f" return {return_type}" + "{}", + "}", + "", + "func main() {", + build_go_checks(function_name, examples), + "}", + "", + ] + ) + problem_go.write_text(template, encoding="utf-8") + + +if __name__ == "__main__": + parser = ArgumentParser(description=__doc__) + parser.add_argument( + "-p", + "--problem-number", + dest="problem_number", + type=int, + required=True, + help="number of the problem to solve", + ) + args = parser.parse_args() + + base_dir = Path(__file__).resolve().parent + instructions = load_instructions(base_dir / "instructions.txt") + + problem_stem = f"{args.problem_number:03d}" + problem_py = base_dir / f"{problem_stem}.py" + problem_jl = base_dir / f"{problem_stem}.jl" + problem_rs = base_dir / f"{problem_stem}.rs" + problem_go = base_dir / f"{problem_stem}.go" + + function_name, examples = parse_instructions(instructions) + create_python(problem_py, function_name, examples) + create_julia(problem_jl, function_name, examples) + create_rust(problem_rs, function_name, examples) + create_go(problem_go, function_name, examples) + + shutil.move(problem_py, base_dir / "Python") + shutil.move(problem_jl, base_dir / "Julia") + shutil.move(problem_rs, base_dir / "Rust") + shutil.move(problem_go, base_dir / "Go") \ No newline at end of file