gioser

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…

TOML 188Rust 55Shell 30Markdown 281211 Definiciones

⎇ main crates/akllay/src/main.rs Rust · 175 líneas · 7.0 KB Historia Quién tocó qué crudo

el awk se llama akllay: `qillqa` estaba TOMADO en tawasuyu, y no lo comprobé 83720b303 · Sergio ·

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}