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…
qillqa: el awk propio — 3531 líneas, sólo libc, y CERO divergencias contra dos oráculos
1//! qillqa — el awk del producto.
2//!
3//! *qillqa* es «escritura» en quechua, y awk es la herramienta de escribir sobre texto. El nombre
4//! sigue el criterio del proyecto (los nombres propios van en quechua) y el precedente de `simi`:
5//! **el nombre lo registra este comentario y lo DECIDE un ADR**, igual que el del shell. Lo que no
6//! está sujeto a decisión es el nombre del comando instalado: la receta enlaza `awk`, porque eso es
7//! contrato y lo llaman los `configure` de medio corpus.
8//!
9//! Uso: qillqa [-F sepc] [-v var=valor]… 'programa' [fichero | var=valor]…
10//! qillqa [-F sepc] [-v var=valor]… -f fuente.awk [-f más.awk]… [fichero | var=valor]…
11
12mod ast;
13mod formato;
14mod interp;
15mod lexer;
16mod parser;
17mod regex;
18mod valor;
19
20use interp::{Flujo, Interprete};
21use std::io::Write;
22use valor::Valor;
23
24fn uso() -> ! {
25 eprintln!("uso: qillqa [-F sepc] [-v var=valor]... 'programa' [fichero...]");
26 eprintln!(" qillqa [-F sepc] [-v var=valor]... -f fuente.awk [fichero...]");
27 std::process::exit(2);
28}
29
30fn main() {
31 let argv: Vec<String> = std::env::args().collect();
32 let mut fuentes: Vec<String> = Vec::new();
33 let mut asignaciones: Vec<String> = Vec::new();
34 let mut fs: Option<String> = None;
35 let mut texto_programa: Option<String> = None;
36 let mut i = 1;
37
38 while i < argv.len() {
39 let a = &argv[i];
40 if a == "--" { i += 1; break; }
41 if a == "-" || !a.starts_with('-') { break; }
42 // Las opciones se pueden pegar al valor (`-F:`) o separar (`-F :`), como en todo POSIX.
43 let (op, pegado) = {
44 let cs: Vec<char> = a.chars().collect();
45 (cs[1], cs[2..].iter().collect::<String>())
46 };
47 let mut toma = |pegado: &str, i: &mut usize| -> String {
48 if !pegado.is_empty() { return pegado.to_string(); }
49 *i += 1;
50 argv.get(*i).cloned().unwrap_or_else(|| uso())
51 };
52 match op {
53 'F' => {
54 let v = toma(&pegado, &mut i);
55 // `-F '\t'` llega con la barra literal: hay que desescaparlo como una cadena.
56 // Y `-F t` es el tabulador por compatibilidad histórica con el awk original.
57 fs = Some(if v == "t" { "\t".to_string() } else { interp::desescapar(&v) });
58 }
59 'v' => asignaciones.push(toma(&pegado, &mut i)),
60 'f' => {
61 let v = toma(&pegado, &mut i);
62 match std::fs::read_to_string(&v) {
63 Ok(s) => fuentes.push(s),
64 Err(e) => { eprintln!("qillqa: no puedo leer `{v}`: {e}"); std::process::exit(2); }
65 }
66 }
67 _ => uso(),
68 }
69 i += 1;
70 }
71
72 if fuentes.is_empty() {
73 match argv.get(i) {
74 None => uso(),
75 Some(p) => { texto_programa = Some(p.clone()); i += 1; }
76 }
77 }
78 let fuente = match texto_programa { Some(t) => t, None => fuentes.join("\n") };
79
80 let prog = match parser::Parser::nuevo(&fuente).and_then(|mut p| p.programa()) {
81 Ok(p) => p,
82 Err(e) => { eprintln!("qillqa: {e}"); std::process::exit(2); }
83 };
84
85 let mut it = Interprete::nuevo(prog);
86
87 // ENVIRON, antes que nada: un BEGIN puede leerlo.
88 {
89 let env = it.array_publico("ENVIRON");
90 for (k, v) in std::env::vars() {
91 env.borrow_mut().insert(k, Valor::de_entrada(&v));
92 }
93 }
94 if let Some(f) = fs { it.poner_var("FS", Valor::Cad(f)); }
95 for a in &asignaciones {
96 match interp::posicion_asignacion(a) {
97 Some(p) => it.poner_var(&a[..p], Valor::de_entrada(&interp::desescapar(&a[p + 1..]))),
98 None => { eprintln!("qillqa: `-v {a}` no tiene forma var=valor"); std::process::exit(2); }
99 }
100 }
101
102 // ARGV/ARGC: ARGV[0] es el nombre del programa y los operandos van detrás.
103 {
104 let operandos: Vec<String> = argv[i..].to_vec();
105 let arr = it.array_publico("ARGV");
106 arr.borrow_mut().insert("0".into(), Valor::Cad("qillqa".into()));
107 for (n, o) in operandos.iter().enumerate() {
108 arr.borrow_mut().insert((n + 1).to_string(), Valor::de_entrada(o));
109 }
110 it.poner_var("ARGC", Valor::Num((operandos.len() + 1) as f64));
111 }
112
113 let mut codigo = 0;
114 let mut salir_ya = false;
115
116 match it.ejecutar_begin() {
117 Err(e) => { fin_con_error(&mut it, &e); }
118 Ok(Flujo::Sale(c)) => { codigo = c; salir_ya = true; }
119 Ok(_) => {}
120 }
121
122 // El bucle principal sólo corre si hay reglas que no sean BEGIN. Si el programa es *sólo* un
123 // BEGIN, awk NO lee la entrada — y eso importa: `awk 'BEGIN{print 1}'` no se cuelga esperando.
124 if !salir_ya && (it.hay_main() || it.hay_end()) {
125 'registros: loop {
126 let r = match it.siguiente_registro() { None => break, Some(r) => r };
127 it.poner_registro(r);
128 match it.ejecutar_reglas() {
129 Err(e) => { fin_con_error(&mut it, &e); }
130 Ok(Flujo::Sale(c)) => { codigo = c; salir_ya = true; break 'registros; }
131 Ok(Flujo::SiguienteFichero) => { it.cerrar_fuente_actual(); }
132 Ok(_) => {}
133 }
134 }
135 }
136
137 if !salir_ya || it.hay_end() {
138 // POSIX: un `exit` en BEGIN o en el cuerpo SÍ ejecuta los END, salvo que el propio END
139 // llame a `exit`. Por eso se entra acá incluso con `salir_ya`.
140 match it.ejecutar_end() {
141 Err(e) => { fin_con_error(&mut it, &e); }
142 Ok(Flujo::Sale(c)) => { codigo = c; }
143 Ok(_) => {}
144 }
145 }
146
147 it.cerrar_todo();
148 if it.codigo_salida != 0 && codigo == 0 { codigo = it.codigo_salida; }
149 std::process::exit(codigo);
150}
151
152fn fin_con_error(it: &mut Interprete, e: &str) -> ! {
153 // `@salir:N` es cómo viaja un `exit` desde dentro de una función: no es un error de verdad.
154 if let Some(n) = e.strip_prefix("@salir:") {
155 let c = n.parse().unwrap_or(0);
156 let _ = it.ejecutar_end();
157 it.cerrar_todo();
158 std::process::exit(c);
159 }
160 it.cerrar_todo();
161 let _ = std::io::stderr().flush();
162 eprintln!("qillqa: {e}");
163 std::process::exit(2);
164}