aller au contenu
FRFlo
Table des matières

Across multiplayer game engines and community modding ecosystems (particularly FiveM / GTA V), Lua remains the language of choice. Renowned for its lightweight footprint and seamless integration within C/C++ runtimes, Lua 5.4 powers massive codebases spanning hundreds of decoupled, interconnected micro-resources.

Yet as codebases grow beyond thousands of files, conventional developer tooling invariably breaks down. Traditional Language Servers (LSP) crumble under intense memory pressure: fragmented AST structures scattered across the heap, stop-the-world garbage collection pauses causing typing stutter, minutes spent cold-indexing workspaces, and complete blindness to gaming environment semantics (manifest definitions, client/server context isolation, asynchronous networked events, and cross-resource contracts).

To break through this bottleneck, I built Lugo (FRFlo/lugo), a bespoke Lua 5.4 parser, Language Server (LSP), and Model Context Protocol (MCP) server engineered from scratch in Go. Designed with an unwavering focus on CPU cache locality and raw throughput, Lugo leverages a zero-allocation flat-array arena architecture, sub-second indexing performance, and native intelligence for the FiveM ecosystem.

Here is an architectural deep dive into its compilation pipeline, memory models, security analyzers, and integration with modern AI coding agents.


Architecture Overview

Lugo is not a thin wrapper around an off-the-shelf parser: every layer of its ingestion and analysis pipeline is engineered to eliminate dynamic heap allocations and maximize CPU L1/L2 cache locality.

flowchart TB
    subgraph Input["Lua Source Files & Manifests"]
        Src["Source Code ([]byte)"]
        Manifest["fxmanifest.lua / __resource.lua"]
    end

    subgraph CoreEngine["Zero-Allocation Analysis Engine"]
        Lexer["SIMD Lexer / Table charProps [256]uint8<br>TokenSet [2]uint64 (0 allocs)"]
        Parser["Pratt Parser (Precedence Climbing)<br>Lookahead 3 tokens & Panic Recovery"]
        Arena["Flat Arena Tree.Nodes ([]Node)<br>NodeID (uint32) & Byte Offsets"]
        Resolver["Semantic Resolver & Type Inference<br>Control-flow & Narrowing"]
    end

    subgraph FiveMModule["FiveM Intelligence & Security"]
        Graph["Resource Graph & Scopes (Client/Server/Shared)"]
        EventBus["Network Event Indexing & Arity Checking"]
        TrustBoundary["Trust-Boundary Diagnostics (SQL/HTTP/Command Sinks)"]
        NUIContract["NUI Contract Validation (Lua <-> JS)"]
    end

    subgraph Protocols["Surface Protocols"]
        LSPHandler["LSP Server (JSON-RPC Framed)<br>Hover, Completion, Refactor, Safe Fixes"]
        MCPHandler["lugo-mcp Server (stdio)<br>lugo:// Resources & Read-Only AI Tools"]
        Formatter["Native AST Formatter & Diagnostics Engine"]
    end

    subgraph Consumers["Consumers"]
        VSCode["VS Code Extension (Marketplace)"]
        Editor["External Editors (Neovim, Helix)"]
        AIAgents["AI Coding Agents (Pi, Claude, Hermes)"]
    end

    Input --> CoreEngine
    CoreEngine --> FiveMModule
    FiveMModule --> Protocols
    Protocols --> Consumers

1. Zero-Allocation Architecture: The Flat-Array Arena AST

The primary culprit behind performance degradation in traditional language servers is abstract syntax tree (AST) node allocation. In typical Go or TypeScript parsers, each node is a pointer to an independently allocated heap struct (*BinaryExpr, *FunctionDecl, *Ident). On a workspace containing 3,000 Lua files and 30 megabytes of source code, this generates tens of millions of tiny allocations, hammering the runtime garbage collector and destroying memory cache locality.

The Compact Uniform Node (ast.Node)

Lugo completely rejects pointer-based AST graphs. All syntax nodes are normalized into a single, compact struct stored in one contiguous slice ([]Node):

type NodeID uint32
type Node struct {
Start, End uint32
Parent NodeID
Left NodeID
Right NodeID
Extra uint32 // Index in Tree.ExtraList
Count uint32 // Count in ExtraList
Kind NodeKind // uint8
Flags uint8
}

This design yields critical performance advantages:

  • Fixed and Compact Size: Each node occupies only 28 to 32 bytes with struct alignment, with zero pointers pointing into the Go heap.
  • Addressing via NodeID (uint32): Relationships (Left, Right, Parent) are simply indices within Tree.Nodes. The sentinel InvalidNode = 0 denotes the absence of a node without requiring nil checks.
  • Pointerless Arbitrary Child Lists: For constructs with variable child counts (function arguments, call parameters, table fields), Lugo stores child NodeID values sequentially in a secondary flat array (Tree.ExtraList []NodeID). The parent node simply records a starting index (Extra) and length (Count).

Byte-Offset Representation

Rather than allocating heap strings (string) for every variable name, keyword, or literal, Lugo stores only two uint32 values: Start and End, pinpointing exact byte offsets within the original source buffer (Tree.Source []byte).

Go string conversions are strictly deferred until an explicit user action demands text output (such as formatting a hover tooltip or dispatching completion items). During lexing, parsing, and semantic resolution, identifier comparisons execute directly across byte slices without allocating heap memory:

func (t *Tree) IdentText(id NodeID) string {
node := &t.Nodes[id]
return string(t.Source[node.Start:node.End])
}

2. SIMD Tokenizer & Pratt Parser

The frontend compilation pipeline is optimized to keep loops and lookahead state entirely within CPU registers.

Character Property Table & Bitmask Sets

Lugo’s lexer (lexer/lexer.go) eliminates branch mispredictions during character classification using a pre-computed 256-byte static lookup table:

const (
propIdent = 1 << iota
propDigit
propHex
propLetter
)
var charProps [256]uint8

Testing whether a token belongs to a specific grammatical set (such as unary operators or block terminators) relies on token.TokenSet, implemented as a bitmask across two 64-bit integers:

type TokenSet [2]uint64
func (s TokenSet) Has(k Kind) bool {
idx := uint(k)
return (s[idx/64] & (1 << (idx % 64))) != 0
}

This lookup executes in a single CPU instruction, avoiding branch table overhead or dynamic allocation.

Pratt Parsing with Precedence Climbing & Panic Recovery

The parser (parser/parser.go) implements Pratt parsing (Precedence Climbing) backed by a 3-token lookahead window. This effortlessly parses Lua 5.4’s expression grammar (operator precedences, bitwise operations, right-associative string concatenation, and chained table indexing) without backtracking.

When encountering syntax errors (such as unclosed if blocks during active typing), the parser activates panic-mode recovery: it synchronizes its cursor to the next statement boundary (end, else, until, local, return) and resumes parsing the remainder of the file. As a result, the document retains full autocompletion, symbol navigation, and diagnostics even while intermediate lines are malformed.

The CI Benchmark Gate: Enforcing 0 Allocs/Op

To guard against accidental performance regressions, Lugo’s continuous integration pipeline executes a dedicated enforcement tool (scripts/benchmark-gate):

Terminal window
go run ./scripts/benchmark-gate

This test runs BenchmarkLexer and BenchmarkParser under -benchmem. If either benchmark reports even a single memory allocation per operation (> 0 allocs/op), CI immediately fails.

Real-world production benchmarks:

  • Cold indexing of an entire workspace spanning 3,042 FiveM files (29.6 MB of raw Lua): 746 ms.
  • Publishing diagnostics across all 3,018 files: 720 ms.
  • Incremental warm re-indexing (detecting file changes via SHA-256 hashes and reusing map memory pools with clear()): sub-100 ms.

3. FiveM Intelligence: Beyond Vanilla Lua

Lugo’s primary differentiator is its deep awareness of the FiveM / CitizenFX runtime model. FiveM servers are structured as collections of independent resources orchestrated by manifest files (fxmanifest.lua or __resource.lua).

flowchart LR
    Manifest["fxmanifest.lua"] --> Discovery["File Discovery & Context Classification"]
    Discovery --> ClientScope["client_scripts<br>Client Natives + Shared"]
    Discovery --> ServerScope["server_scripts<br>Server Natives + Shared"]
    Discovery --> SharedScope["shared_scripts<br>Common Intersection"]

    ClientScope --> NetFlow["Network Validation<br>TriggerServerEvent"]
    ServerScope --> NetFlow["Network Validation<br>TriggerClientEvent"]
    NetFlow --> SecurityScan["Vulnerability Detection<br>(Trust-Boundary & SQL Injection)"]

Isolated Execution Scopes (client, server, shared)

Lugo parses resource manifests dynamically to establish strict runtime scopes:

  1. Client Files: Access GTA V graphic and game engine natives, NUI routines, and Citizen.CreateThread, while server-only database drivers and system functions remain hidden.
  2. Server Files: Access database clients (oxmysql, mysql-async), player management handles, and server natives (DropPlayer, PerformHttpRequest).
  3. Shared Files: Constrained strictly to the common intersection of both environments.

Invoking GetPlayerPed(-1) (a client native) within a server-classified script, or calling MySQL.query from a client script, immediately triggers contextual compile-time warnings without requiring an active runtime.

Resource Accounting

Lugo cross-references the physical file tree against manifest definitions. If a .lua file exists within a resource directory but is omitted from client_scripts, server_scripts, or shared_scripts, Lugo emits a resource accounting warning. The unlisted file is quarantined from the global symbol index to avoid polluting cross-resource lookups.

Network Events & Trust-Boundary Security Analysis

A major source of vulnerabilities in FiveM servers stems from unvalidated network event handlers (RegisterNetEvent). Lugo conducts proactive static flow analysis:

  • Arity and Signatures: Verifies parameter counts and types between TriggerServerEvent("resource:action", arg1, arg2) and RegisterNetEvent("resource:action", function(arg1, arg2) ...).
  • Directional Integrity: Catches server-registered events mistakenly triggered via TriggerServerEvent instead of TriggerClientEvent.
  • Trust Boundaries: Parameters received via server network events are tagged as untrusted. If untrusted data reaches sensitive sinks (such as dynamic SQL execution via oxmysql, outbound HTTP requests, console command dispatchers, or player source lookups following a Wait() yield), Lugo raises a targeted security alert.

NUI Contract Validation (Lua <-> JavaScript)

In-game user interfaces (NUI) rely on bridge communication between Lua game threads and the Chromium web renderer. Lugo simultaneously scans Lua and JavaScript source trees to verify bidirectional contract integrity:

  • Lua callbacks registered via RegisterNUICallback('actionName', ...) are validated against JS fetch('https://resource/actionName') payloads.
  • Outbound UI messages (SendNUIMessage({ action = 'openUI' })) are matched against frontend window.addEventListener('message', ...) handlers, highlighting orphaned or mismatched events immediately.

4. LSP Features & AST-Aware Refactorings

Lugo delivers a comprehensive Language Server Protocol implementation:

  • Advanced Type Inference & Narrowing: Lazy evaluation with control-flow type narrowing (such as if type(x) == "string" then ...), loop variable unpacking (pairs / ipairs), and metatable inheritance resolution (setmetatable(t, { __index = Base })).
  • LuaCATS Support: Comprehensive support for structured doc comments (---@class, ---@type, ---@param, ---@return, ---@generic, ---@field, ---@alias, ---@overload, ---@deprecated).
  • Hover Evaluation: Statically evaluates constant expressions (arithmetic, bitwise, boolean logic, and string concatenation) directly within the hover tooltip.
  • AST-Aware Refactorings:
    • Inverting boolean conditions.
    • Converting nested if-else blocks into clean early returns.
    • Optimizing table.insert(t, val) calls to direct t[#t+1] = val assignments.
    • Converting between method dot syntax (obj.method(self)) and colon syntax (obj:method()).
    • Bulk Safe Fixes: Command-palette refactoring to eliminate unused variables and redundant parameters across an entire workspace without altering expression side effects.

5. The AI Bridge: The lugo-mcp Server

With the rise of autonomous coding agents (such as Pi Coding Agent, Claude Code, and Hermes), the mechanism by which AI interacts with large codebases has become critical. Standard agents frequently rely on crude grep, find, and cat invocations, wasting tens of thousands of context tokens on raw, unparsed text files while missing type relationships.

To address this, Lugo ships with a dedicated binary: lugo-mcp.

Terminal window
lugo-mcp /path/to/my-fivem-project

This executable launches a Model Context Protocol server (communicating over stdio), directly wired into Lugo’s in-memory AST and semantic model:

flowchart LR
    Agent["AI Coding Agent<br>(Pi / Claude / Hermes)"] <-->|"MCP JSON-RPC (stdio)"| LugoMCP["lugo-mcp Process<br>(1 instance / workspace)"]
    LugoMCP <-->|"Direct Memory Access<br>(sync.RWMutex)"| LSPServer["LSP Workspace Model<br>(AST Arena & FiveM Graph)"]

    subgraph ExposedCapabilities["AI Capabilities"]
        Tools["Tools: lugo_hover, lugo_definition,<br>lugo_diagnostics, lugo_references,<br>lugo_fivem_events, lugo_fivem_contracts"]
        Resources["Resources: lugo://workspace/summary,<br>lugo://workspace/resource/{name}"]
        Safety["Guardrail: Read-Only &<br>Preview (lugo_preview_workspace_edit)"]
    end

    LugoMCP --- ExposedCapabilities

High-Fidelity Tools for AI Agents

Rather than guessing code semantics from raw regex queries, the agent accesses compiler-accurate tools:

  • lugo_diagnostics: Returns exact syntax, type, and security diagnostics for any target file.
  • lugo_symbol_context: Retrieves symbol definition coordinates, doc annotations, signatures, and lexical scope in a single round-trip.
  • lugo_fivem_events & lugo_fivem_contracts: Provides an instant, structured map of all networked events and cross-resource exports.
  • lugo://workspace/summary: Returns deterministic freshness indicators (revision hashes, indexed file totals, diagnostic counters).

Safety Guardrail: Read-Only Guarantee

To protect production codebases, lugo-mcp enforces a strict invariant: the MCP server is strictly read-only. It never writes or mutates files on disk directly. Refactoring tools (lugo_validate_workspace_edit and lugo_preview_workspace_edit) validate and preview proposed modifications as WorkspaceEdit objects. The AI agent or user retains full discretion to apply changes explicitly through their environment.


6. Hardening & Memory Management Audit

Maintaining continuous LSP stability during multi-day development sessions demands rigorous defensive programming against memory leaks and IPC resource exhaustion.

In September 2026, Lugo underwent an extensive memory and concurrency audit (docs/memory-audit.md), identifying and resolving 15 key vulnerabilities prior to release:

Finding Severity Identified Issue Implemented Fix
M-01 Critical Signed integer overflow parsing Content-Length headers, leading to panics on malformed frames. Replaced custom parser with strconv.ParseUint, strictly rejecting invalid formatting and enforcing bounds before conversion.
M-02 Critical Negative or exorbitant tabSize parameters in formatting requests triggered out-of-memory crashes. Bound tabSize strictly within 1–16, validating arithmetic and returning clean JSON-RPC errors.
M-03 High Unbounded line ingestion for JSON-RPC headers before line breaks (\n). Enforced an aggregate header size threshold to reject malformed payload streams.
M-04 High Client-supplied buffers via didOpen/didChange bypassed disk MaxFileSize checks. Applied MaxFileSize validation universally prior to JSON deserialization and AST arena allocations.
M-05 High Deeply nested recursive control blocks could exhaust stack frames. Added a universal depth counter across recursive statements, returning recoverable syntax diagnostics.
M-06 High Deleted workspace files retained orphaned resource records and graph edges in memory. Implemented full resource lifecycle pruning when no documents or manifests reference an asset.

Following this audit, the parser engine was subjected to an aggressive fuzzing run of 3,777,986 randomly generated inputs with zero crashes, while the entire test suite passes cleanly under Go’s race detector (-race).


7. Build Pipeline & Multi-Platform Distribution

Lugo is distributed as a self-contained static binary requiring no external runtimes.

Static Cross-Compilation via Zig

The GitHub Actions release workflow utilizes Zig 0.15.2 as a cross-platform CGO compiler to produce static musl binaries across 6 targets:

  • linux/amd64 & linux/arm64
  • windows/amd64 & windows/arm64
  • darwin/amd64 & darwin/arm64 (macOS Apple Silicon & Intel)

VS Code Extension

For Visual Studio Code users, Lugo is distributed via the VS Code Marketplace. The extension automatically detects host OS and CPU architectures, launching the appropriate precompiled Go binary in the background with zero manual configuration.


Conclusion

Lugo demonstrates that by abandoning fragmented heap-allocated object models in favor of flat contiguous memory arenas and byte-offset processing, language servers can achieve an order of magnitude improvement in indexing speed and memory efficiency.

By pairing a zero-allocation parser with deep FiveM runtime intelligence and a native MCP server for AI coding agents, Lugo delivers a modern, high-performance foundation for game server development.

The project is fully open source on GitHub: github.com/FRFlo/lugo.