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