Spaces:
Running
Running
Ethgoin
commited on
Commit
·
d02548d
1
Parent(s):
ae21790
Migración completa a Python
Browse files- app.py +29 -0
- entrada.txt +5 -0
- lexer.py +62 -0
- main.py +19 -0
- parser.py +68 -0
- requirements.txt +1 -0
- semantico.py +46 -0
app.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import json
|
3 |
+
from lexer import lexer
|
4 |
+
from parser import Parser
|
5 |
+
from semantico import AnalizadorSemantico
|
6 |
+
|
7 |
+
def analizar_codigo(archivo):
|
8 |
+
try:
|
9 |
+
contenido = archivo.read().decode("utf-8")
|
10 |
+
tokens = lexer(contenido)
|
11 |
+
parser = Parser(tokens)
|
12 |
+
ast = parser.parse()
|
13 |
+
analizador = AnalizadorSemantico(ast)
|
14 |
+
resultado = analizador.analizar()
|
15 |
+
errores = "\n".join(resultado["errores_semanticos"])
|
16 |
+
json_resultado = json.dumps(resultado, indent=2)
|
17 |
+
return errores, json_resultado
|
18 |
+
except Exception as e:
|
19 |
+
return f"Error: {e}", "{}"
|
20 |
+
|
21 |
+
gr.Interface(
|
22 |
+
fn=analizar_codigo,
|
23 |
+
inputs=gr.File(label="Sube un archivo de código"),
|
24 |
+
outputs=[
|
25 |
+
gr.Textbox(label="Errores detectados"),
|
26 |
+
gr.Code(label="Contenido de analisis.json", language="json")
|
27 |
+
],
|
28 |
+
title="Analizador Semántico en Python - Lenguaje de Robots"
|
29 |
+
).launch()
|
entrada.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
x = 5;
|
2 |
+
y = z + 3.5;
|
3 |
+
IF y THEN {
|
4 |
+
a = y - 1;
|
5 |
+
}
|
lexer.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
from typing import List, Tuple
|
3 |
+
|
4 |
+
Token = Tuple[str, str]
|
5 |
+
|
6 |
+
# Lista de palabras clave y símbolos
|
7 |
+
RESERVED = {
|
8 |
+
"ACTIVATE_ALARM", "ACTIVATE_SENSOR", "BREAK", "CALIBRATE", "CHARGE_BATTERY", "CHECK_BATTERY",
|
9 |
+
"CLOSE_DOOR", "CONTINUE", "COPY_FILE", "DEACTIVATE_ALARM", "DEACTIVATE_SENSOR",
|
10 |
+
"DECREASE_SPEED", "DELETE_FILE", "DOWNLOAD", "ELSE", "FALSE", "FOR", "IF", "INCREASE_SPEED",
|
11 |
+
"INIT", "LOCK", "LOG", "LOW_BATTERY", "MOVE_BACKWARD", "MOVE_FORWARD", "MOVE_TO", "NULL",
|
12 |
+
"OPEN_DOOR", "PAUSE", "PRINT", "READ_SENSOR", "REBOOT", "RENAME_FILE", "RESET", "RESUME",
|
13 |
+
"REVERSE", "ROTATE", "SAVE_FILE", "SCAN", "SET_SPEED", "SHUTDOWN", "SHUT_OFF", "START", "STOP",
|
14 |
+
"STOP_IMMEDIATELY", "THEN", "TOGGLE_LIGHT", "TRUE", "TURN_DOWN", "TURN_LEFT", "TURN_RIGHT",
|
15 |
+
"TURN_UP", "UNLOCK", "UPLOAD", "UPLOAD_FILE", "WAIT", "WHILE"
|
16 |
+
}
|
17 |
+
|
18 |
+
TOKEN_SPEC = [
|
19 |
+
("FLOAT", r'\d+\.\d+'),
|
20 |
+
("INT", r'\d+'),
|
21 |
+
("ASSIGN", r'='),
|
22 |
+
("PLUS", r'\+'),
|
23 |
+
("MINUS", r'-'),
|
24 |
+
("MULTIPLY", r'\*'),
|
25 |
+
("DIVIDE", r'/'),
|
26 |
+
("EQUAL", r'=='),
|
27 |
+
("NOT_EQUAL", r'!='),
|
28 |
+
("GREATER", r'>'),
|
29 |
+
("LESS", r'<'),
|
30 |
+
("OPEN_PAREN", r'\('),
|
31 |
+
("CLOSE_PAREN", r'\)'),
|
32 |
+
("OPEN_BRACE", r'\{'),
|
33 |
+
("CLOSE_BRACE", r'\}'),
|
34 |
+
("SEMICOLON", r';'),
|
35 |
+
("COLON", r':'),
|
36 |
+
("COMMA", r','),
|
37 |
+
("NEWLINE", r'\n'),
|
38 |
+
("SKIP", r'[ \t\r]+'),
|
39 |
+
("IDENTIFIER", r'[a-zA-Z_][a-zA-Z0-9_]*'),
|
40 |
+
("MISMATCH", r'.'),
|
41 |
+
]
|
42 |
+
|
43 |
+
TOK_REGEX = '|'.join(f'(?P<{name}>{pattern})' for name, pattern in TOKEN_SPEC)
|
44 |
+
token_re = re.compile(TOK_REGEX)
|
45 |
+
|
46 |
+
def lexer(code: str) -> List[Token]:
|
47 |
+
tokens = []
|
48 |
+
for match in token_re.finditer(code):
|
49 |
+
kind = match.lastgroup
|
50 |
+
value = match.group()
|
51 |
+
if kind == "NEWLINE" or kind == "SKIP":
|
52 |
+
continue
|
53 |
+
elif kind == "IDENTIFIER":
|
54 |
+
if value in RESERVED:
|
55 |
+
tokens.append((value, value))
|
56 |
+
else:
|
57 |
+
tokens.append(("IDENTIFIER", value))
|
58 |
+
elif kind == "MISMATCH":
|
59 |
+
raise RuntimeError(f"Token no reconocido: {value}")
|
60 |
+
else:
|
61 |
+
tokens.append((kind, value))
|
62 |
+
return tokens
|
main.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from lexer import lexer
|
2 |
+
from parser import Parser
|
3 |
+
from semantico import AnalizadorSemantico
|
4 |
+
import json
|
5 |
+
|
6 |
+
with open("entrada.txt", "r", encoding="utf-8") as f:
|
7 |
+
codigo = f.read()
|
8 |
+
|
9 |
+
tokens = lexer(codigo)
|
10 |
+
parser = Parser(tokens)
|
11 |
+
ast = parser.parse()
|
12 |
+
|
13 |
+
analizador = AnalizadorSemantico(ast)
|
14 |
+
resultado = analizador.analizar()
|
15 |
+
|
16 |
+
with open("analisis.json", "w", encoding="utf-8") as out:
|
17 |
+
json.dump(resultado, out, indent=2)
|
18 |
+
|
19 |
+
print("✔ Análisis completo. Resultados guardados en 'analisis.json'")
|
parser.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List, Tuple, Any
|
2 |
+
|
3 |
+
Token = Tuple[str, str]
|
4 |
+
|
5 |
+
class Parser:
|
6 |
+
def __init__(self, tokens: List[Token]):
|
7 |
+
self.tokens = tokens
|
8 |
+
self.pos = 0
|
9 |
+
self.ast = []
|
10 |
+
|
11 |
+
def current(self) -> Token:
|
12 |
+
return self.tokens[self.pos] if self.pos < len(self.tokens) else ("EOF", "")
|
13 |
+
|
14 |
+
def match(self, expected_type: str) -> Token:
|
15 |
+
if self.current()[0] == expected_type:
|
16 |
+
tok = self.current()
|
17 |
+
self.pos += 1
|
18 |
+
return tok
|
19 |
+
raise SyntaxError(f"Se esperaba {expected_type} pero se encontró {self.current()}")
|
20 |
+
|
21 |
+
def parse(self):
|
22 |
+
while self.current()[0] != "EOF":
|
23 |
+
self.ast.append(self.instruction())
|
24 |
+
return self.ast
|
25 |
+
|
26 |
+
def instruction(self) -> Any:
|
27 |
+
token = self.current()
|
28 |
+
if token[0] == "IDENTIFIER":
|
29 |
+
return self.assignment()
|
30 |
+
elif token[0] == "IF":
|
31 |
+
return self.if_statement()
|
32 |
+
else:
|
33 |
+
raise SyntaxError(f"Instrucción no válida: {token}")
|
34 |
+
|
35 |
+
def assignment(self):
|
36 |
+
ident = self.match("IDENTIFIER")[1]
|
37 |
+
self.match("ASSIGN")
|
38 |
+
expr = self.expression()
|
39 |
+
self.match("SEMICOLON")
|
40 |
+
return {"type": "assign", "var": ident, "value": expr}
|
41 |
+
|
42 |
+
def if_statement(self):
|
43 |
+
self.match("IF")
|
44 |
+
condition = self.expression()
|
45 |
+
self.match("THEN")
|
46 |
+
self.match("OPEN_BRACE")
|
47 |
+
body = []
|
48 |
+
while self.current()[0] != "CLOSE_BRACE":
|
49 |
+
body.append(self.instruction())
|
50 |
+
self.match("CLOSE_BRACE")
|
51 |
+
return {"type": "if", "condition": condition, "body": body}
|
52 |
+
|
53 |
+
def expression(self):
|
54 |
+
left = self.term()
|
55 |
+
while self.current()[0] in ("PLUS", "MINUS"):
|
56 |
+
op = self.match(self.current()[0])[0]
|
57 |
+
right = self.term()
|
58 |
+
left = {"type": "binop", "op": op, "left": left, "right": right}
|
59 |
+
return left
|
60 |
+
|
61 |
+
def term(self):
|
62 |
+
token_type, token_value = self.current()
|
63 |
+
if token_type == "IDENTIFIER":
|
64 |
+
return {"type": "var", "value": self.match("IDENTIFIER")[1]}
|
65 |
+
elif token_type == "NUMERO":
|
66 |
+
return {"type": "num", "value": self.match("NUMERO")[1]}
|
67 |
+
else:
|
68 |
+
raise SyntaxError(f"Expresión inválida: {self.current()}")
|
requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
gradio
|
semantico.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class AnalizadorSemantico:
|
2 |
+
def __init__(self, ast):
|
3 |
+
self.ast = ast
|
4 |
+
self.tabla_simbolos = {}
|
5 |
+
self.errores = []
|
6 |
+
|
7 |
+
def analizar(self):
|
8 |
+
for nodo in self.ast:
|
9 |
+
self.analizar_instruccion(nodo)
|
10 |
+
return {
|
11 |
+
"variables_declaradas": self.tabla_simbolos,
|
12 |
+
"errores_semanticos": self.errores
|
13 |
+
}
|
14 |
+
|
15 |
+
def analizar_instruccion(self, nodo):
|
16 |
+
if nodo["type"] == "assign":
|
17 |
+
tipo_valor = self.analizar_expresion(nodo["value"])
|
18 |
+
self.tabla_simbolos[nodo["var"]] = tipo_valor
|
19 |
+
elif nodo["type"] == "if":
|
20 |
+
tipo_condicion = self.analizar_expresion(nodo["condition"])
|
21 |
+
if tipo_condicion != "int":
|
22 |
+
self.errores.append(
|
23 |
+
f"Condición del IF debe ser de tipo int, pero se encontró '{tipo_condicion}'"
|
24 |
+
)
|
25 |
+
for instr in nodo["body"]:
|
26 |
+
self.analizar_instruccion(instr)
|
27 |
+
|
28 |
+
def analizar_expresion(self, expr):
|
29 |
+
if expr["type"] == "num":
|
30 |
+
return "float" if "." in expr["value"] else "int"
|
31 |
+
elif expr["type"] == "var":
|
32 |
+
nombre = expr["value"]
|
33 |
+
if nombre not in self.tabla_simbolos:
|
34 |
+
self.errores.append(f"Variable '{nombre}' usada sin ser declarada.")
|
35 |
+
return "error"
|
36 |
+
return self.tabla_simbolos[nombre]
|
37 |
+
elif expr["type"] == "binop":
|
38 |
+
tipo_izq = self.analizar_expresion(expr["left"])
|
39 |
+
tipo_der = self.analizar_expresion(expr["right"])
|
40 |
+
if tipo_izq != tipo_der:
|
41 |
+
self.errores.append(f"Tipos incompatibles: {tipo_izq} y {tipo_der}")
|
42 |
+
return "error"
|
43 |
+
return tipo_izq
|
44 |
+
else:
|
45 |
+
self.errores.append(f"Expresión no reconocida: {expr}")
|
46 |
+
return "error"
|