sergio/takana
> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…
⎇ 607fbbc3494f5b1f0f9908d26f32be0b9c264a39
scripts/awk-banco-diferencial.py
History
Who touched what
raw
el awk se llama akllay: `qillqa` estaba TOMADO en tawasuyu, y no lo comprobé
1#!/usr/bin/env python3
2"""Banco DIFERENCIAL de awk: corre los mismos casos en varios motores y compara.
3
4 scripts/awk-banco-diferencial.py # akllay contra busybox awk y gawk --posix
5 scripts/awk-banco-diferencial.py --motor ruta/al/awk # añade otro candidato
6
7Por qué diferencial y no de salidas esperadas escritas a mano: un banco escrito a mano mide lo que
8al autor se le ocurrió preguntar, y ahí es donde se cuela el sesgo — es la lección del §paso 3 de
9`docs/plan-botar-busybox.md`, donde brush pasó 56/57 casos POSIX y no construía una sola receta.
10Contra un oráculo que ya está en producción, cada divergencia es un hecho, no una opinión.
11
12⚠ Los oráculos NO coinciden entre sí en todo: busybox awk y gawk difieren en `substr` con índices
13raros, en `%c` y en el formato de algunos números. Por eso el veredicto no es «igual a X» sino
14«igual a LOS DOS», y cuando los dos difieren entre ellos el caso se marca `discrepan` y no cuenta
15como fallo de nadie — se mira a mano y se decide siguiendo POSIX.
16"""
17import argparse, json, os, subprocess, sys
18
19RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
20
21
22def del_store(receta, rel):
23 est = json.load(open(os.path.join(RAIZ, "docs/state/build-state.json")))["nodes"]
24 if receta not in est:
25 return None
26 p = "/store/" + est[receta]["hash"].split(":")[1] + "-" + receta + "/" + rel
27 return p if os.path.exists(p) else None
28
29
30# (nombre, programa, entrada)
31CASOS = [
32 # ── campos, separadores, registros ──────────────────────────────────────────────────────────
33 ("campos-simple", '{print $2}', "a b c\nd e f\n"),
34 ("campos-nf", '{print NF, $NF}', "a b c\nx\n"),
35 ("campos-vacio", '{print "["$5"]"}', "a b\n"),
36 ("campos-cero", '{print $0}', " hola mundo \n"),
37 ("fs-espacios-rachas", '{print NF}', " a b \n"),
38 ("fs-coma", '-F, {print $2}', "a,b,c\n"),
39 ("fs-tab", '-F\t {print $2}', "a\tb\tc\n"),
40 ("fs-ere", '-F[0-9]+ {print $2}', "ab123cd\n"),
41 ("fs-punto-literal", '-F. {print NF}', "a.b.c\n"),
42 ("asignar-campo", '{$2="X"; print}', "a b c\n"),
43 ("asignar-campo-lejos", '{$5="X"; print; print NF}', "a b\n"),
44 ("asignar-cero", '{$0="p q r"; print $2, NF}', "a b\n"),
45 ("nf-recorta", '{NF=2; print; print NF}', "a b c d\n"),
46 ("ofs-al-rehacer", 'BEGIN{OFS="-"} {$1=$1; print}', "a b c\n"),
47 ("registros-nr-fnr", '{print NR, FNR}', "x\ny\n"),
48 # ── valores: la parte que más se equivoca ───────────────────────────────────────────────────
49 ("strnum-numerico", '{print ($1 > $2)}', "10 9\n"),
50 ("cadena-alfabetica", 'BEGIN{print ("10" > "9")}', ""),
51 ("strnum-cero-falso", '{if ($1) print "si"; else print "no"}', "0\n"),
52 ("cadena-cero-verdad", 'BEGIN{ if ("0") print "si"; else print "no"}', ""),
53 ("texto-se-conserva", '{print $1}', "0012\n"),
54 ("suma-con-basura", '{print $1+0}', "12x\n"),
55 ("entero-sin-decimales", 'BEGIN{print 2/1, 1/3}', ""),
56 ("convfmt", 'BEGIN{CONVFMT="%.2g"; x=1/3; print x ""}', ""),
57 ("ofmt", 'BEGIN{OFMT="%.2f"; print 1/3}', ""),
58 ("uninit-es-cero-y-vacio", 'BEGIN{print x+0, "["x"]", (x==0), (x=="")}', ""),
59 # ── aritmética y operadores ─────────────────────────────────────────────────────────────────
60 ("potencia-derecha", 'BEGIN{print 2^3^2}', ""),
61 ("modulo-real", 'BEGIN{print 5.5 % 2}', ""),
62 ("unario-vs-concat", 'BEGIN{a=1;b=2;print a-b; print a" "-b}', ""),
63 ("incremento", 'BEGIN{i=5; print i++, i, ++i, i}', ""),
64 ("asigna-op", 'BEGIN{x=10; x-=3; x*=2; print x}', ""),
65 ("ternario", 'BEGIN{print (1?"a":"b"), (0?"a":"b")}', ""),
66 ("comparacion-encadena", 'BEGIN{print (1<2), (2<=2), (3!=3)}', ""),
67 ("concatenacion", 'BEGIN{print "a" 1+1 "b"}', ""),
68 # ── expresiones regulares ───────────────────────────────────────────────────────────────────
69 ("ere-patron", '/b/{print "hay"}', "abc\nxyz\n"),
70 ("ere-tilde", '{print ($0 ~ /^a/), ($0 !~ /^a/)}', "abc\nxbc\n"),
71 ("ere-alternancia-larga", 'BEGIN{print match("ab", /a|ab/), RLENGTH}', ""),
72 ("ere-clase", 'BEGIN{print match("foo123", /[[:digit:]]+/), RSTART, RLENGTH}', ""),
73 ("ere-intervalo", 'BEGIN{print match("aaaa", /a{2,3}/), RLENGTH}', ""),
74 ("ere-anclas", 'BEGIN{print ("abc" ~ /^abc$/), ("abc" ~ /^b/)}', ""),
75 ("ere-punto", 'BEGIN{print ("a.c" ~ /a\\.c/), ("abc" ~ /a\\.c/)}', ""),
76 ("ere-estrella-vacia", 'BEGIN{print match("bbb", /a*/), RLENGTH}', ""),
77 ("ere-clase-negada", 'BEGIN{print match("abc1", /[^a-z]/), RSTART}', ""),
78 # ── funciones de cadena ─────────────────────────────────────────────────────────────────────
79 ("substr-normal", 'BEGIN{print substr("abcdef", 2, 3)}', ""),
80 ("substr-sin-largo", 'BEGIN{print substr("abcdef", 3)}', ""),
81 ("substr-inicio-cero", 'BEGIN{print "["substr("abcdef", 0, 3)"]"}', ""),
82 ("substr-negativo", 'BEGIN{print "["substr("abcdef", -1, 3)"]"}', ""),
83 ("substr-pasado", 'BEGIN{print "["substr("abc", 10)"]"}', ""),
84 ("substr-redondeo", 'BEGIN{print substr("abcdef", 1.5, 2.5)}', ""),
85 ("index", 'BEGIN{print index("abcabc","bc"), index("abc","z"), index("abc","")}', ""),
86 ("length", 'BEGIN{print length("hola"), length()}', ""),
87 ("length-campo", '{print length}', "abcd\n"),
88 ("toupper-tolower", 'BEGIN{print toupper("aBc"), tolower("AbC")}', ""),
89 ("sub-simple", 'BEGIN{s="aaa"; n=sub(/a/,"b",s); print n, s}', ""),
90 ("gsub-simple", 'BEGIN{s="aaa"; n=gsub(/a/,"b",s); print n, s}', ""),
91 ("gsub-ampersand", 'BEGIN{s="abc"; gsub(/b/,"[&]",s); print s}', ""),
92 ("gsub-ampersand-escapado", 'BEGIN{s="abc"; gsub(/b/,"[\\\\&]",s); print s}', ""),
93 ("gsub-vacio", 'BEGIN{s="xax"; n=gsub(/a*/,"-",s); print n, s}', ""),
94 ("gsub-en-campo", '{gsub(/o/,"0"); print}', "foo boo\n"),
95 ("split-fs", 'BEGIN{n=split("a:b:c",A,":"); print n, A[1], A[3]}', ""),
96 ("split-ere", 'BEGIN{n=split("a1b22c",A,/[0-9]+/); print n, A[2], A[3]}', ""),
97 ("split-vacio", 'BEGIN{n=split("",A,":"); print n}', ""),
98 ("split-default", 'BEGIN{n=split(" a b ",A); print n, A[1], A[2]}', ""),
99 ("sprintf", 'BEGIN{print sprintf("%03d-%s", 7, "x")}', ""),
100 ("match-sin-exito", 'BEGIN{print match("abc",/z/), RSTART, RLENGTH}', ""),
101 # ── printf ──────────────────────────────────────────────────────────────────────────────────
102 ("printf-enteros", 'BEGIN{printf "%d|%5d|%-5d|%05d\\n", 42, 42, 42, 42}', ""),
103 ("printf-negativo", 'BEGIN{printf "%05d|%+d|% d\\n", -1, 7, 7}', ""),
104 ("printf-flotante", 'BEGIN{printf "%.2f|%e|%g\\n", 3.14159, 150, 0.0000123}', ""),
105 ("printf-cadena", 'BEGIN{printf "%s|%10s|%-10s|%.2s|\\n", "ab","ab","ab","abcd"}', ""),
106 ("printf-caracter-num", 'BEGIN{printf "%c\\n", 65}', ""),
107 ("printf-hex", 'BEGIN{printf "%x|%X|%o\\n", 255, 255, 8}', ""),
108 ("printf-asterisco", 'BEGIN{printf "%*d|%.*f\\n", 5, 42, 2, 3.14159}', ""),
109 ("printf-porciento", 'BEGIN{printf "100%%\\n"}', ""),
110 ("printf-trunca", 'BEGIN{printf "%d\\n", 3.99}', ""),
111 # ── control de flujo ────────────────────────────────────────────────────────────────────────
112 ("if-else", 'BEGIN{ if (1) print "a"; else print "b" }', ""),
113 ("while", 'BEGIN{i=0; while(i<3){printf "%d", i; i++}; print ""}', ""),
114 ("do-while", 'BEGIN{i=0; do {printf "%d", i; i++} while(i<3); print ""}', ""),
115 ("for-clasico", 'BEGIN{for(i=0;i<3;i++) printf "%d", i; print ""}', ""),
116 ("for-in", 'BEGIN{a[1];a[2];a[3]; n=0; for(k in a) n++; print n}', ""),
117 ("break-continue", 'BEGIN{for(i=0;i<5;i++){if(i==1)continue; if(i==3)break; printf "%d",i}; print ""}', ""),
118 ("next", '/skip/{next} {print}', "uno\nskip esto\ndos\n"),
119 ("exit-con-end", 'BEGIN{exit 0} END{print "end corre"}', ""),
120 ("patron-rango", '/ini/,/fin/{print}', "a\nini\nx\nfin\nb\n"),
121 ("patron-expresion", 'NR==2{print}', "a\nb\nc\n"),
122 ("patron-sin-accion", 'NR==2', "a\nb\nc\n"),
123 # ── arrays ──────────────────────────────────────────────────────────────────────────────────
124 ("array-subsep", 'BEGIN{a[1,2]="x"; for(k in a){split(k,p,SUBSEP); print p[1],p[2]}}', ""),
125 ("array-in-crea", 'BEGIN{ if (a["k"]=="") print "vacio"; print ("k" in a) }', ""),
126 ("array-delete", 'BEGIN{a[1]=1;a[2]=2; delete a[1]; n=0; for(k in a)n++; print n}', ""),
127 ("array-delete-todo", 'BEGIN{a[1]=1;a[2]=2; delete a; n=0; for(k in a)n++; print n}', ""),
128 ("array-length", 'BEGIN{a[1];a[2]; print length(a)}', ""),
129 # ── funciones de usuario ────────────────────────────────────────────────────────────────────
130 ("funcion-simple", 'function f(x){return x*2} BEGIN{print f(21)}', ""),
131 ("funcion-recursiva", 'function f(n){return n<=1?1:n*f(n-1)} BEGIN{print f(6)}', ""),
132 ("funcion-array-ref", 'function g(a){a["k"]="puesto"} BEGIN{g(A); print A["k"]}', ""),
133 ("funcion-local", 'function f(x, t){t=x*2; return t} BEGIN{t=9; print f(2), t}', ""),
134 ("funcion-sin-return", 'function f(){} BEGIN{print "["f()"]"}', ""),
135 # ── entrada/salida ──────────────────────────────────────────────────────────────────────────
136 ("print-redirigido", 'BEGIN{print "x" > "/dev/stderr"}', ""),
137 ("print-coma-ofs", 'BEGIN{OFS=":"; print "a","b"}', ""),
138 ("print-ors", 'BEGIN{ORS="|"} {print}', "a\nb\n"),
139 ("print-parentesis", 'BEGIN{print (1), (2)}', ""),
140 ("getline-tuberia", 'BEGIN{ "echo hola" | getline x; print x }', ""),
141 ("getline-simple", 'NR==1{ getline; print "leido:" $0 }', "a\nb\nc\n"),
142 ("rs-parrafo", 'BEGIN{RS=""} {print NR": "$1"-"$2}', "a\nb\n\nc\nd\n"),
143 ("rs-caracter", 'BEGIN{RS=":"} {print NR, $0}', "a:b:c"),
144 # ── rincones del parser ─────────────────────────────────────────────────────────────────────
145 ("barra-division", 'BEGIN{a=6; b=3; print a / b}', ""),
146 ("print-mayor-redirige", 'BEGIN{print (1>2)}', ""),
147 ("comentario", 'BEGIN{ # esto es comentario\nprint "ok"}', ""),
148 ("punto-y-coma", 'BEGIN{;;print "ok";;}', ""),
149 ("continuacion-linea", 'BEGIN{print "a" \\\n "b"}', ""),
150 ("cadena-escapes", 'BEGIN{print "a\\tb\\\\c"}', ""),
151 ("expresion-regex-valor", '{print /b/}', "abc\nxyz\n"),
152]
153
154
155def corre(motor, caso, entrada):
156 """Devuelve (salida+error, código)."""
157 args = list(motor)
158 prog = caso
159 # Los casos que empiezan por `-F` traen la opción pegada al programa.
160 if prog.startswith("-F"):
161 i = prog.index(" ")
162 args += [prog[:i]]
163 prog = prog[i + 1:]
164 args.append(prog)
165 try:
166 r = subprocess.run(args, input=entrada, capture_output=True, text=True, timeout=10)
167 return (r.stdout + r.stderr).rstrip("\n"), r.returncode
168 except subprocess.TimeoutExpired:
169 return "@TIMEOUT", -1
170 except Exception as e:
171 return f"@ERROR {e}", -1
172
173
174def main():
175 ap = argparse.ArgumentParser()
176 ap.add_argument("--motor", default=os.path.join(RAIZ, "target/release/akllay"))
177 ap.add_argument("--verbose", action="store_true")
178 a = ap.parse_args()
179
180 bb = del_store("busybox", "usr/bin/awk")
181 gawk = del_store("gawk", "usr/bin/gawk")
182 oraculos = []
183 if bb: oraculos.append(("busybox", [bb]))
184 if gawk: oraculos.append(("gawk", [gawk, "--posix"]))
185 if not oraculos:
186 print("sin oráculos en el store: no se puede comparar", file=sys.stderr)
187 return 2
188
189 print(f"motor: {a.motor}")
190 print(f"oráculos: {', '.join(n for n, _ in oraculos)}\n")
191 igual = discrepan = distinto = 0
192 fallos = []
193 for nombre, prog, entrada in CASOS:
194 mio, _ = corre([a.motor], prog, entrada)
195 refs = [(n, corre(cmd, prog, entrada)[0]) for n, cmd in oraculos]
196 valores = {r for _, r in refs}
197 if len(valores) > 1:
198 discrepan += 1
199 if a.verbose:
200 print(f" ~ {nombre}: los oráculos discrepan " +
201 " · ".join(f"{n}=[{r}]" for n, r in refs) + f" · akllay=[{mio}]")
202 continue
203 esperado = refs[0][1]
204 if mio == esperado:
205 igual += 1
206 else:
207 distinto += 1
208 fallos.append((nombre, prog, entrada, esperado, mio))
209
210 print(f" iguales a los oráculos : {igual}")
211 print(f" DIVERGEN : {distinto}")
212 print(f" los oráculos discrepan : {discrepan} (no cuentan: se deciden por POSIX a mano)")
213 if fallos:
214 print("\n── divergencias ──")
215 for n, p, e, esp, mio in fallos:
216 print(f"\n ✗ {n}\n programa: {p}\n entrada : {e!r}\n oráculos: [{esp}]\n akllay : [{mio}]")
217 return 1 if fallos else 0
218
219
220if __name__ == "__main__":
221 sys.exit(main())