Gerador de Lorem Ipsum em Zig — Tutorial Passo a Passo
Neste tutorial, vamos construir um gerador de texto Lorem Ipsum no terminal. O programa gera texto placeholder com quantidade configurável de parágrafos, frases e palavras. Este projeto é ideal para praticar manipulação de arrays, geração aleatória e formatação de saída em Zig.
O Que Vamos Construir
Nosso gerador vai:
- Gerar parágrafos de texto Lorem Ipsum realista
- Permitir configurar número de parágrafos, frases por parágrafo e palavras por frase
- Incluir o parágrafo clássico “Lorem ipsum dolor sit amet…” como primeira opção
- Usar um vocabulário latino para gerar texto que parece natural
- Suportar saída formatada com quebras de linha adequadas
Por Que Este Projeto?
Este projeto nos ensina a trabalhar com arrays de strings constantes em comptime, geração de números aleatórios para seleção de elementos, e construção de strings dinâmicas com ArrayList. É um projeto leve mas que cobre padrões muito úteis em Zig.
Pré-requisitos
- Zig 0.13+ instalado (guia de instalação)
- Familiaridade com arrays e slices em Zig (fundamentos)
Passo 1: Estrutura do Projeto
mkdir gerador-lorem-ipsum
cd gerador-lorem-ipsum
zig init
Passo 2: Vocabulário Latino
O Lorem Ipsum usa um vocabulário pseudo-latino. Definimos as palavras como um array constante em comptime.
const std = @import("std");
const io = std.io;
const fmt = std.fmt;
const mem = std.mem;
const ArrayList = std.ArrayList;
/// Vocabulário de palavras latinas usadas no Lorem Ipsum.
/// Em Zig, arrays constantes de strings são armazenados em .rodata
/// e não ocupam memória heap. O compilador conhece o tamanho total
/// em tempo de compilação.
const vocabulario = [_][]const u8{
"lorem", "ipsum", "dolor", "sit", "amet",
"consectetur", "adipiscing", "elit", "sed", "do",
"eiusmod", "tempor", "incididunt", "ut", "labore",
"et", "dolore", "magna", "aliqua", "enim",
"ad", "minim", "veniam", "quis", "nostrud",
"exercitation", "ullamco", "laboris", "nisi", "aliquip",
"ex", "ea", "commodo", "consequat", "duis",
"aute", "irure", "in", "reprehenderit", "voluptate",
"velit", "esse", "cillum", "fugiat", "nulla",
"pariatur", "excepteur", "sint", "occaecat", "cupidatat",
"non", "proident", "sunt", "culpa", "qui",
"officia", "deserunt", "mollit", "anim", "id",
"est", "laborum", "ac", "ante", "bibendum",
"blandit", "congue", "cras", "cursus", "dapibus",
"diam", "dictum", "dignissim", "donec", "egestas",
"elementum", "etiam", "euismod", "facilisis", "faucibus",
"felis", "fermentum", "fringilla", "gravida", "habitant",
"hendrerit", "iaculis", "imperdiet", "integer", "interdum",
"justo", "lacinia", "lacus", "laoreet", "lectus",
"leo", "libero", "ligula", "lobortis", "luctus",
"maecenas", "massa", "mattis", "mauris", "metus",
};
/// O parágrafo clássico de Lorem Ipsum que sempre inicia o texto.
const paragrafo_classico =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " ++
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim " ++
"ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " ++
"aliquip ex ea commodo consequat. Duis aute irure dolor in " ++
"reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " ++
"pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " ++
"culpa qui officia deserunt mollit anim id est laborum.";
Passo 3: Configuração do Gerador
/// Configuração para geração de texto.
/// Valores padrão produzem texto com aparência realista.
const ConfigGeracao = struct {
paragrafos: u32 = 3,
frases_por_paragrafo_min: u32 = 3,
frases_por_paragrafo_max: u32 = 7,
palavras_por_frase_min: u32 = 5,
palavras_por_frase_max: u32 = 15,
iniciar_com_classico: bool = true,
/// Valida a configuração.
pub fn validar(self: ConfigGeracao) !void {
if (self.paragrafos == 0 or self.paragrafos > 100) return error.ParagrafosInvalidos;
if (self.frases_por_paragrafo_min == 0) return error.FrasesInvalidas;
if (self.frases_por_paragrafo_min > self.frases_por_paragrafo_max) return error.FrasesInvalidas;
if (self.palavras_por_frase_min < 3) return error.PalavrasInvalidas;
if (self.palavras_por_frase_min > self.palavras_por_frase_max) return error.PalavrasInvalidas;
}
};
Passo 4: Gerador de Texto
/// Gerador de texto Lorem Ipsum.
/// Usa um PRNG para selecionar palavras aleatórias do vocabulário
/// e as organiza em frases e parágrafos com formatação adequada.
const GeradorLorem = struct {
rng: std.Random,
config: ConfigGeracao,
const Self = @This();
pub fn init(rng: std.Random, config: ConfigGeracao) Self {
return .{ .rng = rng, .config = config };
}
/// Seleciona uma palavra aleatória do vocabulário.
fn palavraAleatoria(self: *Self) []const u8 {
const idx = self.rng.intRangeAtMost(usize, 0, vocabulario.len - 1);
return vocabulario[idx];
}
/// Gera um número aleatório dentro de um intervalo.
fn intAleatorio(self: *Self, min: u32, max: u32) u32 {
return self.rng.intRangeAtMost(u32, min, max);
}
/// Capitaliza a primeira letra de um slice, escrevendo no buffer.
fn capitalizar(palavra: []const u8, buf: []u8) []const u8 {
if (palavra.len == 0) return "";
const len = @min(palavra.len, buf.len);
@memcpy(buf[0..len], palavra[0..len]);
if (buf[0] >= 'a' and buf[0] <= 'z') {
buf[0] -= 32; // ASCII: 'a' - 'A' = 32
}
return buf[0..len];
}
/// Gera uma frase aleatória.
/// A frase começa com maiúscula e termina com ponto.
pub fn gerarFrase(self: *Self, writer: anytype) !void {
const num_palavras = self.intAleatorio(
self.config.palavras_por_frase_min,
self.config.palavras_por_frase_max,
);
var i: u32 = 0;
while (i < num_palavras) : (i += 1) {
const palavra = self.palavraAleatoria();
if (i == 0) {
// Capitalizar primeira palavra
var buf: [64]u8 = undefined;
const cap = capitalizar(palavra, &buf);
try writer.print("{s}", .{cap});
} else {
try writer.print(" {s}", .{palavra});
}
// Inserir vírgula ocasionalmente (não na última palavra)
if (i > 0 and i < num_palavras - 1 and self.rng.intRangeAtMost(u32, 0, 7) == 0) {
try writer.print(",", .{});
}
}
try writer.print(". ", .{});
}
/// Gera um parágrafo completo.
pub fn gerarParagrafo(self: *Self, writer: anytype) !void {
const num_frases = self.intAleatorio(
self.config.frases_por_paragrafo_min,
self.config.frases_por_paragrafo_max,
);
var i: u32 = 0;
while (i < num_frases) : (i += 1) {
try self.gerarFrase(writer);
}
}
/// Gera o texto completo conforme a configuração.
pub fn gerar(self: *Self, writer: anytype) !void {
var p: u32 = 0;
while (p < self.config.paragrafos) : (p += 1) {
if (p == 0 and self.config.iniciar_com_classico) {
try writer.print("{s}", .{paragrafo_classico});
} else {
try self.gerarParagrafo(writer);
}
try writer.print("\n", .{});
if (p < self.config.paragrafos - 1) {
try writer.print("\n", .{});
}
}
}
};
Decisão de design: O gerador escreve diretamente para um writer em vez de retornar uma string alocada. Isso é idiomático em Zig — usamos writer interfaces para evitar alocações desnecessárias e permitir que o chamador decida onde o texto vai (stdout, arquivo, buffer).
Passo 5: Contadores e Estatísticas
/// Conta palavras, frases e caracteres em um texto.
const EstatisticasTexto = struct {
caracteres: usize = 0,
palavras: usize = 0,
frases: usize = 0,
paragrafos: usize = 0,
pub fn analisar(texto: []const u8) EstatisticasTexto {
var stats = EstatisticasTexto{};
var em_palavra = false;
stats.caracteres = texto.len;
if (texto.len > 0) stats.paragrafos = 1;
for (texto, 0..) |c, i| {
// Contar palavras
if (c != ' ' and c != '\n' and c != '\t') {
if (!em_palavra) {
stats.palavras += 1;
em_palavra = true;
}
} else {
em_palavra = false;
}
// Contar frases (terminam com . ! ?)
if (c == '.' or c == '!' or c == '?') {
stats.frases += 1;
}
// Contar parágrafos (duas quebras de linha)
if (c == '\n' and i + 1 < texto.len and texto[i + 1] == '\n') {
stats.paragrafos += 1;
}
}
return stats;
}
};
Passo 6: Interface CLI
pub fn main() !void {
const stdout = io.getStdOut().writer();
const stdin = io.getStdIn().reader();
var prng = std.Random.DefaultPrng.init(blk: {
var seed: u64 = undefined;
std.posix.getrandom(std.mem.asBytes(&seed)) catch unreachable;
break :blk seed;
});
try stdout.print(
\\
\\ ==========================================
\\ GERADOR DE LOREM IPSUM - Zig
\\ ==========================================
\\
, .{});
var buf: [64]u8 = undefined;
while (true) {
try stdout.print(
\\
\\ [1] Gerar com padrao (3 paragrafos)
\\ [2] Gerar personalizado
\\ [3] Gerar apenas palavras
\\ [4] Gerar apenas frases
\\ [5] Sair
\\
\\ Opcao:
, .{});
const opcao_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse break;
const opcao = mem.trim(u8, opcao_raw, " \t\r\n");
if (mem.eql(u8, opcao, "5")) break;
var config = ConfigGeracao{};
if (mem.eql(u8, opcao, "2")) {
try stdout.print("\n Numero de paragrafos (1-20): ", .{});
const p_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse continue;
if (fmt.parseInt(u32, mem.trim(u8, p_raw, " \t\r\n"), 10)) |v| {
config.paragrafos = v;
} else |_| {}
try stdout.print(" Iniciar com paragrafo classico? (s/n): ", .{});
const c_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse continue;
const c_str = mem.trim(u8, c_raw, " \t\r\n");
config.iniciar_com_classico = !mem.eql(u8, c_str, "n");
} else if (mem.eql(u8, opcao, "3")) {
// Modo de apenas palavras
try stdout.print("\n Numero de palavras: ", .{});
const n_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse continue;
const num = fmt.parseInt(u32, mem.trim(u8, n_raw, " \t\r\n"), 10) catch 20;
var gerador = GeradorLorem.init(prng.random(), config);
try stdout.print("\n", .{});
var i: u32 = 0;
while (i < num) : (i += 1) {
if (i > 0) try stdout.print(" ", .{});
try stdout.print("{s}", .{gerador.palavraAleatoria()});
}
try stdout.print("\n", .{});
continue;
} else if (mem.eql(u8, opcao, "4")) {
try stdout.print("\n Numero de frases: ", .{});
const n_raw = stdin.readUntilDelimiterOrEof(&buf, '\n') catch continue orelse continue;
const num = fmt.parseInt(u32, mem.trim(u8, n_raw, " \t\r\n"), 10) catch 5;
var gerador = GeradorLorem.init(prng.random(), config);
try stdout.print("\n", .{});
var i: u32 = 0;
while (i < num) : (i += 1) {
try gerador.gerarFrase(stdout);
}
try stdout.print("\n", .{});
continue;
} else if (!mem.eql(u8, opcao, "1")) {
try stdout.print(" Opcao invalida.\n", .{});
continue;
}
config.validar() catch {
try stdout.print(" Configuracao invalida. Usando valores padrao.\n", .{});
config = ConfigGeracao{};
};
try stdout.print("\n--- Texto Gerado ---\n\n", .{});
// Gera para um buffer para poder contar estatísticas
var texto_buf = ArrayList(u8).init(std.heap.page_allocator);
defer texto_buf.deinit();
var gerador = GeradorLorem.init(prng.random(), config);
try gerador.gerar(texto_buf.writer());
// Exibe o texto
try stdout.print("{s}\n", .{texto_buf.items});
// Estatísticas
const stats = EstatisticasTexto.analisar(texto_buf.items);
try stdout.print(
\\
\\--- Estatisticas ---
\\ Paragrafos: {d}
\\ Frases: {d}
\\ Palavras: {d}
\\ Caracteres: {d}
\\
, .{ stats.paragrafos, stats.frases, stats.palavras, stats.caracteres });
}
try stdout.print("\n Ate logo!\n", .{});
}
Passo 7: Testes
test "capitalizar palavra" {
var buf: [64]u8 = undefined;
const resultado = GeradorLorem.capitalizar("lorem", &buf);
try std.testing.expectEqualStrings("Lorem", resultado);
}
test "capitalizar vazio" {
var buf: [64]u8 = undefined;
const resultado = GeradorLorem.capitalizar("", &buf);
try std.testing.expectEqualStrings("", resultado);
}
test "estatisticas de texto" {
const texto = "Primeira frase. Segunda frase.\n\nSegundo paragrafo.";
const stats = EstatisticasTexto.analisar(texto);
try std.testing.expectEqual(@as(usize, 3), stats.frases);
try std.testing.expectEqual(@as(usize, 2), stats.paragrafos);
try std.testing.expectEqual(@as(usize, 6), stats.palavras);
}
test "config padrao e valida" {
const config = ConfigGeracao{};
try config.validar();
}
test "config invalida - zero paragrafos" {
var config = ConfigGeracao{};
config.paragrafos = 0;
try std.testing.expectError(error.ParagrafosInvalidos, config.validar());
}
test "config invalida - min > max frases" {
var config = ConfigGeracao{};
config.frases_por_paragrafo_min = 10;
config.frases_por_paragrafo_max = 3;
try std.testing.expectError(error.FrasesInvalidas, config.validar());
}
test "vocabulario nao esta vazio" {
try std.testing.expect(vocabulario.len > 50);
}
Compilando e Executando
zig build test
zig build run
Conceitos Aprendidos
- Arrays constantes de strings em comptime
- Geração aleatória de índices para seleção de elementos
- Writer interfaces para saída flexível
- Validação de configuração com erros
ArrayListpara construção de strings dinâmicas- Capitalização de caracteres ASCII
Próximos Passos
- Explore como escrever em arquivos para salvar o texto gerado
- Aprenda sobre alocadores de memória para entender o
page_allocator - Construa o próximo projeto: Verificador de Palíndromo