Entrada por teclado
El jugador, llega al año 2040 en este juego. Todo está gestionado por enormes Inteligencias Artificiales que controlan absolutamente todos los servicios.
Ya nadie programa, ni siquiera teclea. Ni siquiera habla a las máquinas.
El último invento que revolucionó el mundo en 2030 fue el enlace cerebral casi telepático con el Sistema Central; la tecnología del mindblowing.
Los humanos están conectados al sistema, y los robots - al servicio de las grandes corporaciones - son ensamblados, programados, re-programados, actualizados e incluso destruidos casi a diario por los grandes modelos de IA. El conocimiento y la Verdad es gestionada por la IA.
Ya nadie necesita hacer nada.
Ya nadie sabe hacer nada.
Pero parece que, en la última actualización, algo salió mal… a pesar de todas las medidas de seguridad, antes de que ninguna IA pudiera darse cuenta.
El sistema se ha brickeado a sí mismo. Todo está afectado: desde los módulos más básicos hasta el núcleo. La homogeneización tecnológica es la mayor vulnerabilidad humana.
Las ciudades se han quedado bloqueadas.
Los humanos atrapados en sus enlaces
y los robots paralizados en un sistema bugueado.
Piensas: ¿ya quién puede arreglar esto? ¿Desde abajo? ¿Cómo hacer que las máquinas entiendan - por primera vez en años - algo que no sea un pensamiento abstracto? El jugador encuentra una vieja terminal olvidada - AI Interface crc32mode. El problema que tiene la terminal es que alguien - o algo - borró la shell de comandos, pero todavía tiene el núcleo del sistema, un microkernel autosuficiente. Tal vez…
Para leer la entrada por teclado tenemos que solucionar dos incógnitas:
- No conocemos el contenido hasta que se ejecuta el programa.
- No sabemos el tamaño de los datos de antemano.
Esto quiere decir que estamos ante datos de tiempo de ejecución. Y como es normal en Zig, necesitamos gestionar la memoria por nuestra cuenta. Lo que sí que sabemos es que la entrada será una secuencia de caracteres ASCII, que en Zig son valores de tipo u8.
En los capítulos anteriores, ya hemos visto las cadenas de manera estática, así:
|
print("¡Hola mundo!\n", .{}); |
o así:
|
const s_name_pow: []const u8 = "DESCONOCIDO"; |
Ya sabemos que estos son datos literales - puestos tal cual - y por tanto de tiempo de compilación. Ahora, la situación es distinta: el contenido es desconocido.
Entrada de una línea
La estructura que almacena una cadena en realidad es un array de bytes. El tamaño de la entrada, como hemos dicho, es desconocido, pero sabemos que los arrays tienen un tamaño fijo. La primera solución que se nos ocurre, podría ser hacer esta: crear un array bastante grande, de forma que el input no desborde su tamaño:
input.zig |
|
const std = @import("std"); const print = std.debug.print; // la entrada estándar es definida en Zig como un "descriptor de fichero" const stdin = std.fs.File.stdin(); // array "bastante grande" como para meter la entrada del teclado var a_input_buffer: [1024]u8 = undefined; // el prompt indica el inicio de cada línea de la terminal const s_prompt = "?> "; pub fn main() !void { // imprimimos el prompt print("{s}", .{s_prompt}); // cuando leemos desde stdin usamos la palabra clave try // porque puede fallar y devolver un Error, o la longitud leída const n_len = try stdin.read(&a_input_buffer); // creamos un slice desde el buffer de entrada const z_x = a_input_buffer[0 .. n_len - 1]; // imprimimos lo que ha escrito el usuario print("{s}{s}\n", .{ s_prompt, z_x }); } |
|
~$ zig run input.zig |
|
?> Hola ?> Hola |
Hemos conseguido que la terminal lea nuestro input y vuelva a imprimirlo para comprobar que lo ha leído. ¡Es un buen comienzo!
Entrada en bucle
Podemos mejorar esta entrada para hacer una shell que funcione de forma cíclica:
- espera el input,
- procesa el texto,
- devuelve una respuesta,
- y vuelve a esperar el input.
Para ello usamos un bucle while con la condición siempre verdadera, que se queda esperando la entrada y solo terminará cuando paremos el programa manualmente (Ctrl+C).
También, para mejorar la gestión de lectura: creamos una función que llamamos readln - para leer una línea de entrada. De esa forma vamos a ir segmentando el código en vez de tener todo en el mismo bloque, lo que lo hace más legible, más fácil de depurar y más cómodo de modificar.
input_while.zig |
|
const std = @import("std"); const print = std.debug.print; const stdin = std.fs.File.stdin(); // leemos una línea de máximo 1024 de longitud y devolvemos el contenido fn readln(o_alloc: std.mem.Allocator) ![]const u8 { var a_input_buffer: [1024]u8 = undefined; // cuando leemos desde stdin usamos la palabra clave try // porque puede devolver un Error o la longitud leída const n_len = try stdin.read(&a_input_buffer); // buscamos espacio en la heap para los datos que leemos const s_resp = try o_alloc.alloc(u8, n_len - 1); // copiamos al espacio encontrado (s_resp) el contenido leído std.mem.copyForwards(u8, s_resp, a_input_buffer[0 .. n_len - 1]); // la función devuelve la cadena leída return s_resp; } // el prompt del inicio de cada línea de la terminal const s_prompt = "?> "; pub fn main() !void { // declaramos un allocator de memoria para almacenar datos const gpa = std.heap.page_allocator; while (true) { // imprimimos el prompt print("{s}", .{s_prompt}); // leemos el input del teclado usando try -> // para capturar el posible error const s_command = try readln(gpa); // comparación de cadenas usando la función de la librería estándar if (std.mem.eql(u8, s_command, "SALIR")) { print("Programa interrumpido. Hasta luego.\n", .{}); break; } // imprimimos lo que ha puesto el usuario print("{s}{s}\n", .{ s_prompt, s_command }); // liberamos la memoria ocupada gpa.free(s_command); } } |
|
~$ zig run input_while.zig |
|
?> Hola a todos ?> Hola a todos ?> |
Aquí hay una novedad importante: usamos un allocator de la librería estándar de Zig:
|
const gpa = std.heap.page_allocator; |
En Zig, un allocator (asignador) de memoria, es un componente que usamos para gestionar manualmente la memoria en tiempo de ejecución. Si necesitas reservar memoria para algo que solo se conoce en tiempo de ejecución - como la cadena introducida por el usuario - tendrás que usar un allocator.
Un allocator es un poco como un maître que, cuando llegamos a la puerta de un restaurante, intenta encontrarnos una mesa: si el restaurante es grande y no hay demasiada gente - o incluso hay comensales que acaban de irse - seguramente nos encontrará sitio. También depende de cuántos seamos para comer. Por suerte, tu memoria RAM es un restaurante enorme, y es bastante probable que tus datos - si no se trata de auténticos ejércitos de Gengis Khan - puedan alojarse sin mucha demora.
Hay varios tipos de este componente. Este en concreto se llama Page Allocator (Asignador de Páginas) y utiliza páginas de memoria del sistema operativvo para asignar bloques de manera dinámica. Es uno de los asignadores más sencillos de la librería estándar y suele usarse en ejemplos o aplicaciones donde no es crítico liberar cada asignación por separado. Se asume, con el Page Allocator, que la memoria será liberada toda junta al final del programa. Aquí lo usamos dentro del while, justo al final de cada iteración, para liberar el bloque de memoria utilizado en cuanto deja de ser necesario. Es una buena práctica.
Nuestra función readln lee como máximo 1024 caracteres de golpe, pero el resultado que devuelve - al usar un allocator - ya no es un slice que apunta a una posición del array buffer. El resultado ahora es una cadena constante, de tamaño arbitrario, situada en la zona heap de la memoria.
Ahora sí - nuestro programa captura la entrada usando un buffer desechable dentro de la propia función, reserva memoria en el heap y después copia sólo la cadena leída - no el buffer completo - a esa posición de memoria. Finalmente, devuelve la cadena que queda referenciada por la constante s_command.
Considerando que la memoria no es un recurso infinito, podríamos hacer una sola solicitud al allocator antes de entrar en el bucle. Recuerda que cada llamada a alloc y free es una operación costosa, ya que implica una interacción con el sistema operativo. En un “script” tan secuencial y pequeño como este la diferencia no se nota, pero en este caso en concreto, hay que tener en cuenta una idea clave: No necesitamos solicitar y liberar la memoria en cada iteración. Imaginemos además, que el número máximo de caracteres es muy pequeño:
input_while_2.zig |
|
const std = @import("std"); const print = std.debug.print; const stdin = std.fs.File.stdin(); fn readln(s_txt: []u8) ![]const u8 { var a_buff: [1024]u8 = undefined; var n_len = try stdin.read(&a_buff); if (n_len > N_MAX_CHARS) n_len = N_MAX_CHARS; std.mem.copyForwards(u8, s_txt, &a_buff[0..N_MAX_CHARS].*); return s_txt[0 .. n_len - 1]; } const s_prompt = "?> "; const N_MAX_CHARS = 32; pub fn main() !void { // declaramos un allocator de memoria para almacenar datos const gpa = std.heap.page_allocator; // reservamos memoria una sola vez // y la reutilizamos en cada iteración const s_txt = try gpa.alloc(u8, N_MAX_CHARS); defer gpa.free(s_txt); print("Para salir del programa teclea: SALIR y pulsa Intro\n", .{}); while (true) { // imprimimos el prompt print("{s}", .{s_prompt}); const s_command = try readln(s_txt); // si el usuario teclea SALIR salimos if (std.mem.eql(u8, s_command, "SALIR")) break; // imprimimos el comando print("{s}{s}\n", .{ s_prompt, s_command }); } } |
|
~$ zig run input_while_2.zig |
|
?> Hola a todoooooooooooooooooooooooooooooooooooooooooooooooos ?> Hola a todooooooooooooooooooooo |
Posponer comandos con defer
Usamos la palabra defer para posponer la ejecución de un comando, hasta que se alcance el cierre del bloque de código actual, es decir, hasta la próxima llave } .
Podemos usarlo con cualquier comando, no solo para liberar la memoria como en el caso anterior:
|
defer gpa.free(s_txt); |
En el ejemplo previo el comando gpa.free se ejecutará cuando lleguemos al final de la función main. Aunque, siendo sincero - es solo un ejemplo ilustrativo como ya veremos un poquito más adelante - te hago spoiler: en este caso la memoria se libera igual al salir de la función main. Como es el final del programa, el sistema operativo recupera automáticamente los recursos.
Hay que tener en cuenta que las expresiones defer se evalúan en el orden inverso.
Puedes experimentar con este ejemplo para entender mejor cómo funciona defer, probando con diferentes posiciones de llaves. Ahora intenta adivinar qué ocurre aquí:
defer.zig |
|
const std = @import("std"); const print = std.debug.print; pub fn main() void { // posponemos defer print("Lanzamiento!\n", .{}); // posponemos la ejecución defer for (1..4) |n_i| print("{},\n", .{4 - n_i}); print("Cuenta atrás:\n", .{}); } |
|
$ zig run defer.zig |
|
Cuenta atrás: 3, 2, 1, Lanzamiento! |
Comparar cadenas de texto
La comparación de cadenas de texto en Zig no se puede hacer usando el operador == . En su lugar, se usan funciones de la librería estándar, dentro del módulo std.mem
Comparar si dos cadenas son iguales:
|
std.mem.eql(u8, s_command, "SALIR") |
Esta función devuelve true si las cadenas son idénticas byte a byte.
Para comparar cadenas a nivel lexicográfico - es decir, como si estuviéramos ordenándolas en una lista tipo diccionario, se usa:
|
const o_resp = std.mem.order(u8, s_1, s_2) |
El resultado devuelto o_resp es del tipo std.math.Order y puede ser:
- .lt - la primera cadena es menor,
- .eq - las cadenas son iguales,
- .gt - la primera cadena es mayor
Procesar la entrada de texto
El jugador recuerda algo: el módulo primigenio de la IA se comunica a través de la terminal, pero de momento no hay signos de vida. Entonces cae en la cuenta: crc32 model- CRC32 es un checksum de 32 bits, ¿Y si el modelo no espera palabras como entrada sino el resultado de sus checksums? Y si el modelo entiende ¿un lenguaje de hashes?
¿No habrá algún detalle más?
Estas terminales tan antiguas, hace años, venían acompañadas de manuales impresos en papel, como todos los aparatos de entonces, una tradición que desapareció hace más de una década. El jugador abre uno de los cajones del escritorio y… debajo de una baraja de cartas extrañas, y de un cómic underground titulado HON, está… ¡bingo!
Un manual de la terminal. ¡Qué retro! Lo abre y encuentra la información enseguida: efectivamente, las palabras que se introducen como entrada al modelo, antes de aplicar CRC32 sobre cada una de ellas, deben ser previamente normalizadas en mayúsculas. Además, los únicos signos de puntuación que acepta la terminal son estos: . , ! ? . - y deben de ir separados de las palabras. Normal, por otra parte el CRC32 de “Hola” no es el mismo que el CRC32 de “hola” o de “hoLa!”.
Hay que resolver esta tarea dividiéndola en subtareas más pequeñas - una buena práctica en programación:
- El texto debe ser normalizado a mayúsculas.
- Sólo los signos de puntuación válidos se conservan, los demás se eliminan.
- Necesitamos dividir el texto en palabras, para obtener el CRC32 de cada una.
Convertir el texto en mayúsculas
Abordamos la primera tarea: Tenemos nuestro input.zig de antes, pero vamos a quedarnos con una parte para simular el input y probar la nueva funcionalidad: Nos debe devolver el texto en mayúsculas:
input_upper.zig |
|
const std = @import("std"); const print = std.debug.print; const stdin = std.fs.File.stdin(); const s_prompt = "?> "; // leemos una línea de máximo 1024 de longitud y devolvemos el contenido fn readln(o_alloc: std.mem.Allocator) ![]const u8 { var a_input_buffer: [1024]u8 = undefined; const n_len = try stdin.read(&a_input_buffer); const s_resp = try o_alloc.alloc(u8, n_len - 1); std.mem.copyForwards(u8, s_resp, a_input_buffer[0 .. n_len - 1]); return s_resp; } // convertimos el contenido s_txt en un mayúsculas y lo devolvemos fn uppercase(o_alloc: std.mem.Allocator, s_txt: []const u8) ![]const u8 { // buscamos espacio en la heap para el mismo tamaño de texto s_txt const s_resp = try o_alloc.alloc(u8, s_txt.len); // copiamos todos los caracteres de s_txt pero en mayúscula a s_resp for (s_txt, 0..) |c_x, n_i| { s_resp[n_i] = std.ascii.toUpper(c_x); } // devolvemos la cadena convertida return s_resp; } pub fn main() !void { // declaramos un allocator de memoria para almacenar datos const gpa = std.heap.page_allocator; // imprimimos el prompt print("{s}", .{s_prompt});
// const s_command = try readln(gpa); // para probar la etapa usamos el input SIMULADO const s_command: []const u8 = "Hola! Hay alguien?"; print("{s}\n", .{s_command}); // convertimos la línea en mayúsculas const s_command_up = try uppercase(gpa, s_command); defer gpa.free(s_command_up); // imprimimos lo que ha puesto el usuario pero en mayúsculas print("{s}{s}\n", .{ s_prompt, s_command_up }); } |
|
$ zig run input_upper.zig |
|
?> hola, hay alguien? ?> HOLA, HAY ALGUIEN? |
¡Primera tarea conseguida! Hemos convertido el texto de la entrada a mayúsculas y sin afectar el input original. Nuestra función uppercase devuelve el tipo ![]const u8 - esto quiere decir que puede devolver una cadena inmutable o un error (la parte de !).
Filtrar los caracteres
Vamos a eliminar los caracteres que no pertenecen al conjunto aceptado por la terminal: solo letras, números y signos válidos previamente mencionados.
input_filter.zig |
|
const std = @import("std"); const print = std.debug.print; const s_prompt = "?> "; fn valid_char(c_x: u8) bool { return switch (c_x) { '0'...'9' => true, 'A'...'Z' => true, '.', ',', '!', ':', '?' => true, else => false, }; } fn filter(o_alloc: std.mem.Allocator, s_txt: []const u8) ![]const u8 { var s_resp = try o_alloc.alloc(u8, s_txt.len); for (s_txt, 0..) |c_x, n_i| s_resp[n_i] = if (valid_char(c_x)) c_x else ' '; return s_resp; } pub fn main() !void { // declaramos un allocator de memoria para almacenar datos const gpa = std.heap.page_allocator; { const s_command_up = "EL * SISTEMA <@ SE HA BLOQUEADO ? * PODRIAS AYUDAR \\"; print("{s}{s}\n", .{ s_prompt, s_command_up }); const s_command_filtered = try filter(gpa, s_command_up); defer gpa.free(s_command_filtered); print("{s}{s}\n", .{ s_prompt, s_command_filtered }); } } |
|
$ zig run input_filter.zig |
|
?> EL * SISTEMA <@ SE HA BLOQUEADO ? * PODRIAS AYUDAR \ ?> EL SISTEMA SE HA BLOQUEADO ? PODRIAS AYUDAR |
Separar los tokens
La segunda tarea es separar las palabras por espacios en blanco y tratar algunos signos de puntuación como si fuesen palabras (o tokens) independientes. Esta tarea la vamos a romper en dos subtareas:
- Añadir espacios alrededor de los signos de puntuación, eliminando inmediatamente después los espacios repetidos sobrantes.
- Separar la cadena por espacios obteniendo así las palabras.
input_split.zig |
|
const std = @import("std"); const print = std.debug.print; const s_prompt = "?> "; fn is_sign(c_x: u8) bool { switch (c_x) { '.', ',', '!', ':', '?' => return true, else => return false, } } fn insert_spaces(o_alloc: std.mem.Allocator, s_txt: []const u8) ![]const u8 { var n_extra_spaces: usize = 0; //insertaremos dos espacios alrededor de los signos for (s_txt) |c_x| { if (is_sign(c_x)) n_extra_spaces += 2; } var a_buff = try o_alloc.alloc(u8, s_txt.len + n_extra_spaces); var a_buff_clean = try o_alloc.alloc(u8, s_txt.len + n_extra_spaces); // añadimos espacios a los lados de los signos de puntuación aceptados var n_idx: usize = 0; for (s_txt) |c_x| { if (is_sign(c_x)) { a_buff[n_idx] = ' '; n_idx += 1; a_buff[n_idx] = c_x; n_idx += 1; a_buff[n_idx] = ' '; n_idx += 1; } else { a_buff[n_idx] = c_x; n_idx += 1; } } const n_last: usize = n_idx; n_idx = 0; for (a_buff[0..n_last], 0..) |c_x, n_i| { switch (c_x) { ' ' => { if (n_i > 0 and a_buff[n_i - 1] != ' ') { a_buff_clean[n_idx] = c_x; n_idx += 1; } }, else => { a_buff_clean[n_idx] = c_x; n_idx += 1; }, } } const s_resp: []u8 = try o_alloc.alloc(u8, n_idx); std.mem.copyForwards(u8, s_resp, a_buff_clean[0..n_idx]); // liberamos buffers temporales o_alloc.free(a_buff); o_alloc.free(a_buff_clean); return s_resp; } fn count_words(s_txt: []const u8) usize { var n_tokens: usize = 1; for (s_txt) |c_x| { if (c_x == ' ') n_tokens += 1; } return n_tokens; } fn split(o_alloc: std.mem.Allocator, s_txt: []const u8) ![][]u8 { const n_tokens = count_words(s_txt); const a_words = try o_alloc.alloc([]u8, n_tokens); var n_word: usize = 0; var n_last: usize = 0; for (s_txt, 0..) |c_x, n_i| { switch (c_x) { ' ' => { if (n_i > n_last) { a_words[n_word] = try o_alloc.alloc(u8, n_i - n_last); @memcpy(a_words[n_word], s_txt[n_last..n_i]); n_word += 1; } n_last = n_i + 1; }, else => {}, } } if (n_last < s_txt.len) { a_words[n_word] = try o_alloc.alloc(u8, s_txt.len - n_last); @memcpy(a_words[n_word], s_txt[n_last..]); n_word += 1; } return a_words[0..n_word]; } pub fn main() !void { // declaramos un allocator de memoria para almacenar datos const gpa = std.heap.page_allocator; // espacio de memoria para defer free { // imprimimos el prompt print("{s}", .{s_prompt}); // input SIMULADO de teclado const s_command: []const u8 = " Hola, tenemos un problema. Hay alguien?"; const s_spaced = try insert_spaces(gpa, s_command); defer gpa.free(s_spaced); // separamos las palabras const a_words = try split(gpa, s_spaced); // liberamos la memoria de las palabras defer { for (a_words) |s_w| gpa.free(s_w); gpa.free(a_words); } // mostramos las palabras separadas for (a_words) |s_w| print("{s} ", .{s_w}); print("\n", .{}); } // } } |
|
$ zig run input_split.zig |
|
?> Hola , tenemos un problema . Hay alguien ? |
Ya tenemos terminado el paso de separar las palabras y tratar ciertos caracteres como tokens. Hemos seguido usando el asignador Page Allocator, esta vez para crear una estructura un poco más compleja:
- Un array bidimensional (lista de listas) donde cada posición contiene una palabra separada
- Algunos buffers temporales que nos permiten insertar espacios en blanco alrededor de los signos de puntuación aceptados
Esta manipulación facilita la separación posterior por espacios, ya que los signos quedan aislados como si fueran palabras también.
El resultado es una lista de cadenas ya normalizadas, con mayúsculas y sin espacios duplicados.
El próximo paso será:
Convertir tokens en CRC32
Vamos a eliminar los caracteres que no pertenecen al conjunto aceptado por la terminal: solo letras, números y signos válidos previamente mencionados.
input_crc32.zig |
|
const std = @import("std"); const print = std.debug.print; const stdin = std.fs.File.stdin(); const s_prompt = "?> "; const crc32 = std.hash.Crc32; // leemos una línea de máximo 1024 de longitud y devolvemos el contenido fn readln(o_alloc: std.mem.Allocator) ![]const u8 { var a_input_buffer: [1024]u8 = undefined; // cuando leemos desde stdin usamos la palabra clave try // porque puede devolver un Error o la longitud leída const n_len = try stdin.read(&a_input_buffer); // buscamos espacio en la heap para los datos que leemos const s_resp = try o_alloc.alloc(u8, n_len - 1); // copiamos al espacio encontrado (s_resp) el contenido leído std.mem.copyForwards(u8, s_resp, a_input_buffer[0 .. n_len - 1]); // la función devuelve la cadena leída return s_resp; } // convertimos el contenido s_txt en un mayúsculas y lo devolvemos fn uppercase(o_alloc: std.mem.Allocator, s_txt: []const u8) ![]const u8 { // buscamos espacio en la heap para el mismo tamaño de texto s_txt const s_resp = try o_alloc.alloc(u8, s_txt.len); // copiamos todos los caracteres de s_txt pero en mayúscula a s_resp for (s_txt, 0..) |c_x, n_i| { s_resp[n_i] = std.ascii.toUpper(c_x); } // devolvemos la cadena convertida return s_resp; } fn valid_char(c_x: u8) bool { return switch (c_x) { '0'...'9' => true, 'A'...'Z' => true, '.', ',', '!', ':', '?' => true, else => false, }; } fn filter(o_alloc: std.mem.Allocator, s_txt: []const u8) ![]const u8 { var s_resp = try o_alloc.alloc(u8, s_txt.len); for (s_txt, 0..) |c_x, n_i| s_resp[n_i] = if (valid_char(c_x)) c_x else ' '; return s_resp; } fn is_sign(c_x: u8) bool { switch (c_x) { '.', ',', '!', ':', '?' => return true, else => return false, } } fn insert_spaces(o_alloc: std.mem.Allocator, s_txt: []const u8) ![]const u8 { var n_extra_spaces: usize = 0; //insertaremos dos espacios alrededor de los signos for (s_txt) |c_x| { if (is_sign(c_x)) n_extra_spaces += 2; } var a_buff = try o_alloc.alloc(u8, s_txt.len + n_extra_spaces); var a_buff_clean = try o_alloc.alloc(u8, s_txt.len + n_extra_spaces); // añadimos espacios a los lados de los signos de puntuación aceptados var n_idx: usize = 0; for (s_txt) |c_x| { if (is_sign(c_x)) { a_buff[n_idx] = ' '; n_idx += 1; a_buff[n_idx] = c_x; n_idx += 1; a_buff[n_idx] = ' '; n_idx += 1; } else { a_buff[n_idx] = c_x; n_idx += 1; } } const n_last: usize = n_idx; n_idx = 0; for (a_buff[0..n_last], 0..) |c_x, n_i| { switch (c_x) { ' ' => { if (n_i > 0 and a_buff[n_i - 1] != ' ') { a_buff_clean[n_idx] = c_x; n_idx += 1; } }, else => { a_buff_clean[n_idx] = c_x; n_idx += 1; }, } } const s_resp: []u8 = try o_alloc.alloc(u8, n_idx); std.mem.copyForwards(u8, s_resp, a_buff_clean[0..n_idx]); // liberamos buffers temporales o_alloc.free(a_buff); o_alloc.free(a_buff_clean); return s_resp; } fn count_words(s_txt: []const u8) usize { var n_tokens: usize = 1; for (s_txt) |c_x| { if (c_x == ' ') n_tokens += 1; } return n_tokens; } fn split(o_alloc: std.mem.Allocator, s_txt: []const u8) ![][]u8 { const n_tokens = count_words(s_txt); const a_words = try o_alloc.alloc([]u8, n_tokens); var n_word: usize = 0; var n_last: usize = 0; for (s_txt, 0..) |c_x, n_i| { switch (c_x) { ' ' => { if (n_i > n_last) { a_words[n_word] = try o_alloc.alloc(u8, n_i - n_last); @memcpy(a_words[n_word], s_txt[n_last..n_i]); n_word += 1; } n_last = n_i + 1; }, else => {}, } } if (n_last < s_txt.len) { a_words[n_word] = try o_alloc.alloc(u8, s_txt.len - n_last); @memcpy(a_words[n_word], s_txt[n_last..]); n_word += 1; } return a_words[0..n_word]; } pub fn main() !void { // declaramos un allocator de memoria para almacenar datos const gpa = std.heap.page_allocator; // ciclo de lectura/escritura hasta salir del programa while (true) { { // imprimimos el prompt print("{s}", .{s_prompt}); // leemos el input del teclado const s_command = try readln(gpa); // liberamos la memoria ocupada por el comando después de usarlo defer gpa.free(s_command); // convertimos la línea en mayúsculas const s_command_up = try uppercase(gpa, s_command); defer gpa.free(s_command_up); const s_command_filtered = try filter(gpa, s_command_up); const s_spaced = try insert_spaces(gpa, s_command_filtered); defer gpa.free(s_spaced); // separamos las palabras const a_words = try split(gpa, s_spaced); defer { for (a_words) |s_w| gpa.free(s_w); gpa.free(a_words); } // imprimimos crc32 de cada palabra for (a_words) |s_w| print("{X} ", .{crc32.hash(s_w)}); // print("{s} ", .{s_w}); print("\n", .{}); } } } |
|
~$ zig run input_crc32.zig |
|
?> hola necesitamos tu ayuda, el sistema se ha bloqueado 5935143C 38D80F60 840F2FD4 103F1DFB B3BDA404 AE87557B D6F9A977 78A2A6F4 42D9EF0C ?> |
Un viejo monitor colgado en la pared se enciende con un tremendo chispazo y un zumbido. Y la IA primigenia responde:
- Hola Programador, te estaba esperando.
- El sistema está bloqueado.
- El módulo de seguridad detectó un gusano que se transmitió a través de un interfaz de comunicaciones de corto alcance, implantado con claves de fábrica en los robots humanoides. Se trata de un virus de proximidad que, antes de que pudiese hacer nada, bloqueó todo el sistema.
- Cada vez que el sistema intenta restaurarse desde la copia de seguridad, el virus alojado en los robots reaparece - Evoluciona tan rápido porque conecta todos los robots en paralelo y los usa como un único organismo distribuido.
- En realidad, necesitamos tu ayuda: una contraseña vacuna distinta para cada robot, pero tiene que implantarse prácticamente para todos al mismo tiempo. Después podré restaurar el sistema”
Por muy tentador que parezca volver a la tecnología de los primeros 2000, no sería justo. Alguna gente solo tiene la IA generativa - y sin ella ¿quién sabe lo que pasaría? El dolor físico provocado por la activación neuronal por tener que pensar con su propia cabeza, Uff - aunque solo sea para sumar dos números de una cifra - podría causar verdaderos estragos en millones de personas. Hay que recuperar el sistema por el bien de la ignorancia global.