#!/usr/bin/env python3
"""
generar_gramatica.py - Genera syntaxes/sipi.tmLanguage.json y
snippets/sipi.json a partir de la lista REAL de comandos del motor
(COMANDOS_CONOCIDOS en sipi.py) y de BLOQUES_QUE_ABREN (para el
plegado de codigo).

Por que un generador y no un archivo escrito a mano: en la v35.0 de
SiPi, el editor de escritorio tuvo un bug real donde su lista de
palabras clave para resaltado se copio a mano desde el motor y se
desincronizo con el tiempo (26 comandos reales nunca se coloreaban).
La correccion de ese bug fue cargar la lista dinamicamente desde el
motor en vez de mantener una copia -- este script aplica exactamente
esa misma leccion a la extension de VS Code: nunca se escribe la
lista de comandos a mano en el .tmLanguage.json, siempre se regenera
desde la fuente de verdad.

Uso:
    python3 generar_gramatica.py
    (correr desde esta carpeta, o con la carpeta src/ del proyecto
    como argumento: python3 generar_gramatica.py /ruta/a/src)
"""
import ast
import json
import os
import re
import sys

AQUI = os.path.dirname(os.path.abspath(__file__))


def _ruta_src():
    if len(sys.argv) > 1:
        return sys.argv[1]
    candidata = os.path.normpath(os.path.join(AQUI, "..", "..", "src"))
    if os.path.exists(os.path.join(candidata, "sipi.py")):
        return candidata
    raise SystemExit(
        "No se encontro 'sipi.py'. Pasa la ruta de la carpeta src/ como argumento: "
        "python3 generar_gramatica.py /ruta/a/src"
    )


def _extraer_lista(src_texto, nombre_variable):
    patron = re.compile(
        r"^" + re.escape(nombre_variable) + r"\s*=\s*(\[.*?\])\s*$",
        re.DOTALL | re.MULTILINE,
    )
    m = patron.search(src_texto)
    if not m:
        raise SystemExit(f"No se encontro la variable '{nombre_variable}' en sipi.py")
    return ast.literal_eval(m.group(1))


def _extraer_set(src_texto, nombre_variable):
    patron = re.compile(
        r"^" + re.escape(nombre_variable) + r"\s*=\s*(\{.*?\})\s*$",
        re.DOTALL | re.MULTILINE,
    )
    m = patron.search(src_texto)
    if not m:
        raise SystemExit(f"No se encontro la variable '{nombre_variable}' en sipi.py")
    return ast.literal_eval(m.group(1))


def generar():
    carpeta_src = _ruta_src()
    with open(os.path.join(carpeta_src, "sipi.py"), "r", encoding="utf-8") as f:
        contenido = f.read()

    comandos = sorted(set(_extraer_lista(contenido, "COMANDOS_CONOCIDOS")))
    bloques_que_abren = sorted(_extraer_set(contenido, "BLOQUES_QUE_ABREN"))

    comandos_regex = "|".join(re.escape(c) for c in comandos)

    gramatica = {
        "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
        "name": "SiPi",
        "scopeName": "source.sipi",
        "fileTypes": ["sipi"],
        "patterns": [
            {"include": "#comentarios"},
            {"include": "#strings"},
            {"include": "#numeros"},
            {"include": "#palabras_logicas"},
            {"include": "#comandos"},
        ],
        "repository": {
            "comentarios": {
                "patterns": [
                    {"name": "comment.line.double-slash.sipi", "match": "//.*$"},
                    {"name": "comment.block.sipi", "begin": r"/\*", "end": r"\*/"},
                ]
            },
            "strings": {
                "patterns": [
                    {
                        "name": "string.quoted.double.sipi",
                        "begin": '"',
                        "end": '"',
                        "patterns": [
                            {"name": "constant.character.escape.sipi", "match": r"\\."},
                            {
                                "name": "meta.interpolation.sipi",
                                "match": r"\{[^}]+\}",
                            },
                        ],
                    },
                    {"name": "string.quoted.triple.sipi", "begin": '"""', "end": '"""'},
                ]
            },
            "numeros": {
                "patterns": [
                    {"name": "constant.numeric.sipi", "match": r"\b\d+(\.\d+)?\b"}
                ]
            },
            "palabras_logicas": {
                "patterns": [
                    {
                        "name": "keyword.control.sipi",
                        "match": r"\b(y|o|no|verdadero|falso|nulo)\b",
                    }
                ]
            },
            "comandos": {
                "patterns": [
                    {
                        "name": "keyword.control.sipi",
                        "match": rf"\b({comandos_regex})\b",
                    }
                ]
            },
        },
    }

    with open(os.path.join(AQUI, "syntaxes", "sipi.tmLanguage.json"), "w", encoding="utf-8") as f:
        json.dump(gramatica, f, ensure_ascii=False, indent=2)

    # Snippets para los patrones de bloque mas comunes (los que abre
    # BLOQUES_QUE_ABREN, tomados directo del motor, mas 'programa' que
    # es la primera linea obligatoria de todo archivo .sipi).
    plantillas_por_bloque = {
        "programa": ('programa "${1:Mi programa}"\n$0'),
        "si": "si ${1:condicion}\n    $0\nfin",
        "mientras": "mientras ${1:condicion}\n    $0\nfin",
        "repetir": "repetir ${1:5} veces\n    $0\nfin",
        "para_cada": "para_cada ${1:item} en ${2:lista}\n    $0\nfin",
        "funcion": "funcion ${1:nombre}(${2:parametros})\n    $0\nfin",
        "clase": "clase ${1:Nombre}\n    $0\nfin",
        "interfaz": "interfaz ${1:Nombre}\n    metodo ${2:nombre}()\n    fin\nfin",
        "intentar": "intentar\n    $0\ncapturar\n    decir \"Error: {error}\"\nfin",
        "seleccionar": "seleccionar ${1:valor}\n    caso ${2:opcion}\n        $0\n    otro\n        \nfin",
        "ventana": 'ventana "${1:Titulo}" ${2:400} ${3:300}\n    $0\nfin',
        "crear_juego": 'crear_juego "${1:Titulo}" ${2:800} ${3:600}\n    $0\nfin',
        "escena_3d": 'escena_3d "${1:Titulo}" ${2:640} ${3:480}\n    $0\nfin',
    }
    snippets = {}
    for bloque in bloques_que_abren:
        if bloque in plantillas_por_bloque:
            snippets[f"SiPi: bloque {bloque}"] = {
                "prefix": bloque,
                "body": plantillas_por_bloque[bloque].split("\n"),
                "description": f"Bloque '{bloque} ... fin' de SiPi",
            }
    snippets["SiPi: programa"] = {
        "prefix": "programa",
        "body": plantillas_por_bloque["programa"].split("\n"),
        "description": "Primera linea obligatoria de un archivo .sipi",
    }

    with open(os.path.join(AQUI, "snippets", "sipi.json"), "w", encoding="utf-8") as f:
        json.dump(snippets, f, ensure_ascii=False, indent=2)

    print(f"[SiPi] Gramatica generada con {len(comandos)} comandos reales.")
    print(f"[SiPi] {len(snippets)} snippets generados a partir de {len(bloques_que_abren)} bloques reales.")


if __name__ == "__main__":
    generar()
