# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Purpose Pugz is a Pug-like HTML template engine written in Zig 0.15.2. It implements Pug 3 syntax for indentation-based HTML templating with a focus on server-side rendering. ## Rules - Do not auto commit, user will do it. - At the start of each new session, read this CLAUDE.md file to understand project context and rules. - When the user specifies a new rule, update this CLAUDE.md file to include it. ## Build Commands - `zig build` - Build the project (output in `zig-out/`) - `zig build test` - Run all tests - `zig build bench-compiled` - Run compiled templates benchmark (compare with Pug.js) - `zig build bench-interpreted` - Inpterpret trmplates ## Architecture Overview The template engine supports two rendering modes: ### 1. Runtime Rendering (Interpreted) ``` Source → Lexer → Tokens → Parser → AST → Runtime → HTML ``` ### 2. Build-Time Compilation (Compiled) ``` Source → Lexer → Tokens → Parser → AST → build_templates.zig → generated.zig → Native Zig Code ``` The compiled mode is **~3x faster** than Pug.js. ### Core Modules | Module | Purpose | |--------|---------| | **src/lexer.zig** | Tokenizes Pug source into tokens. Handles indentation tracking, raw text blocks, interpolation. | | **src/parser.zig** | Converts token stream into AST. Handles nesting via indent/dedent tokens. | | **src/ast.zig** | AST node definitions (Element, Text, Conditional, Each, Mixin, etc.) | | **src/runtime.zig** | Evaluates AST with data context, produces final HTML. Handles variable interpolation, conditionals, loops, mixins. | | **src/build_templates.zig** | Build-time template compiler. Generates optimized Zig code from `.pug` templates. | | **src/view_engine.zig** | High-level ViewEngine for web servers. Manages views directory, auto-loads mixins. | | **src/root.zig** | Public library API - exports `ViewEngine`, `renderTemplate()`, `build_templates` and core types. | ### Test Files - **src/tests/general_test.zig** - Comprehensive integration tests for all features - **src/tests/doctype_test.zig** - Doctype-specific tests - **src/tests/inheritance_test.zig** - Template inheritance tests ## Build-Time Template Compilation For maximum performance, templates can be compiled to native Zig code at build time. ### Setup in build.zig ```zig const std = @import("std"); pub fn build(b: *std.Build) void { const pugz_dep = b.dependency("pugz", .{}); // Compile templates at build time const build_templates = @import("pugz").build_templates; const compiled_templates = build_templates.compileTemplates(b, .{ .source_dir = "views", // Directory containing .pug files }); const exe = b.addExecutable(.{ .name = "myapp", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .imports = &.{ .{ .name = "pugz", .module = pugz_dep.module("pugz") }, .{ .name = "tpls", .module = compiled_templates }, }, }), }); } ``` ### Usage in Code ```zig const tpls = @import("tpls"); pub fn handleRequest(allocator: std.mem.Allocator) ![]u8 { // Zero-cost template rendering - just native Zig code return try tpls.home(allocator, .{ .title = "Welcome", .user = .{ .name = "Alice", .email = "alice@example.com" }, .items = &[_][]const u8{ "One", "Two", "Three" }, }); } ``` ### Generated Code Features The compiler generates optimized Zig code with: - **Static string merging** - Consecutive static content merged into single `appendSlice` calls - **Zero allocation for static templates** - Returns string literal directly - **Type-safe data access** - Uses `@field(d, "name")` for compile-time checked field access - **Automatic type conversion** - `strVal()` helper converts integers to strings - **Optional handling** - Nullable slices handled with `orelse &.{}` - **HTML escaping** - Lookup table for fast character escaping ### Benchmark Results (2000 iterations) | Template | Pug.js | Pugz | Speedup | |----------|--------|------|---------| | simple-0 | 0.8ms | 0.1ms | **8x** | | simple-1 | 1.4ms | 0.6ms | **2.3x** | | simple-2 | 1.8ms | 0.6ms | **3x** | | if-expression | 0.6ms | 0.2ms | **3x** | | projects-escaped | 4.4ms | 0.6ms | **7.3x** | | search-results | 15.2ms | 5.6ms | **2.7x** | | friends | 153.5ms | 54.0ms | **2.8x** | | **TOTAL** | **177.6ms** | **61.6ms** | **~3x faster** | Run benchmarks: ```bash # Pugz (Zig) zig build bench-compiled # Pug.js (for comparison) cd src/benchmarks/pugjs && npm install && npm run bench ``` ## Memory Management **Important**: The runtime is designed to work with `ArenaAllocator`: ```zig var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); // Frees all template memory at once const html = try pugz.renderTemplate(arena.allocator(), template, data); ``` This pattern is recommended because template rendering creates many small allocations that are all freed together after the response is sent. ## Key Implementation Details ### Lexer State Machine The lexer tracks several states for handling complex syntax: - `in_raw_block` / `raw_block_indent` / `raw_block_started` - For dot block text (e.g., `script.`) - `indent_stack` - Stack-based indent/dedent token generation **Important**: The lexer distinguishes between `#id` (ID selector), `#{expr}` (interpolation), and `#[tag]` (tag interpolation) by looking ahead at the next character. ### Token Types Key tokens: `tag`, `class`, `id`, `lparen`, `rparen`, `attr_name`, `attr_value`, `text`, `interp_start`, `interp_end`, `indent`, `dedent`, `dot_block`, `pipe_text`, `literal_html`, `self_close`, `mixin_call`, etc. ### AST Node Types - `element` - HTML elements with tag, classes, id, attributes, children - `text` - Text with segments (literal, escaped interpolation, unescaped interpolation, tag interpolation) - `conditional` - if/else if/else/unless branches - `each` - Iteration with value, optional index, else branch - `mixin_def` / `mixin_call` - Mixin definitions and invocations - `block` - Named blocks for template inheritance - `include` / `extends` - File inclusion and inheritance - `raw_text` - Literal HTML or text blocks ### Runtime Value System ```zig pub const Value = union(enum) { null, bool: bool, int: i64, float: f64, string: []const u8, array: []const Value, object: std.StringHashMapUnmanaged(Value), }; ``` The `toValue()` function converts Zig structs to runtime Values automatically. ## Supported Pug Features ### Tags & Nesting ```pug div h1 Title p Paragraph ``` ### Classes & IDs (shorthand) ```pug div#main.container.active .box // defaults to div #sidebar // defaults to div ``` ### Attributes ```pug a(href="/link" target="_blank") Click input(type="checkbox" checked) div(style={color: 'red'}) div(class=['foo', 'bar']) button(disabled=false) // omitted when false button(disabled=true) // disabled="disabled" ``` ### Text & Interpolation ```pug p Hello #{name} // escaped interpolation p Hello !{rawHtml} // unescaped interpolation p= variable // buffered code (escaped) p!= rawVariable // buffered code (unescaped) | Piped text line p. Multi-line text block
Literal HTML
// passed through as-is // Interpolation-only text works too h1.header #{title} // renders