A README for AI coding agents working on go-zapscript.
go-zapscript is a Go parser and evaluator library for ZapScript, a custom domain-specific language designed for the Zaparoo Project. It provides functionality for parsing and executing commands related to game launching, playlist management, media playback, input simulation, and system control through physical tokens in gaming emulation environments.
Tech Stack: Go 1.24+, expr-lang/expr (expression evaluation), testify (testing), rapid (property-based testing)
Testing Standards: Comprehensive test coverage with unit tests, property-based tests, and fuzz tests
- Write tests for all new code - comprehensive coverage required
- Use
task lint-fixto resolve all linting and formatting issues - Keep diffs small and focused - one concern per change
- Use parallel tests with
t.Parallel()for all test functions - Reference existing patterns before writing new code - consistency matters
- Handle all errors explicitly - use golangci-lint's error handling checks
- Keep functions small and focused on single responsibility
- ❌ Skip writing tests for new features or bug fixes
- ❌ Make large, unfocused diffs - keep changes small and targeted
- ❌ Add new dependencies without discussion
- ❌ Write comments that restate what the code does - comments should explain why, not what
- ❌ Use standard
logorfmt.Printlnin library code - ❌ Skip the
t.Parallel()call in test functions
- Use Go 1.24+ with Go modules enabled
- Handle all errors explicitly - use golangci-lint's error handling checks
- Use explicit returns in functions longer than 5 lines (avoid naked returns)
- Keep functions small and focused on single responsibility
- Line length: 120 characters max
The goal is useful tests, not coverage metrics. High coverage means nothing if tests don't catch bugs.
- Parser behavior - valid inputs produce correct output
- Edge cases - empty inputs, boundary conditions, escape sequences
- Error paths - invalid inputs return appropriate errors
- Expression evaluation - dynamic expressions evaluate correctly
- Use property-based tests for parser robustness (see
parser_property_test.go) - Use fuzz tests to catch panics and crashes (see
parser_fuzz_test.go) - Use table-driven tests for multiple scenarios
- Use
t.Parallel()in all test functions - Use
cmp.Difffrom google/go-cmp for detailed assertion output
- Would this test catch a real bug?
- Does it test behavior the user/caller cares about?
- Is this testing MY code or a library's code?
# Test a specific file or function
go test -run TestSpecificFunction ./...
go test -race ./...
# Lint and format - ALWAYS prefer task commands
task lint-fix # PREFERRED: Full lint with auto-fixes
task lint # Run linting without fixes
# Run single test by name
go test -run TestParseScript ./...
# Run tests with verbose output
go test -v ./...
# Run fuzz tests
go test -fuzz=FuzzParseScript -fuzztime=30s ./...# Full test suite with race detection
task test
# Full lint with auto-fixes
task lint-fix
# Run all linting checks
task lintDon't guess - ask for help or gather more information first.
- Ask clarifying questions - Get requirements clear before coding
- Propose a plan first - Outline approach, then implement
- Reference existing patterns - Check similar code in the codebase for consistency
- Look at git history -
git log -p filenameshows how code evolved - Keep scope focused - Small, well-defined changes are easier to review and debug
Remember: It's better to ask than to make incorrect assumptions. The project values correctness over speed.
go-zapscript/
├── parser.go # Main parsing engine with ScriptReader
├── reader.go # ScriptReader input handling
├── symbols.go # Parser symbols and character constants
├── traits.go # Traits syntax parsing (#key=value)
├── types.go # Type definitions, constants, and argument structures
├── models.go # JSON-serializable data structures for scripts
├── advargs.go # Type-safe advanced argument wrapper
├── exprenv.go # Expression evaluation environment types
├── parser_test.go # Core parsing tests
├── parser_coverage_test.go # Extended coverage tests
├── parser_media_title_test.go # Media title syntax tests
├── parser_traits_test.go # Traits syntax tests
├── parser_property_test.go # Property-based tests (rapid)
├── parser_fuzz_test.go # Fuzz tests
├── parser_internal_test.go # Internal implementation tests
├── exprenv_test.go # Expression environment tests
├── Taskfile.yml # Build and development tasks
└── .golangci.yml # Linting configuration
**command:arg1,arg2?advkey=advvalue&advkey2=advvalue2
**or*- Command prefix (required)command- Command name:- Argument separator (before first arg),- Multiple argument separator?- Advanced argument prefix&- Advanced argument separator=- Advanced argument assignment|or||- Command chain separator
[[expression]]
Expressions are evaluated at runtime using expr-lang. The environment includes device info, active media, last scanned token, and launching context.
@system/title
Direct media launch by system and title.
^n- Newline^t- Tab^r- Carriage return^^- Literal caret
+tag- AND (must have tag)-tag- NOT (must not have tag)~tag- OR (any of these tags)
Traits provide metadata key-value pairs using # prefix:
#key=value #key2=value2
#key=value- Key-value pair#flag- Boolean shorthand (sets value totrue)#key="quoted value"- Quoted string (preserves spaces)#key=[a,b,c]- Array values
Type Inference (unquoted values):
true/false→ boolean123→ integer3.14→ float- Everything else → string
- Quoted values are always strings
Examples:
#favorite → {"favorite": true}
#count=5 → {"count": 5}
#name="My Game" → {"name": "My Game"}
#tags=[action,rpg,indie] → {"tags": ["action", "rpg", "indie"]}
#enabled=true #priority=1 → {"enabled": true, "priority": 1}
Fallback Behavior: Invalid trait syntax (e.g., #my-trait with hyphen) falls back to auto-launch content.
// Create a parser instance
parser := zapscript.NewParser(scriptText)
// Parse commands from script
script, err := parser.ParseScript()
// Parse expression placeholders
result, err := parser.ParseExpressions("text with [[expr]]")
// Evaluate expressions with environment
result, err := parser.EvalExpressions(envStruct)40+ commands including:
- Launch:
launch,launch.system,launch.random,launch.search,launch.title - Playlist:
playlist.play,playlist.stop,playlist.next,playlist.previous,playlist.goto,playlist.pause,playlist.load,playlist.open - System:
execute,delay,evaluate,stop,echo - MiSTer:
mister.ini,mister.core,mister.script,mister.mgl - HTTP:
http.get,http.post - Input:
input.keyboard,input.gamepad,input.coinp1,input.coinp2 - UI:
ui.notice,ui.picker - Metadata:
traits(parsed from#key=valuesyntax)
Copy these patterns for new code:
- Parser Methods:
parser.go- ScriptReader parsing methods - Traits Parsing:
traits.go- Type inference, arrays, quoted values - Type Definitions:
types.go- Constant and type patterns - Table-Driven Tests:
parser_test.go- Test organization pattern - Traits Tests:
parser_traits_test.go- Comprehensive trait syntax tests - Property Tests:
parser_property_test.go- Property-based testing with rapid - Fuzz Tests:
parser_fuzz_test.go- Fuzz testing pattern
Following golangci-lint configuration in .golangci.yml:
- Line length: 120 characters max
- Error handling: All errors must be checked (errcheck, wrapcheck)
- Imports: Grouped and sorted with gci formatter
- Formatting: Use gofumpt (stricter than gofmt)
- JSON tags: snake_case (enforced by tagliatelle)
- License headers: Apache 2.0 required on all files (enforced by goheader)
- Nil checks: Comprehensive (nilnil, nilerr rules)
Use Conventional Commits format:
<type>[optional scope]: <description>
Types:
feat:- New featurefix:- Bug fixdocs:- Documentation onlyrefactor:- Code refactoringtest:- Adding or updating testschore:- Other changes
Examples:
git commit -m "feat: add support for new command type"
git commit -m "fix: resolve escape sequence parsing issue"
git commit -m "test: add property tests for expression evaluation"ALWAYS run these commands in order:
# 1. Fix all linting and formatting issues (REQUIRED)
task lint-fix
# 2. Run all tests with race detection (REQUIRED)
task test- Tests pass (
task test) - Linting passes (
task lint-fix) - License headers on new files
- Commit message follows Conventional Commits format
- Diff is small and focused on one concern
- No commented-out code or debug prints
- Read any files in the repository
- Run tests:
go test ./...ortask test - Run
task lint-fixto fix linting and formatting issues - Run
task lintto check linting - View git history:
git log,git diff
- Installing new Go dependencies
- Running
git pushorgit commit - Deleting files or directories
- Changing the public API surface
- Adding new command types to the parser
- Write tests - comprehensive test coverage is required for all new code
- Small diffs - focused changes are easier to review
- Use existing patterns - consistency matters
- Ask when uncertain - better than wrong assumptions
- Use
t.Parallel()- all tests should run in parallel