A Lua-based compiler and runtime for the Zork Implementation Language (ZIL).
This project provides a runtime environment for executing ZIL programs, including the classic Zork adventure games.
For a repo-level system summary, see ARCHITECTURE.md. Agent-specific guidance lives in AGENTS.md.
For the proposed state-aware action-card layer used by parser-free and illustrated
play, see Companion ZIL Files. To add that layer to an
adventure, follow Generating companion.zil
or invoke the repository's @companion-author agent.
A released companion must author and validate every reachable room and material state family, and must complete the game in companion mode using numbered choices only. Automatic suggestions keep unfinished areas operable but do not count as complete authored coverage.
- ZIL to Lua Compilation: Compiles ZIL source code to Lua for execution
- Require System: Load .zil files using Lua's
require()function, just like moonscript - Source Mapping: Error messages automatically reference ZIL source files instead of generated Lua files (see SOURCE_MAPPING.md)
- Interactive Gameplay: Full support for classic text adventure games
- Comprehensive Testing: Unit and integration tests for all components
- Lua 5.4 or compatible version
The default interface presents numbered, state-aware companion choices:
lua5.4 main.luaCompanion mode shows every eligible grouped choice. Select one by number or type any parser command:
lua5.4 main.lua --companionUse the original free-text parser interface with:
lua5.4 main.lua --textRun lua5.4 main.lua --help for story-module and choice-count options. Games
without an authored companion.zil use conservative visible-object and
direct-exit suggestions.
The project includes comprehensive testing at multiple levels: unit tests (134+), integration tests, game walkthroughs, and pure ZIL tests.
make test # Run all tests (unit + integration)
make test-unit # Run unit tests only
make test-integration # Run integration tests only
make test-pure-zil # Run pure ZIL tests (new!)- Unit Tests: Parser (60 tests), Compiler (44 tests), Runtime (25 tests), Source Mapping (5 tests)
- Integration Tests: Zork1 game tests, parser/runtime tests, horror game tests
- Parser/Runtime Tests: Directions, containers, light, take, pronouns
- Pure ZIL Tests: Self-contained tests written entirely in ZIL using ASSERT function
You can now write tests entirely in ZIL without Lua wrappers:
<ROUTINE TEST-MY-FEATURE ()
<TELL "Testing..." CR>
<ASSERT T "This passes">
<ASSERT <==? 5 5> "Numbers match">
<ASSERT <FSET? ,APPLE ,TAKEBIT> "Apple is takeable">
<TELL "All tests completed!" CR>>
<ROUTINE GO () <TEST-MY-FEATURE>>Run with the generic test runner:
lua5.4 run-zil-test.lua tests.my-testFeatures:
- ASSERT checks condition and prints [PASS] or [FAIL]
- Combine with ZIL operators:
==?,FSET?,LOC, etc. - Simple, minimal setup
- Only ZIL test files needed - one generic Lua runner for all tests
For integration test documentation, see tests/TESTS.md.
main.lua- Main entry point for running the gamezilscript/- ZIL runtime implementationinit.lua- Main module for require system (loads when yourequire "zilscript")base.lua- Core loader functionality for .zil filesbootstrap.lua- Core runtime functions and globals (includes ASSERT function)parser.lua- ZIL parsercompiler.lua- ZIL to Lua compilerevaluate.lua- Expression evaluatorruntime.lua- Runtime loader utilitiessourcemap.lua- Source mapping for error messages
tests/- Test framework and test filesrun_tests.lua- Integration test runner- Pure ZIL test examples (test-simple-new.zil, test-insert-file.zil, etc.)
zork1_basic.lua- Basic integration testszork1_walkthrough.lua- Extended integration testsunit/- Unit tests directorytest_framework.lua- Unit testing frameworktest_parser.lua- Parser unit teststest_compiler.lua- Compiler unit teststest_runtime.lua- Runtime unit testsrun_all.lua- Unit test runner
infocom/zork1/- Zork 1 game filesbooks/blackwood-horror/- Additional adventure files
The list of ZIL files to compile is specified in main.lua:
local files = {
"infocom/zork1/globals.zil",
"infocom/zork1/parser.zil",
"infocom/zork1/verbs.zil",
"infocom/zork1/syntax.zil",
"books/blackwood-horror/horror.zil",
"infocom/zork1/main.zil",
}You can modify this list to load different ZIL files or create your own adventure.
The ZIL runtime now supports a require-based loading system similar to moonscript, allowing you to load .zil files using Lua's standard require() function.
-- Initialize the ZIL loader
require "zil"
-- Now you can require .zil files just like Lua modules
-- This will automatically compile and load infocom/zork1/actions.zil
local actions = require "infocom.zork1.actions"When you require "zil":
- The ZIL loader is automatically installed into Lua's package system
- A
package.zilpathis created (similar topackage.path) that tells Lua where to find .zil files - When you
requirea module, Lua will search for both .lua and .zil files - If a .zil file is found, it's automatically compiled to Lua and executed
- The compiled module is cached by Lua's require system (no recompilation on subsequent requires)
require "zil" -- Initialize the loader
-- Load ZIL modules
local test = require "tests.test-require" -- Loads tests/test-require.zil
-- The compiled code is cached
local test2 = require "tests.test-require" -- Reuses cached versionFor a complete example, see examples/require_example.lua.
The zil module also provides utility functions:
local zil = require "zil"
-- Compile ZIL code to Lua
local lua_code = zil.to_lua('<ROUTINE FOO () <RETURN 1>>')
-- Load ZIL code from a string
local chunk = zil.loadstring('<ROUTINE BAR () <TELL "Hello">>')
-- Load and execute a ZIL file
zil.dofile("myfile.zil")
-- Manage the loader
zil.remove_loader() -- Remove the ZIL loader
zil.insert_loader() -- Re-insert the ZIL loaderBy default, .zil files are compiled to Lua in memory only. For debugging purposes, you can enable saving of the compiled .lua files:
local zil = require "zil"
-- Enable saving compiled .lua files for modules
zil.config.save_lua = true
-- Now when you require a .zil module, it will save the compiled .lua file
require "mymodule" -- Creates mymodule.zil.lua
-- Disable to stop saving files
zil.config.save_lua = falseWhen enabled, each .zil module loaded via require() will save its compiled Lua code to a .zil.lua file in the same directory. This is useful for:
- Debugging compilation issues
- Understanding how ZIL code translates to Lua
- Examining generated code for performance analysis
Note: The generated .zil.lua files are automatically excluded from git via .gitignore.
The ZIL runtime is modular and well-tested, with a compiler architecture inspired by TypeScript:
- Parser (
zilscript/parser.lua): Parses ZIL source code into AST - Compiler (
zilscript/compiler/): Compiles ZIL AST to Lua code (12 focused modules)- Core modules:
init,buffer,utils,value,fields,forms,toplevel,print_node - TypeScript-inspired modules ✨:
visitor,diagnostics,emitter,checker
- Core modules:
- Runtime (
zilscript/runtime.lua): Executes compiled Lua code - Bootstrap (
zilscript/bootstrap.lua): Core runtime functions and globals - Source Mapping (
zilscript/sourcemap.lua): Maps Lua errors back to ZIL source locations
See zilscript/compiler/README.md for detailed compiler module documentation.
- Source Mapping: Error messages reference ZIL source files, not generated Lua files (see SOURCE_MAPPING.md)
- Modular Compiler: Clean separation of concerns across 12 focused modules
- TypeScript-Inspired Architecture: Visitor pattern, diagnostic collection, semantic checking, and structured emission
- Test Infrastructure: 149+ unit test assertions plus comprehensive integration tests
- Test Assertions: Built-in test commands for verifying game state
Generated Lua files (with zil_ prefix) are created during compilation and excluded from git via .gitignore.
See individual game files for their respective licenses.