Skip to content

Commit 6af8a7c

Browse files
authored
Add an AGENTS.md file (#10)
1 parent e30c00a commit 6af8a7c

26 files changed

Lines changed: 2673 additions & 33 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,4 @@ docs/api/
106106
latest
107107
history.txt
108108
.history
109+
.claude/

AGENTS.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to coding agents collaborating on this repository.
4+
5+
## Mission
6+
7+
Element 0 is a small, embeddable Lisp dialect inspired by Scheme, implemented in Zig.
8+
The interpreter (Elz) is designed to integrate into Zig applications as a scripting engine.
9+
Priorities, in order:
10+
11+
1. Correctness and R5RS compliance where applicable.
12+
2. Clean, minimal public API for embedding into Zig projects.
13+
3. Maintainable and well-tested code.
14+
4. Cross-platform support (Linux, macOS, and Windows).
15+
16+
## Core Rules
17+
18+
- Use English for code, comments, docs, and tests.
19+
- Prefer small, focused changes over large refactoring.
20+
- Add comments only when they clarify non-obvious behavior.
21+
- Do not add features, error handling, or abstractions beyond what is needed for the current task.
22+
23+
## Repository Layout
24+
25+
- `src/lib.zig`: Main public API export module for embedding Elz as a library.
26+
- `src/main.zig`: REPL entry point (`elz-repl` binary).
27+
- `src/elz/core.zig`: Core value types, Environment, and Module definitions.
28+
- `src/elz/interpreter.zig`: Main `Interpreter` struct.
29+
- `src/elz/eval.zig`: Evaluation engine.
30+
- `src/elz/parser.zig`: S-expression parser.
31+
- `src/elz/env_setup.zig`: Environment initialization and FFI setup.
32+
- `src/elz/ffi.zig`: Foreign function interface for calling Zig functions from Element 0.
33+
- `src/elz/gc.zig`: Garbage collection wrapper (uses Boehm-Demers-Weiser GC).
34+
- `src/elz/errors.zig`: Error types.
35+
- `src/elz/writer.zig`: Value serialization and display.
36+
- `src/elz/api_helpers.zig`: Public API helper functions.
37+
- `src/elz/primitives/`: Built-in functions grouped by category (math, lists, strings, control, predicates, vectors, hashmaps, io, ports, datetime, os, modules, and process).
38+
- `src/stdlib/std.elz`: Standard library written in Element 0 itself.
39+
- `examples/zig/`: FFI examples showing how to call Zig functions from Element 0.
40+
- `examples/elz/`: Element 0 script examples.
41+
- `tests/`: Element 0 language-level tests (`test_stdlib.elz`, `test_advanced.elz`, `test_edge_cases.elz`, `test_regression.elz`, `test_module_lib.elz`).
42+
- `.github/workflows/`: CI workflows (tests, lints, docs, and releases).
43+
- `build.zig` / `build.zig.zon`: Zig build configuration and dependencies.
44+
- `Makefile`: GNU Make wrapper around `zig build`.
45+
46+
## Architecture
47+
48+
### Interpreter Pipeline
49+
50+
Source code flows through: Parser (`parser.zig`) -> Evaluator (`eval.zig`) -> Writer (`writer.zig`).
51+
The `Interpreter` struct in `interpreter.zig` ties these together and manages the root environment.
52+
53+
### Core / Primitives Split
54+
55+
- `src/elz/core.zig` defines the value types and environment model.
56+
- `src/elz/primitives/` contains all built-in functions, each in a category-specific module.
57+
- New built-in functions should be added to the appropriate primitives' module.
58+
59+
### FFI
60+
61+
Zig functions can be registered with the interpreter via `env_setup.define_foreign_func()`.
62+
This is the primary extension mechanism for embedding use cases.
63+
64+
### Garbage Collection
65+
66+
Memory is managed by the Boehm-Demers-Weiser GC (`bdwgc`), wrapped in `gc.zig`. The GC is linked as a C library dependency.
67+
68+
### Dependencies
69+
70+
Managed via Zig's package manager (`build.zig.zon`):
71+
72+
- Chilli: CLI framework for the REPL.
73+
- BDWGC (v8.2.12): Garbage collector.
74+
- Linenoise (v2.0): Line editing for the REPL (POSIX only).
75+
- Minish: Property-based testing framework.
76+
77+
## Zig Conventions
78+
79+
- Zig version: 0.15.2.
80+
- Formatting is enforced by `zig fmt`. Run `make format` before committing.
81+
- Naming: `snake_case` for functions and variables, `PascalCase` for types and structs.
82+
- Element 0 symbols use `kebab-case` (e.g., `zig-mul`, `string-length`).
83+
84+
## Required Validation
85+
86+
Run all test suites for any change:
87+
88+
| Target | Command | What It Runs |
89+
|---------------------|--------------------|--------------------------------------------------------------|
90+
| Zig unit tests | `make test` | Inline `test` blocks in `src/**/*.zig` |
91+
| Property tests | `make test-prop` | Property-based tests in `tests/*_prop_test.zig` (Minish) |
92+
| Integration tests | `make test-integ` | Integration tests in `tests/*_integ_test.zig` |
93+
| Language tests | `make test-elz` | Element 0 test files in `tests/test_*.elz` |
94+
| All tests | `make test-all` | Runs all of the above |
95+
| Lint | `make lint` | Checks Zig formatting with `zig fmt --check` |
96+
97+
For interactive exploration: `make repl`.
98+
99+
## First Contribution Flow
100+
101+
1. Read the relevant source module under `src/elz/`.
102+
2. Implement the smallest possible change.
103+
3. Add or update inline `test` blocks in the changed Zig module. Add Element 0 tests in `tests/` if language behavior changed.
104+
4. Run `make test-all`.
105+
5. Verify interactively with `make repl` if needed.
106+
107+
Good first tasks:
108+
109+
- Add a new built-in function in the appropriate `src/elz/primitives/` module.
110+
- Add a new standard library function in `src/stdlib/std.elz`.
111+
- Fix an edge case identified in `tests/test_edge_cases.elz`.
112+
- Add a new FFI example in `examples/zig/`.
113+
114+
## Testing Expectations
115+
116+
- Unit and regression tests live as inline `test` blocks in the module they cover (`src/elz/*.zig` and `src/elz/primitives/*.zig`).
117+
- Property-based tests live in `tests/*_prop_test.zig` and use the Minish framework. They test invariants like commutativity, roundtrip properties, and crash resistance.
118+
- Integration tests live in `tests/*_integ_test.zig` and test the public embedding API (init, evalString, FFI, error propagation, sandboxing).
119+
- Language-level tests live in `tests/test_*.elz` and are run by the interpreter itself via `make test-elz`.
120+
- No language-facing change is complete without an Element 0 test.
121+
122+
## Change Design Checklist
123+
124+
Before coding:
125+
126+
1. Identify which module(s) the change touches (core, primitives, parser, eval, etc.).
127+
2. Consider whether the change requires updates to the standard library (`std.elz`).
128+
3. Check cross-platform implications if the change touches OS or I/O primitives.
129+
130+
Before submitting:
131+
132+
1. `make test && make test-elz` passes.
133+
2. `make lint` passes.
134+
3. Docs updated if the public API surface changed.
135+
136+
## Commit and PR Hygiene
137+
138+
- Keep commits scoped to one logical change.
139+
- PR descriptions should include:
140+
1. Behavioral change summary.
141+
2. Tests added or updated.
142+
3. Interactive verification done (yes/no).

Makefile

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ SHELL := /usr/bin/env bash
2727
# Targets
2828
################################################################################
2929

30-
.PHONY: all help build rebuild run run-elz test test-elz release clean lint format docs serve-docs install-deps setup-hooks test-hooks
30+
.PHONY: all help build rebuild run run-elz test test-elz test-prop test-integ test-all release clean lint format docs serve-docs install-deps setup-hooks test-hooks
3131
.DEFAULT_GOAL := help
3232

3333
help: ## Show the help messages for all targets
@@ -43,13 +43,13 @@ init: ## Initialize a new Zig project
4343
@echo "Initializing a new Zig project..."
4444
@$(ZIG) init
4545

46-
build: ## Build project (e.g. 'make build BUILD_TYPE=ReleaseSafe')
46+
build: ## Build project (like 'make build BUILD_TYPE=ReleaseSafe')
4747
@echo "Building project in $(BUILD_TYPE) mode with $(JOBS) concurrent jobs..."
4848
@$(ZIG) build $(BUILD_OPTS) -j$(JOBS)
4949

5050
rebuild: clean build ## clean and build
5151

52-
run: ## Run a Zig example (e.g. 'make run EXAMPLE=e1_ffi_1')
52+
run: ## Run a Zig example (like 'make run EXAMPLE=e1_ffi_1')
5353
@if [ "$(EXAMPLE)" = "all" ]; then \
5454
echo "--> Running all Zig examples..."; \
5555
fail=0; \
@@ -64,7 +64,7 @@ run: ## Run a Zig example (e.g. 'make run EXAMPLE=e1_ffi_1')
6464
$(ZIG) build run-$(EXAMPLE) $(BUILD_OPTS); \
6565
fi
6666

67-
run-elz: build ## Run a Lisp example (e.g. 'make run-elz ELZ_EXAMPLE=e1-cons-car-cdr')
67+
run-elz: build ## Run a Lisp example (like 'make run-elz ELZ_EXAMPLE=e1-cons-car-cdr')
6868
@if [ "$(ELZ_EXAMPLE)" = "all" ]; then \
6969
echo "--> Running all Lisp examples..."; \
7070
fail=0; \
@@ -91,6 +91,18 @@ test-elz: ## Run Element 0 standard library tests
9191
@echo "Running Element 0 standard library tests..."
9292
@$(ZIG) build test-elz $(BUILD_OPTS) -j$(JOBS) $(TEST_FLAGS)
9393

94+
test-prop: ## Run property-based tests
95+
@echo "Running property-based tests..."
96+
@$(ZIG) build test-prop $(BUILD_OPTS) -j$(JOBS) $(TEST_FLAGS)
97+
98+
test-integ: ## Run integration tests
99+
@echo "Running integration tests..."
100+
@$(ZIG) build test-integ $(BUILD_OPTS) -j$(JOBS) $(TEST_FLAGS)
101+
102+
test-all: ## Run all tests (unit, property, integration, and elz)
103+
@echo "Running all tests..."
104+
@$(ZIG) build test-all $(BUILD_OPTS) -j$(JOBS) $(TEST_FLAGS)
105+
94106
release: ## Build in Release mode
95107
@echo "Building the project in Release mode..."
96108
@$(MAKE) build BUILD_TYPE=$(RELEASE_MODE)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ Result of (zig-mul 7 6) is: 42
191191

192192
### Documentation
193193

194-
You can find the full API documentation for the latest release of Elz [here](https://Element0Lang.github.io/element-0/).
194+
You can find the full API documentation for the latest release of Elz [here](https://element0lang.github.io/element-0/).
195195

196196
Alternatively, you can use the `make docs` command to generate the API documentation for the current version of
197197
Elz from the source code.

ROADMAP.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ It outlines the features to be implemented and their current status.
103103
* [x] **Math Library**: More common mathematical functions (like trigonometric and logarithmic functions).
104104
* [x] **List Utilities**: `filter`, `fold-left`, `fold-right`, and other common list processing functions.
105105
* [x] **String Utilities**: `string-append`, `string-ref`, `substring`, `string-split`, `number->string`, `string->number`, `make-string`, `string=?`, `string<?`, `string>?`, `string<=?`, `string>=?`, `gensym` implemented.
106-
* [ ] **Regular Expressions**: A library for advanced text pattern matching.
106+
* [x] **Regular Expressions**: `regex-match?`, `regex-search`, `regex-replace`, `regex-split` implemented with NFA-based engine supporting literals, `.`, `*`, `+`, `?`, character classes, and anchors.
107107
* [x] **OS and Filesystem**: `getenv`, `file-exists?`, `delete-file`, `current-directory`, `directory-list`, `rename-file` implemented.
108-
* [ ] **Advanced I/O**: A `format` procedure and a more comprehensive port system.
108+
* [x] **Advanced I/O**: `format` procedure with `~a`, `~s`, `~%`, `~~` directives, and `value->string` implemented.
109109
* [x] **Date and Time**: `current-time`, `current-time-ms`, `time->components`, `sleep-ms` implemented.
110110

111111
### 4. Advanced Language Features (Post-R5RS)
@@ -114,16 +114,17 @@ It outlines the features to be implemented and their current status.
114114
* [x] **Module System**: A system for organizing code into reusable and encapsulated modules.
115115
* [x] `define-macro` (simple procedural macros)
116116
* [ ] `syntax-rules` (hygienic macros) or similar system for compile-time metaprogramming.
117-
* [ ] `call-with-current-continuation` (`call/cc`): Support for first-class continuations.
117+
* [x] `call-with-escape-continuation` (`call/ec`): Escape-only continuations for early returns. Full `call/cc` deferred pending CPS rewrite.
118118

119119
### 5. Better Host Integration and Embeddability
120120

121-
* [ ] **Advanced FFI**
121+
* **Advanced FFI**
122122
* [ ] Support for passing complex Zig structs.
123123
* [ ] Ability to pass Elz closures to Zig as callbacks.
124-
* [ ] Automatic type conversions for more data types.
125-
* [ ] **Sandboxing and Security**
124+
* [x] Automatic type conversions for `bool`, `[]const u8`, and `?T` (optional) types.
125+
* **Sandboxing and Security**
126126
* [x] A sandboxed mode to restrict access to I/O and other sensitive operations.
127-
* [ ] Host-level controls for memory and execution time limits.
128-
* [ ] **Serialization**
129-
* [ ] Built-in procedures to serialize and deserialize Elz objects (for example, to JSON or S-expressions).
127+
* [x] Host-level controls for execution time limits (`time_limit_ms` in `SandboxFlags`).
128+
* **Serialization**
129+
* [x] `json-serialize` and `json-deserialize` for JSON round-tripping.
130+
* [x] `value->string` for S-expression serialization.

build.zig

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ pub fn build(b: *std.Build) void {
143143
});
144144
docs_step.dependOn(&gen_docs_cmd.step);
145145

146-
// --- Test Setup ---
146+
// --- Unit Test Setup ---
147147
const test_module = b.createModule(.{
148148
.root_source_file = lib_source,
149149
.target = target,
@@ -162,6 +162,57 @@ pub fn build(b: *std.Build) void {
162162
const test_step = b.step("test", "Run unit tests");
163163
test_step.dependOn(&run_lib_unit_tests.step);
164164

165+
// --- Property-Based and Integration Test Setup ---
166+
const test_prop_step = b.step("test-prop", "Run property-based tests");
167+
const test_integ_step = b.step("test-integ", "Run integration tests");
168+
const minish_dep = b.dependency("minish", .{});
169+
170+
{
171+
const tests_path = "tests";
172+
var tests_dir = fs.cwd().openDir(tests_path, .{ .iterate = true }) catch |err| {
173+
if (err == error.FileNotFound) {
174+
@panic("Can't open 'tests' directory");
175+
}
176+
@panic("Can't open 'tests' directory");
177+
};
178+
defer tests_dir.close();
179+
180+
var test_iter = tests_dir.iterate();
181+
while (test_iter.next() catch @panic("Failed to iterate tests")) |entry| {
182+
if (!std.mem.endsWith(u8, entry.name, ".zig")) continue;
183+
184+
const is_prop_test = std.mem.endsWith(u8, entry.name, "_prop_test.zig");
185+
const is_integ_test = std.mem.endsWith(u8, entry.name, "_integ_test.zig");
186+
187+
if (!is_prop_test and !is_integ_test) continue;
188+
189+
const test_path = b.fmt("{s}/{s}", .{ tests_path, entry.name });
190+
191+
const t_module = b.createModule(.{
192+
.root_source_file = b.path(test_path),
193+
.target = target,
194+
.optimize = optimize,
195+
});
196+
t_module.addImport("elz", lib_module);
197+
198+
if (is_prop_test) {
199+
t_module.addImport("minish", minish_dep.module("minish"));
200+
}
201+
202+
const t = b.addTest(.{ .root_module = t_module });
203+
const bdwgc_dep_t = b.dependency("bdwgc", .{});
204+
t.addIncludePath(bdwgc_dep_t.path("include"));
205+
t.linkLibrary(gc);
206+
207+
const run_t = b.addRunArtifact(t);
208+
if (is_prop_test) {
209+
test_prop_step.dependOn(&run_t.step);
210+
} else {
211+
test_integ_step.dependOn(&run_t.step);
212+
}
213+
}
214+
}
215+
165216
// --- Example Setup ---
166217
const examples_path = "examples/zig";
167218
var examples_dir = fs.cwd().openDir(examples_path, .{ .iterate = true }) catch |err| {
@@ -198,10 +249,34 @@ pub fn build(b: *std.Build) void {
198249
run_step.dependOn(&run_cmd.step);
199250
}
200251

201-
// --- Run Element 0 Standard Library Tests ---
252+
// --- Run Element 0 Language Tests ---
202253
const test_elz_step = b.step("test-elz", "Run the Element 0 language tests");
203-
const run_elz_tests_cmd = b.addRunArtifact(repl_exe);
204-
run_elz_tests_cmd.addArg("--file");
205-
run_elz_tests_cmd.addArg("tests/test_stdlib.elz");
206-
test_elz_step.dependOn(&run_elz_tests_cmd.step);
254+
{
255+
const tests_path = "tests";
256+
var tests_dir = fs.cwd().openDir(tests_path, .{ .iterate = true }) catch |err| {
257+
if (err == error.FileNotFound) {
258+
@panic("Can't open 'tests' directory");
259+
}
260+
@panic("Can't open 'tests' directory");
261+
};
262+
defer tests_dir.close();
263+
264+
var test_iter = tests_dir.iterate();
265+
while (test_iter.next() catch @panic("Failed to iterate tests")) |entry| {
266+
if (!std.mem.startsWith(u8, entry.name, "test_")) continue;
267+
if (!std.mem.endsWith(u8, entry.name, ".elz")) continue;
268+
269+
const run_elz_test_cmd = b.addRunArtifact(repl_exe);
270+
run_elz_test_cmd.addArg("--file");
271+
run_elz_test_cmd.addArg(b.fmt("{s}/{s}", .{ tests_path, entry.name }));
272+
test_elz_step.dependOn(&run_elz_test_cmd.step);
273+
}
274+
}
275+
276+
// --- Run All Tests ---
277+
const test_all_step = b.step("test-all", "Run all tests");
278+
test_all_step.dependOn(&run_lib_unit_tests.step);
279+
test_all_step.dependOn(test_prop_step);
280+
test_all_step.dependOn(test_integ_step);
281+
test_all_step.dependOn(test_elz_step);
207282
}

build.zig.zon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
.url = "https://github.com/antirez/linenoise/archive/refs/tags/2.0.tar.gz",
1717
.hash = "N-V-__8AAJ4HAgCX79UDBfNwhqAqUVoGC44ib6UYa18q6oa_",
1818
},
19+
.minish = .{
20+
.url = "https://github.com/CogitatorTech/minish/archive/refs/tags/v0.1.0.tar.gz",
21+
.hash = "minish-0.1.0-SQtSTWHkAQACfz3xGuWHU8zbx320vK_47r2yto3Pq0Rf",
22+
},
1923
},
2024
.paths = .{ "build.zig", "build.zig.zon", "src", "LICENSE", "README.md" },
2125
}

0 commit comments

Comments
 (0)