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 815Rust 56Shell 37Markdown 291229 Definiciones

⎇ ae6f0b45d94eb275e2464d898b35ee9a64595f43 crates/qillqa/src/main.rs Rust · 164 líneas · 6.1 KB Historia Quién tocó qué crudo

qillqa: el awk propio — 3531 líneas, sólo libc, y CERO divergencias contra dos oráculos ae6f0b45d · Sergio ·

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}