fix: stabilize learning path, reliability and legacy Windows builds
Unify beginner syntax while preserving compatibility, repair ownership and parser guards, add verified distribution tooling and VC6 Windows 98 build inputs, and clarify documentation and release status. Preserve DOS build-input line endings and exclude generated binaries/caches.
This commit is contained in:
@@ -27,6 +27,16 @@ jobs:
|
||||
run: python3 tools/validate_compiler_warnings.py
|
||||
- name: Validate trusted command documentation
|
||||
run: python3 tools/validate_trusted_command_docs.py
|
||||
- name: Validate documentation and release status
|
||||
run: python3 tools/validate_documentation_status.py
|
||||
- name: Validate VC6 Windows 98 build settings
|
||||
run: python3 tools/validate_msvc6_source.py
|
||||
- name: Validate single-file C89 build
|
||||
run: python3 tools/validate_c89.py
|
||||
- name: Validate executable learning path
|
||||
run: python3 tools/validate_learning_syntax.py
|
||||
- name: Validate memory and malformed-input reliability
|
||||
run: python3 tools/validate_reliability.py
|
||||
- name: Validate CI workflow coverage
|
||||
run: python3 tools/validate_ci_workflow.py
|
||||
- name: Check lessons
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Preserve DOS line endings for the Windows 98 COMMAND.COM build inputs.
|
||||
build98.bat -text whitespace=cr-at-eol
|
||||
build98.rsp -text whitespace=cr-at-eol
|
||||
@@ -1,5 +1,6 @@
|
||||
claro
|
||||
claro.exe
|
||||
claro98.exe
|
||||
*.o
|
||||
*.obj
|
||||
*.out
|
||||
|
||||
@@ -57,3 +57,12 @@ The runtime must remain a single C89 implementation file. The full gate runs
|
||||
`python3 tools/validate_c89.py` to compile it independently with strict C89 flags
|
||||
and test formatting, numeric boundaries, and simple functions. Do not introduce
|
||||
C99 syntax or depend on generated project headers. See [C89 notes](docs/C89.md).
|
||||
|
||||
|
||||
## Beginner syntax consistency
|
||||
|
||||
Follow [the syntax guide](docs/SYNTAX.md) in introductory teaching material.
|
||||
Keep older spellings in [compatibility reference](docs/COMPATIBILITY.md), not
|
||||
parallel first-lesson examples. `tools/validate_learning_syntax.py` enforces the
|
||||
policy and checks actual output; extend its explicit coverage when adding a
|
||||
lesson or recommended example. Preserve supported parser aliases.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
CC = gcc
|
||||
CFLAGS = -std=c89 -pedantic-errors -Werror=implicit-function-declaration -O0
|
||||
LDFLAGS = -lm
|
||||
ifeq ($(OS),Windows_NT)
|
||||
LDFLAGS += -Wl,--stack,16777216
|
||||
endif
|
||||
|
||||
all: claro claro.exe
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ Claro helps beginners learn programming through readable commands, simple rules,
|
||||
and small programs. Accessibility for people with learning disabilities is a
|
||||
core design goal; it still needs testing with learners.
|
||||
|
||||
Current release: **v2026.10.20.160950**
|
||||
Development version: **v2026.10.20.223203**
|
||||
|
||||
This is a development snapshot, not a newly published release. See
|
||||
[current status](docs/CURRENT_STATUS.md) and the dated
|
||||
[release ledger](docs/RELEASE_STATUS.md) for verified builds and limitations.
|
||||
|
||||
## Start here
|
||||
|
||||
@@ -37,6 +41,9 @@ Start with input/output, variables, basic math, decisions, loops, lists,
|
||||
simple functions, and saving text. You do not need classes, packages,
|
||||
networking, or advanced types to learn with Claro.
|
||||
|
||||
The [beginner syntax guide](docs/SYNTAX.md) gives one recommended form per
|
||||
concept. Older forms remain supported in a separate compatibility reference.
|
||||
|
||||
## Find your way
|
||||
|
||||
- `lessons/` — small, ordered lessons.
|
||||
@@ -63,6 +70,13 @@ gcc -std=c89 -pedantic-errors src/claro.c -o claro -lm
|
||||
No generated source or project header is required. See [C89 build notes](docs/C89.md)
|
||||
for platform dependencies, testing, and limitations.
|
||||
|
||||
## Downloads and portability
|
||||
|
||||
Use the [distribution guide](docs/PORTABILITY.md) to build and verify clean
|
||||
source, Linux x86-64, and Windows x86-64 archives. Windows execution is tested
|
||||
under Wine, not yet on a native Windows machine. For VC6 / Windows 98 SE
|
||||
compilation, use [build98.bat and its guide](docs/WINDOWS98.md).
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE).
|
||||
|
||||
@@ -3,7 +3,7 @@ setlocal
|
||||
set CC=gcc
|
||||
if not "%~1"=="" set CC=%~1
|
||||
echo Building Claro (C89)...
|
||||
%CC% -std=c89 -pedantic-errors -Werror=implicit-function-declaration src\claro.c -O0 -o claro.exe -lm
|
||||
%CC% -std=c89 -pedantic-errors -Werror=implicit-function-declaration src\claro.c -O0 -o claro.exe -Wl,--stack,16777216 -lm
|
||||
if errorlevel 1 (
|
||||
echo Build failed.
|
||||
exit /b 1
|
||||
|
||||
@@ -4,7 +4,7 @@ param(
|
||||
)
|
||||
|
||||
Write-Host "Building Claro (C89)..." -ForegroundColor Cyan
|
||||
& $CC "-std=c89" "-pedantic-errors" "-Werror=implicit-function-declaration" "src\claro.c" "-O0" "-o" $Out "-lm"
|
||||
& $CC "-std=c89" "-pedantic-errors" "-Werror=implicit-function-declaration" "src\claro.c" "-O0" "-o" $Out "-Wl,--stack,16777216" "-lm"
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
Write-Host "Built: $Out" -ForegroundColor Green
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
@echo off
|
||||
rem Run from the Claro folder after the VC6 VCVARS32.BAT environment setup.
|
||||
rem Keep this file compatible with the Windows 98 SE COMMAND.COM shell.
|
||||
echo Building Claro for Windows 98 SE with Microsoft Visual C++ 6.0...
|
||||
cl @build98.rsp
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/nologo
|
||||
/TC
|
||||
/W3
|
||||
/ML
|
||||
/O1
|
||||
/DWINVER=0x0410
|
||||
/D_WIN32_WINDOWS=0x0410
|
||||
/Foclaro98.obj
|
||||
/Feclaro98.exe
|
||||
src\claro.c
|
||||
/link /MACHINE:I386 /SUBSYSTEM:CONSOLE,4.10 /STACK:16777216
|
||||
+6
-4
@@ -11,8 +11,9 @@ gcc -std=c89 -pedantic-errors -Werror=implicit-function-declaration src/claro.c
|
||||
```
|
||||
|
||||
`make -s all` uses the same strict C89 gate and builds `claro` and `claro.exe`.
|
||||
The Windows build scripts select C89 too, but Windows compilation/runtime has
|
||||
not been verified by this Linux refactor. The `.exe` name built by Linux make
|
||||
The Windows build scripts select C89 too and reserve a 16 MiB stack.
|
||||
A MinGW x64 executable has been compiled and exercised under Wine; native
|
||||
Windows acceptance is still unverified. See [portability notes](PORTABILITY.md). The `.exe` name built by Linux make
|
||||
still contains a Linux executable; it is not a Windows cross-build.
|
||||
|
||||
The source is formatted with four-space indentation and a 100-column limit.
|
||||
@@ -38,8 +39,9 @@ randomness and does not claim improved distribution.
|
||||
C89 describes the C language and standard library, not an entire operating
|
||||
system. Directory listing, subprocesses, filesystem checks, and HTTP execution
|
||||
still use existing platform branches (POSIX or Windows); real HTTP needs curl.
|
||||
This is not proof of MSVC6, Windows 98, DOS, or freestanding-system support.
|
||||
Those environments need their own compiler, link, and runtime checks.
|
||||
The separate [MSVC6 / Windows 98 build](WINDOWS98.md) uses the actual legacy
|
||||
compiler with a guarded CRT handle type and dedicated linker settings. Wine
|
||||
execution does not establish native Windows 98, DOS, or freestanding support.
|
||||
|
||||
The C89 gate is tested with the host GCC and the host's integer/double model.
|
||||
It does not establish compatibility with every minimum-capability C89 compiler
|
||||
|
||||
+36
-28
@@ -1,35 +1,43 @@
|
||||
# Classroom Readiness Checklist
|
||||
# Classroom pilot checklist
|
||||
|
||||
Claro v2026.10.20.160950 is suitable for beginner classroom-style testing of the stable console-language foundation. Advanced platform features should still be introduced carefully and only after the basics are comfortable.
|
||||
**Status: ready for supervised learner testing, not classroom certification.**
|
||||
Accessibility for learners with learning disabilities is a design goal and is
|
||||
**not yet validated** by intended learners. Passing tests does not establish it.
|
||||
Use [current status](CURRENT_STATUS.md) for scope, not older feature roadmaps.
|
||||
|
||||
## Ready to test
|
||||
## Before a session
|
||||
|
||||
- Beginner console programs
|
||||
- Interactive examples
|
||||
- Variables, math, choices, loops, lists, maps, functions
|
||||
- Simple files and JSON
|
||||
- Friendly `check` command
|
||||
- Windows and Linux/macOS build paths
|
||||
- Local project/package workflow basics
|
||||
- Offline networking lessons with `claro://` URLs
|
||||
- Use a build whose platform was actually tested; check `claro --version`.
|
||||
On the legacy Windows build the executable is `claro98.exe`.
|
||||
- Run `claro test` and `claro validate` in the extracted project folder.
|
||||
- Prepare a writable practice folder and explain how to stop/close a stuck run.
|
||||
- Use only reviewed, trusted scripts. Claro is not a sandbox and runs with the
|
||||
learner's permissions; do not collect sensitive information in exercises.
|
||||
|
||||
## Use with caution
|
||||
## Observe the first hour
|
||||
|
||||
- Advanced scripts
|
||||
- Large programs
|
||||
- Graphics/game examples
|
||||
- Remote networking examples
|
||||
- Package registry or publishing workflows
|
||||
- Concurrency examples beyond documented safe task patterns
|
||||
1. Run [hello](../lessons/01_hello.claro) and change its message.
|
||||
2. Try [input](../lessons/02_ask_name.claro), variables and simple arithmetic.
|
||||
3. Introduce one decision, then one small loop, at the learner's pace.
|
||||
4. Follow the [ten lessons](../lessons/README.md) and [first-hour guide](FIRST_HOUR.md).
|
||||
5. Try a small quiz; ask the learner to explain what changed and why.
|
||||
6. Introduce a harmless mistake, run `claro check`, and observe whether the
|
||||
message identifies the right line and suggests an understandable repair.
|
||||
|
||||
## Recommended testing session
|
||||
Show one recommended spelling per concept. Do not introduce classes, typing,
|
||||
packages, networking, concurrency or graphics as prerequisites to first programs.
|
||||
|
||||
1. Build Claro or use the included executable.
|
||||
2. Run `claro --version` and confirm `Claro v2026.10.20.160950`.
|
||||
3. Run `claro test`.
|
||||
4. Run `claro validate`.
|
||||
5. Run `lessons/01_hello.claro`.
|
||||
6. Run `lessons/02_ask_name.claro`.
|
||||
7. Run `examples/quiz.claro`.
|
||||
8. Deliberately mistype `SAY` as `PRNT` and run `claro check`.
|
||||
9. Try one foundation feature example, such as `examples/objects_classes.claro` or `examples/networking.claro`.
|
||||
## Record feedback
|
||||
|
||||
- Can the learner start, run, change and save a program without repeated coaching?
|
||||
- Which instructions, words, prompts or errors cause confusion?
|
||||
- Are examples short enough? Is the expected output obvious?
|
||||
- Can the learner recover from a mistake and understand what a loop does?
|
||||
- What presentation, pacing or assistive-technology adjustments help this person?
|
||||
|
||||
Record observations without claiming one design suits every disability. Get
|
||||
consent before retaining feedback and avoid unnecessary personal/health data.
|
||||
Make one small improvement, then repeat learner testing.
|
||||
|
||||
Platform checks, hosted release verification and accessibility feedback are
|
||||
separate milestones. See [release status](RELEASE_STATUS.md).
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Claro Command Line
|
||||
|
||||
Claro v2026.10.20.160950 supports these main commands:
|
||||
Claro v2026.10.20.223203 supports these main commands:
|
||||
|
||||
```bash
|
||||
claro file.claro
|
||||
@@ -58,7 +58,7 @@ claro package doctor
|
||||
claro package lock
|
||||
```
|
||||
|
||||
The v2026.10.20.160950 package command is a safer local project-file helper. It creates `claro.project`, `claro.lock`, and local folders under `packages/`. Package names may appear only once in `claro.project`; `claro package doctor` reports duplicate entries so the lockfile stays unambiguous. It also requires the project manifest to have exactly one non-empty `name:` field. It is not yet an online package registry.
|
||||
The v2026.10.20.223203 package command is a safer local project-file helper. It creates `claro.project`, `claro.lock`, and local folders under `packages/`. Package names may appear only once in `claro.project`; `claro package doctor` reports duplicate entries so the lockfile stays unambiguous. It also requires the project manifest to have exactly one non-empty `name:` field. It is not yet an online package registry.
|
||||
|
||||
## IDE metadata
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Compatibility syntax
|
||||
|
||||
You do not need these spellings for your first programs. Learn the
|
||||
[recommended beginner syntax](SYNTAX.md) first. These older forms remain
|
||||
supported so existing scripts do not break.
|
||||
|
||||
- Older `SET score TO 8` means the same as `SET score 8`.
|
||||
- Older `ASK "Number?" AS answer` means the same as `ASK "Number?" answer`.
|
||||
- Older `ENDIF`, `DONE`, and `LEARNED` close choices, loops, and functions;
|
||||
the beginner path uses `END` for all three.
|
||||
- Older `TEACH add TAKES a, b` and `CALL add WITH 2, 3` correspond to
|
||||
`TEACH add a b` and `DO add 2 3`.
|
||||
|
||||
This complete older-style program is covered by an executable compatibility test:
|
||||
|
||||
```claro
|
||||
TEACH add TAKES a, b
|
||||
RETURN a + b
|
||||
LEARNED
|
||||
CALL add WITH 2, 3
|
||||
SAY RESULT
|
||||
SET score TO 8
|
||||
ASK "Number?" AS answer
|
||||
IF answer = 4
|
||||
DO 2 TIMES
|
||||
SAY score
|
||||
DONE
|
||||
ELSE
|
||||
SAY "no"
|
||||
ENDIF
|
||||
```
|
||||
|
||||
Enter `4`. It prints `5`, asks `Number?`, then prints `8` twice.
|
||||
|
||||
Do not automatically replace every `AS` or `TO`: commands such as
|
||||
`READ FILE "note.txt" AS note` and `ADD "milk" TO items` still need those words.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Claro Complete Platform Roadmap
|
||||
|
||||
Current release: **Claro v2026.10.20.160950**
|
||||
Current release: **Claro v2026.10.20.223203**
|
||||
|
||||
This roadmap tracks the larger goal: Claro as a complete beginner-friendly programming platform, not only a toy scripting interpreter.
|
||||
|
||||
|
||||
+48
-305
@@ -1,322 +1,65 @@
|
||||
# Claro v2026.10.20.160950 Current Status
|
||||
# Claro current status
|
||||
|
||||
This file is the beginner-safe status map for the current package. It separates what is ready to use from what is historical, experimental, or still planned.
|
||||
Development version: **v2026.10.20.223203**
|
||||
|
||||
## How to read the status labels
|
||||
Claro is a **learning tool first**. The development timestamp identifies source;
|
||||
it does not mean that a release was published or every platform was tested.
|
||||
See [release status](RELEASE_STATUS.md) for dated, artifact-specific evidence.
|
||||
|
||||
- **Stable foundation**: good enough for normal examples and beginner learning.
|
||||
- **Foundation present**: usable, but still missing important polish or coverage.
|
||||
- **Experimental/planned**: do not rely on it in beginner lessons yet.
|
||||
- **Historical**: kept for release history, not current instructions.
|
||||
## Beginner core — tested learning path
|
||||
|
||||
## Security and correctness review progress
|
||||
- Input/output, variables, arithmetic, decisions, loops, lists, simple functions,
|
||||
and text-file save/load have executable lesson/example coverage.
|
||||
- Teach short `ASK` and `SET`, `TEACH` with `DO`, and `END` for introductory blocks.
|
||||
[Older forms](COMPATIBILITY.md) remain supported, outside the first lessons.
|
||||
- The [first hour](FIRST_HOUR.md), [ten lessons](../lessons/README.md), generated
|
||||
starter and introductory examples are checked for behavior, not just syntax.
|
||||
- The runtime remains one C89 file: [src/claro.c](../src/claro.c).
|
||||
|
||||
The v2026.10.20.160950 review identified missing-argument reads in standard-library built-ins. The current runtime now validates required argument counts centrally before any builtin indexes `args[]`. Covered calls include `math.abs`, `math.clamp`, `random.seed`, `random.int`, text helpers, CSV helpers, path helpers, and collection helpers. Missing arguments produce a beginner-facing `needs N arguments` runtime error.
|
||||
This is a foundation for learner trials, not proof of accessibility or classroom
|
||||
readiness. Intended learners have **not yet validated** accessibility; use the
|
||||
[classroom pilot checklist](CLASSROOM_READINESS.md) to gather that evidence.
|
||||
|
||||
Focused regression coverage: `tests/38_builtin_arity.claro`, `tests/39_random_inverted_range.claro`, and `tests/40_call_depth_guard.claro`.
|
||||
## Reliability and trust boundary
|
||||
|
||||
The runtime now rejects inverted `random.int` ranges before modulo arithmetic with: `random.int needs the lower bound to be less than or equal to the upper bound.` This prevents invalid ranges from producing incorrect values or a divide-by-zero signal.
|
||||
Maintained checks cover ownership cleanup, malformed expressions, nested blocks,
|
||||
call depth and the learning path under leak-enabled sanitizers. See
|
||||
[reliability](RELIABILITY.md) for scope and [testing](TESTING.md) to reproduce it.
|
||||
Earlier failure logs are history, not current unresolved findings.
|
||||
|
||||
User-defined function and object-method calls now have a documented maximum active call depth of 256. Exceeding it produces: `Claro function call depth exceeded the safe limit of 256. Simplify the recursion or add a stopping condition.` Ordinary non-recursive beginner programs are unaffected.
|
||||
Claro runs trusted scripts with the user's permissions. It is **not a sandbox**.
|
||||
Intentional loops can run indefinitely, and global file/JSON/CPU budgets are not
|
||||
provided. See [actual runtime guards](RUNTIME_LIMITS.md).
|
||||
|
||||
Verified in this checkout on 2026-09-23:
|
||||
## Platforms — do not confuse compilation with OS acceptance
|
||||
|
||||
- `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /tmp/claro-call-depth-asan -lm` plus `ASAN_OPTIONS=detect_leaks=0 /tmp/claro-call-depth-asan tests/40_call_depth_guard.claro`: expected diagnostic; no AddressSanitizer or UndefinedBehaviorSanitizer report.
|
||||
- `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /tmp/claro-arity-asan -lm` plus `ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 /tmp/claro-arity-asan tests/38_builtin_arity.claro`: all four missing-argument diagnostics; no sanitizer report.
|
||||
- `make -s all`: rebuilt both `claro` and `claro.exe` from current source.
|
||||
- `./claro test`: `PASS: 0 failure(s)`.
|
||||
- `./claro doctor`: all checks `OK`.
|
||||
- `./claro validate`: validation passed.
|
||||
- Strict GCC C89 and extracted source validation have passed on Linux.
|
||||
- Linux x86-64 static-musl downloads have been tested on the build host.
|
||||
- MinGW x64 and MSVC600 x86 executables have been built and tested under Wine.
|
||||
- The dedicated [Windows 98 SE build](WINDOWS98.md) uses `build98.bat`.
|
||||
- **Native modern Windows and Windows 98 SE execution remain unverified.**
|
||||
- macOS, ARM, DOS and other untested targets are not implied by C89 compliance.
|
||||
|
||||
The memory cleanup slices release the previous deep value when a runtime variable or map entry is overwritten, release temporary split argument arrays and strings from `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM`, release loaded program paths, lines, and pointer arrays at the end of each script run, and now release the remaining runtime-owned variables, functions, modules, classes, import paths, captured output, return value, and error strings before the interpreter exits. The expression evaluator now releases discarded intermediate `Value` operands and command boundaries release evaluated `SET`/`SAY` values after copying or printing them. `FOR EACH` now releases its copied collection after iteration, preventing discarded list/map copies from accumulating in long scripts. `IF` and `DO ... TIMES` now release their temporary control-expression values after branch/loop selection. `ASK` now releases evaluated prompt values and temporary input values after converting/copying them into runtime storage. Focused coverage is `tools/validate_memory_cleanup.py`, `tools/validate_expression_cleanup.py`, `tools/validate_control_flow_cleanup.py`, `tools/validate_control_expression_cleanup.py`, and `tools/validate_ask_prompt_cleanup.py`; each focused cleanup validator runs an ASan/UBSan build with LeakSanitizer checking. Verified on 2026-09-24: `python3 tools/validate_ask_prompt_cleanup.py` passes (2,000 prompt/input operations under ASan/UBSan/LSan); `make -s all`, `./claro test`, `./claro doctor`, and `./claro validate` all pass. Caught `TRY` errors now release their previous message and source-path values when cleared; `python3 tools/validate_runtime_error_cleanup.py` confirms caught builtin-arity and inverted-range errors pass ASan/UBSan/LSan. Additional malformed-input leak sites may remain, so this does not claim all runtime allocations are leak-free. The `COUNT ... AS` command now releases its evaluated list/map value after extracting the item count. `python3 tools/validate_count_expression_cleanup.py` first reproduced a 28,000-... [truncated]
|
||||
Exact versions and tested environments are in the [release ledger](RELEASE_STATUS.md).
|
||||
The [portability guide](PORTABILITY.md) explains the standard archive targets.
|
||||
|
||||
The `FIND ... IN ... AS ...` command now releases its evaluated search value and copied list/map after storing the result. Focused coverage is `tools/validate_find_expression_cleanup.py`; it runs 2,000 searches under ASan/UBSan with LeakSanitizer enabled. Verified on 2026-09-24: `python3 tools/validate_find_expression_cleanup.py` passes with no reported leaks; `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This slice is runtime-verified under sanitizers; broader malformed-input sanitizer coverage remains blocked by the previously documented `rt_error` message leak.
|
||||
## Existing optional features versus plans
|
||||
|
||||
`REMOVE` now releases its evaluated needle and copied list/map value after updating runtime storage. `python3 tools/validate_remove_expression_cleanup.py` passed with 2,000 repeated string-needle operations under ASan/UBSan/LSan. Removed elements from populated copied lists remain a separate ownership case. This slice is runtime-verified under sanitizers; malformed-input diagnostics still have the previously recorded `rt_error` leak.
|
||||
Types, objects, local projects/packages, HTTP and editor helpers remain available
|
||||
in their reference guides; beginners do not need them. Existing deterministic
|
||||
task helpers are not a claim of native parallel execution or a full scheduler.
|
||||
|
||||
`ADD ... TO ...` now releases both the evaluated item and copied list after `list_add` and `rt_set` have made their owned copies. `python3 tools/validate_add_expression_cleanup.py` first reproduced leaks under ASan/UBSan/LSan (2,000 repeated string concatenations), then passed after the ownership cleanup. Full checks passed: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, all existing focused cleanup validators, and `git diff --check`. This slice is runtime-verified under sanitizers; malformed-input diagnostics still have the previously recorded `rt_error` leak.
|
||||
Graphics/SDL, a web server, remote registry, richer concurrency and a full editor
|
||||
integration are plans or experiments, not beginner-ready platform promises.
|
||||
They are **not active expansion priorities** without a separate request.
|
||||
|
||||
`PUT ... KEY ... VALUE ...` now releases the evaluated map, key, and value copies after runtime storage has copied them. `python3 tools/validate_put_expression_cleanup.py` passes with 2,000 repeated string writes under ASan/UBSan/LSan. Verified 2026-09-26: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, the focused sanitizer validator, and `git diff --check` pass. Separate malformed-argument sanitizer smoke tests for the existing builtin-arity and inverted-range diagnostics do not report out-of-bounds access, but LeakSanitizer reports the already documented `rt_error` diagnostic-string leaks (552 bytes/14 allocations for arity cases; 79 bytes/2 allocations for the inverted-range case). This slice is runtime-verified; those error-path leaks remain a separate blocker to claiming sanitizer-clean diagnostics.
|
||||
## What is next
|
||||
|
||||
The `GET ARGUMENT ... AS ...` command now releases its evaluated index and copied temporary argument values after storage takes ownership. `tools/validate_get_argument_expression_cleanup.py` confirmed and fixed a 6,000-byte leak across 2,000 repetitions under ASan/UBSan/LSan; `GET ENV ... AS ...` now also releases its evaluated name value after conversion, with `tools/validate_get_env_expression_cleanup.py` detecting 1,688,000 leaked bytes across 2,000 lookups before the fix. Both focused checks are wired into the v1.17 validation script. This is runtime-verified narrow ownership work; other GET expression branches remain separate boundaries.
|
||||
1. Observe actual beginners and improve instructions/error recovery.
|
||||
2. Verify the Windows builds on their real target operating systems.
|
||||
3. Review the accumulated local changes and publish only when authorized.
|
||||
|
||||
The `RUN COMMAND` path now decodes POSIX `pclose()` wait status before storing `LASTEXIT`, so a child that exits with code 3 exposes `3` rather than the encoded status 768. Focused coverage is `tests/42_last_exit_code.claro`. HTTP responses now have a 1,048,576-byte cap and marker-like response bodies are preserved while extracting the final HTTP status marker. Focused coverage is `tools/validate_http_hardening.py`. Remaining memory-growth areas include other expression temporaries. Claro remains a trusted-script interpreter, not a sandbox. `RUN COMMAND` now releases its evaluated command expression and temporary captured-output value after copying output into runtime storage. The new `tools/validate_run_command_expression_cleanup.py` first detected 12,000 bytes leaked across 2,000 command executions, then passed under ASan/UBSan/LSan after the ownership fix.
|
||||
|
||||
`RUN COMMAND` is documented and validated as a trusted-code capability: it executes shell commands with the user's permissions and is not a sandbox. Claro does not claim untrusted-script safety or use a fragile blacklist sanitizer. Focused documentation coverage is `tools/validate_trusted_command_docs.py`.
|
||||
|
||||
`REMOVE` also releases the removed element from its copied list before shifting later elements. Focused verification: `python3 tools/validate_remove_list_item_cleanup.py` passed with 2,000 add/remove pairs under ASan/UBSan/LSan and no reported leaks. `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` also passed. This is a narrow, runtime-verified ownership cleanup; it does not change `REMOVE` syntax or behavior.
|
||||
|
||||
`GET ... KEY ... AS ...` now releases its temporary map and key values after lookup. `python3 tools/validate_get_key_expression_cleanup.py` reproduced 1,004,000 leaked bytes before the change and passes after it with ASan/UBSan/LSan enabled. `EXISTS FILE ... AS ...` now releases its evaluated path value after checking it; `python3 tools/validate_exists_expression_cleanup.py` first reproduced 62,000 leaked bytes across 2,000 calls, then passed with ASan/UBSan/LSan enabled. These are narrow expression-temporary cleanup slices, not a claim that all runtime allocations are leak-free.
|
||||
|
||||
`GET ... AT ... AS ...` now releases its evaluated index expression after converting it to a list position. `python3 tools/validate_get_at_expression_cleanup.py` first reproduced 4,000 leaked bytes across 2,000 string-valued indices, then passed under ASan/UBSan/LSan with the selected value preserved. The validator is wired into `tools/validate_v1_17.py`; this is a narrow ownership fix, not a claim that all runtime allocations are leak-free.
|
||||
|
||||
`LIST FOLDER ... AS ...` now releases its evaluated path and the temporary folder-list value after runtime storage copies the list. `python3 tools/validate_list_folder_expression_cleanup.py` reproduced 220,000 leaked bytes across 2,000 operations under ASan/UBSan/LSan before the change and passes after it with no leak report. The working-tree checks `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This is a narrow ownership improvement, not a claim that all runtime allocations are leak-free.
|
||||
The `DELETE FILE ...` and `DELETE FOLDER ...` commands now release their evaluated path values after converting the paths to owned strings. `python3 tools/validate_delete_expression_cleanup.py` first reproduced 178,000 leaked bytes across 2,000 `DELETE FILE` expression evaluations under ASan/UBSan/LSan, then passed after cleanup. This check covers expression ownership; malformed-input diagnostic paths still have the previously documented `rt_error` message leak. The `COPY FILE` and `MOVE FILE` commands now also release their evaluated source/destination path values after conversion to strings; `python3 tools/validate_copy_move_expression_cleanup.py` reproduced 74,000 bytes leaked across 1,000 copy/move pairs before the fix and is the focused ASan/UBSan/LSan regression gate. `CREATE FOLDER ...` now releases its evaluated path value after converting it to an owned path string; `python3 tools/validate_create_folder_expression_cleanup.py` reproduced 200,000 leaked bytes across 2,000 calls before the fix and passes with ASan/UBSan/LSan enabled. This narrow cleanup slice does not establish that all runtime allocations are leak-free.
|
||||
|
||||
`typecheck` now frees its temporary type environment on return, including the owned variable names and type-string values; replacing an inferred type releases the previous value first. `python3 tools/validate_typecheck_token_cleanup.py` reproduced 479 bytes of leaked type-environment allocations before the change, then passed under ASan/UBSan/LSan with the fix. The validator is named for the original local work file; it specifically checks type-environment cleanup, not tokenizer allocations. This is a narrow cleanup slice and does not establish that all typecheck metadata allocations are leak-free.
|
||||
|
||||
`claro check` now releases expression-token arrays and strings after variable-name analysis, and frees its temporary variable environment at the end of each check. The untracked focused regression validator `tools/validate_check_token_cleanup.py` first reproduced 310,447 leaked bytes across 2,000 repeated-string expressions; after the cleanup, `python3 tools/validate_check_token_cleanup.py` passes under ASan/UBSan/LSan. Fresh verification: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This is a narrow, runtime-verified cleanup for check mode; it does not establish that all allocations are leak-free.
|
||||
|
||||
`READ FILE ... AS ...` now releases both its evaluated path and the temporary file-content value after copying contents into runtime storage. `python3 tools/validate_read_file_expression_cleanup.py` reproduced 204,000 leaked bytes across 2,000 reads before the fix, then passed under ASan/UBSan/LSan. The check is wired into `tools/validate_v1_17.py`; this is one verified cleanup boundary, not a claim that all file-command temporaries are leak-free.
|
||||
|
||||
`WRITE FILE` and `APPEND FILE` now release their evaluated path and content values after the operation. `python3 tools/validate_write_file_expression_cleanup.py` reproduced 206,000 leaked bytes across 2,000 writes before the fix; the focused ASan/UBSan/LSan validator is wired into `tools/validate_v1_17.py`. Other file-command temporary ownership remains separate.
|
||||
|
||||
## Feature matrix
|
||||
|
||||
### Beginner scripting core
|
||||
|
||||
Status: **Stable foundation**
|
||||
|
||||
Ready now:
|
||||
- variables with `SET`
|
||||
- output with `SAY`
|
||||
- input with `ASK`
|
||||
- choices with `IF` / `ELSE` / `END`
|
||||
- loops
|
||||
- lists and maps
|
||||
- files, JSON, text helpers, and tested path/collection library helpers
|
||||
- friendly checking with `claro check`
|
||||
|
||||
Good starting docs:
|
||||
- `QUICK_START.md`
|
||||
- `FIRST_HOUR.md`
|
||||
- `CLI.md`
|
||||
- `SPEC.md`
|
||||
|
||||
### Functions
|
||||
|
||||
Status: **Stable foundation**
|
||||
|
||||
Ready now:
|
||||
- simple modern form: `TEACH name arg` ... `END`
|
||||
- compatibility form: `TEACH name TAKES arg` ... `LEARNED`
|
||||
- calls with `DO` or older `CALL ... WITH`
|
||||
|
||||
Good starting docs:
|
||||
- `SIMPLE_FUNCTIONS.md`
|
||||
- `README.md`
|
||||
|
||||
### Static type safety
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- typed variables such as `SET score NUMBER 10`
|
||||
- `TYPE OF` and `CHECK TYPE`
|
||||
- `CHECK TYPE` gives a direct repair hint when the learner forgets the `IS` keyword, for example `CHECK TYPE score` reports `CHECK TYPE needs IS. Try: CHECK TYPE score IS NUMBER.`
|
||||
- `CHECK TYPE` gives a direct repair hint when `IS` has no expected type, for example `CHECK TYPE score IS` reports `CHECK TYPE score needs a type after IS. Try: CHECK TYPE score IS NUMBER.`
|
||||
- `CHECK TYPE` explains when the learner leaves out the expression as well, for example bare `CHECK TYPE` reports `CHECK TYPE needs an expression, IS, and a type. Try: CHECK TYPE score IS NUMBER.`
|
||||
- typed function and method returns reject simple extra tokens after a return value with a direct repair hint, such as `RETURN amount extra` reporting `Keep only one expression after RETURN.` This is covered for modern functions and both modern and compatibility object methods.
|
||||
- `CHECK TYPE` explains when `IS` is present but the expression is missing, for example `CHECK TYPE IS NUMBER` reports `CHECK TYPE needs an expression before IS. Try: CHECK TYPE score IS NUMBER.`
|
||||
- `CHECK TYPE` rejects extra words after a valid expected type with a direct repair hint, so `CHECK TYPE score IS NUMBER TEXT` explains that only one type belongs in the check
|
||||
- `CHECK TYPE` rejects unknown expected type names with a beginner-facing list of supported types
|
||||
- typed function return diagnostics also identify an unknown return expression in compatibility `TAKES` / `LEARNED` functions, matching the modern function and object-method guidance
|
||||
- `TYPE OF` reports a direct repair hint when the learner forgets `AS`, for example `TYPE OF score` suggests `TYPE OF score AS kind`
|
||||
- bare `TYPE OF` reports that the expression, `AS`, and result name are all required, with a complete repair example
|
||||
- `TYPE OF` reports a direct repair hint when the learner forgets the expression before `AS`, for example `TYPE OF AS kind` reports `TYPE OF needs an expression before AS. Try: TYPE OF score AS kind.`
|
||||
- `TYPE OF` prioritizes the missing expression diagnostic even when extra words follow the incomplete form, so `TYPE OF AS kind extra` still points the learner to the missing expression first.
|
||||
- `TYPE OF` reports a direct repair hint when the learner forgets the result name after `AS`, for example `TYPE OF score AS` reports `TYPE OF needs a result name after AS. Try: TYPE OF score AS kind.`
|
||||
- A valid `TYPE OF score AS kind` statement has a focused positive typecheck fixture, so the complete form is protected from diagnostic-only regression coverage.
|
||||
- `TYPE OF` remains case-insensitive like other Claro keywords, with focused positive coverage for lowercase `type of score as kind` syntax.
|
||||
- `CHECK TYPE` remains case-insensitive like other Claro keywords, with focused positive coverage for lowercase `check type score is number` syntax.
|
||||
- Object declarations and field checks remain case-insensitive like other Claro keywords, with focused positive coverage for lowercase `class`, `has`, `end`, `new`, `set`, and `check type` in one typed object-field example.
|
||||
- `TYPE OF` rejects extra words after its result name with a direct repair hint, so `TYPE OF score AS kind extra` explains that only one result name belongs there; typed `TEACH ... RETURNS TYPE` declarations likewise reject extra words after the return type with a direct repair hint
|
||||
- typed `TEACH ... RETURNS` declarations reject a missing return type with a direct repair hint, such as `Function greet needs a return type after RETURNS. Add a type such as NUMBER.` The same diagnostic names the complete class and method, including compatibility `TAKES` / `LEARNED` methods: `Method Player.score needs a return type after RETURNS. Add a type such as NUMBER.`
|
||||
- typed list/map checks through `claro typecheck`, including a nested `LIST OF MAP` insertion example
|
||||
- compatibility `TAKES` / `LEARNED` functions also have focused positive numeric-addition, subtraction, division, and multiplication return fixtures, keeping arithmetic return coverage aligned with modern functions and compatibility methods.
|
||||
- compatibility `TAKES` / `LEARNED` functions have positive complete-branch return coverage beside the incomplete-branch diagnostic, so older conditional examples are checked for both accepted and rejected paths.
|
||||
- a narrow function/method argument check: `CHECK TYPE parameter IS TYPE` inside a function or simple object method lets `claro typecheck` accept correct checked calls, catch mismatched `DO`, `CALL ... WITH`, `DO object.method ...`, and compatibility `CALL ... WITH` arguments, including checked methods that appear after another method in the same class, report missing checked function and method arguments, report missing unchecked arguments for simple functions and simple object methods in both modern `DO` and compatibility `CALL ... WITH` forms, treat empty compatibility calls such as `CALL greet WITH` and `CALL player.rename WITH` as missing-argument mistakes, catch extra arguments to simple functions and checked methods even when the checked method appears after another method in the same class, catch modern `DO` and compatibility `CALL ... WITH` calls to undeclared simple functions, explain when `DO object.method ...` or compatibility `CALL ...` happens before the object is created with `NEW` even if the method name is also wrong, and catch modern plus compatibility calls to undeclared object methods with a class-specific `TEACH` hint
|
||||
- simple function and object-method return checks with `TEACH name ... RETURNS TYPE` now include a compatibility `TAKES` / `LEARNED` function whose conditional returns are incomplete, preserving the same every-path diagnostic as modern functions.
|
||||
- function and method declarations now reject repeated parameter names with a direct repair hint before calls are checked, including modern and compatibility `TAKES` / `LEARNED` functions and methods; object methods with the same name are also rejected within a class with a rename hint in both modern and compatibility method syntax
|
||||
- top-level function declarations now reject repeated names with a direct repair hint, so two `TEACH greet` blocks cannot silently compete for one callable function; a missing function name after `TEACH` gets a direct beginner-facing repair hint, and a missing method name inside `CLASS` names the class and gives a method example
|
||||
- class declarations now reject a missing class name after `CLASS` with a direct beginner-facing repair hint
|
||||
- class declarations now reject repeated field names within the same class with a direct repair hint before field types are used, so two `HAS score ...` lines cannot silently choose conflicting metadata; different classes may independently use the same field name
|
||||
- class field declarations now reject missing names such as bare `HAS`, missing types such as `HAS score`, unknown types such as `HAS score BANANA`, and extra tokens such as `HAS score NUMBER TEXT` with the field name, class name, supported type examples, and a repair hint before object-field checks use that metadata
|
||||
- class declarations now reject repeated class names with a direct repair hint, so two `CLASS Player` blocks cannot silently compete for the same name; extra words after a class name also get a direct repair hint instead of becoming part of the class identity
|
||||
- object creation now rejects repeated object names with a direct repair hint, so two `NEW Player player` statements cannot silently replace one another during type checking; when a script declares at least one class, a misspelled `NEW` class name gets a direct declaration hint without changing the older permissive no-class form; a missing class name or object name gets a direct example repair; extra words after the object name get a direct repair hint instead of being silently ignored
|
||||
- a narrow object-field assignment/check-type check for simple `NEW Class object` plus direct `SET object.field value` and `CHECK TYPE object.field IS TYPE` cases when the class declares `HAS field TYPE`; validation now covers correct NUMBER, TEXT, and YESNO direct assignments, dedicated positive and explicitly typed NUMBER/TEXT field assignments, missing values in untyped direct field assignments with a repair hint, explicitly typed method-body assignments to undeclared fields with a `HAS field TYPE` repair hint in modern and compatibility method syntax, dedicated positive NUMBER/TEXT/YESNO `CHECK TYPE` metadata fixtures, simple and chained object aliases for both field assignments and `CHECK TYPE` metadata (including a chained TEXT-field check), aliased object-method calls including chained aliases in modern `DO` and compatibility `CALL ... WITH` forms (with a dedicated positive modern `DO` chained-alias fixture), field-to-field, compound arithmetic field expressions, and arithmetic/text expression result types, negative NUMBER/TEXT/YESNO field `CHECK TYPE` metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type diagnostics, field collection when a `HAS` field appears after a simple method, NUMBER/TEXT/YESNO-valued unknown-field diagnostics for direct assignments to undeclared fields including explicitly typed assignments, numeric and text results from simple arithmetic/text expressions in object-field assignments, plus method-body field assignment and `CHECK TYPE` diagnostics for NUMBER, TEXT, and YESNO fields in modern and compatibility syntax, including compatibility YESNO `CHECK TYPE` metadata coverage, direct method assignments to undeclared fields now produce a beginner-facing missing-field diagnostic when the assigned expression has a known type, and a plain beginner-facing unknown-field diagnostic when the assigned expression is not inferable yet in both modern and compatibility method syntax; method-body `CHECK TYPE` now reports an unknown field with a repair hint in both method syntaxes, including compatibility `TAKES` / `LEARNED` methods; explicitly typed assignments to declared method fields now validate the class-declared field type instead of trusting only the inline type annotation, so a wrong value such as `SET score TEXT \"oops\"` reports the method and field in the diagnostic.
|
||||
- inline method-field annotations are checked against the class `HAS` declaration in modern and compatibility methods; conflicting known types and unknown annotation names produce field-specific repair guidance, with positive and negative focused fixtures for both syntaxes.
|
||||
- direct object-field `AS ... TO` assignments reject extra words after the value with a repair hint, so `SET player.score AS NUMBER TO 10 extra` cannot silently ignore the trailing text; they explain when `TO` is missing after the type and when the value is missing after `TO`; short explicitly typed assignments such as `SET player.score NUMBER 10 extra` receive the same repair-oriented check and explain when the value is missing after the type
|
||||
- Direct object-field validation also has positive coverage for explicit `NUMBER`, `TEXT`, and `YESNO` annotations, such as `SET player.ready YESNO YES`, plus the compatibility-shaped `AS ... TO` form such as `SET player.score AS NUMBER TO 10`, while the shorter unannotated form remains supported; method-body field assignments have matching positive coverage for both modern and compatibility `TEACH` forms using `SET score AS NUMBER TO 10`, and those two `AS ... TO` method fixtures are now included in `claro validate`; conflicting annotations such as `SET player.score TEXT "oops"` explain the class-declared type and how to repair it, and unknown annotations such as `SET player.score AS BANANA TO 10` or `SET player.score BANANA 10` identify the invalid type and suggest the declared field type.
|
||||
|
||||
Still needed:
|
||||
- Keep the focused typecheck validator's fixture list complete as new positive and negative examples are added. `claro validate` now executes the complete focused fixture matrix as well, so release validation cannot silently omit a listed typecheck example. Short explicitly typed field assignments inside modern and compatibility methods also have focused coverage: `SET score NUMBER` explains that one value expression is required after the type, matching direct object-field assignment guidance.
|
||||
- The expression checker now carries a known TEXT operand through all arithmetic operators so object-field and typed-function-return diagnostics do not hide addition, subtraction, multiplication, or division mistakes; focused numeric-subtraction, numeric-division, and numeric-multiplication positives plus text-operand addition, subtraction, multiplication, and division negatives protect this behavior. Typed-function return diagnostics now have focused subtraction and division positives alongside multiplication coverage. Text concatenation into a TEXT field has focused positive fixtures for both operand orders, including concatenation of two TEXT object fields. Each numeric operator names the text operand and explains that it needs NUMBER values.
|
||||
- Method return validation now has focused positive subtraction and division fixtures alongside the existing arithmetic return coverage, including compatibility `TAKES` / `LEARNED` subtraction and division cases, so checked NUMBER method parameters and numeric subtraction or division remain accepted by the release validator in both method spellings.
|
||||
- Modern and compatibility `TAKES` / `LEARNED` methods have positive NUMBER, TEXT, and YESNO field-assignment coverage, and modern plus compatibility method-body `CHECK TYPE` metadata now have positive YESNO coverage beside the wrong-value diagnostics.
|
||||
- Modern method-body TEXT concatenation has positive coverage in both operand orders, such as `SET first first + "!"` and `SET last "Dr. " + last`.
|
||||
- Compatibility method-body TEXT concatenation has matching positive coverage in both operand orders, so `TAKES` / `LEARNED` methods keep the same beginner-safe field update behavior.
|
||||
- richer typed function signatures and return values beyond the focused simple function/method `RETURNS TYPE` check
|
||||
- type checking through branches and loops
|
||||
- richer object field type checking beyond simple direct assignments
|
||||
- typed imports/modules
|
||||
|
||||
|
||||
Good starting docs:
|
||||
- `ADVANCED_STATIC_TYPING.md`
|
||||
- `V1_14_STATIC_TYPES.md` (feature history plus examples)
|
||||
|
||||
### Objects and classes
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- `CLASS`
|
||||
- typed `HAS` fields
|
||||
- `NEW`
|
||||
- field access such as `player.score`
|
||||
- simple methods, including static diagnostics for checked method parameters
|
||||
- direct object-field assignments with a narrow static diagnostic for wrong value types
|
||||
- object helper commands
|
||||
|
||||
Still needed:
|
||||
- broader object field type checking across aliases, methods, and more complex object flows
|
||||
- constructor/default-value polish
|
||||
- clearer object debugging and teaching examples
|
||||
|
||||
Good starting docs:
|
||||
- `V1_15_OBJECTS_CLASSES.md`
|
||||
- `README.md`
|
||||
|
||||
### Package and project workflow
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- `claro new`
|
||||
- `claro package init`
|
||||
- `claro package add/list/remove/doctor/lock`
|
||||
- local project files such as `claro.project`, `claro.lock`, and `packages/`
|
||||
- starter projects from `claro new` now use the same manifest-version and lock-version headers as `claro package init`
|
||||
- the default `make` target builds both `claro` and `claro.exe`, so cross-platform release validators exercise freshly built executables
|
||||
- project-name safety checks for `claro new`, so unsafe names such as `../bad` are rejected before Claro creates folders
|
||||
- package-name safety checks when adding packages, including the 64-character package-name limit, when `claro package doctor` audits an existing `claro.project`, when `claro package lock` writes lockfile data, when `claro package list` shows existing packages, when `claro package init` sees unsafe names already present in `claro.project`, when `claro package add` sees unsafe names already present in `claro.project`, when `claro package remove` refreshes the lockfile after an edit, when learners need to remove an unsafe package entry that is already present, when `claro package doctor` verifies lockfile checksums for listed packages, and when `claro package doctor` rejects lockfile package entries that are not listed in `claro.project`
|
||||
- `claro package doctor` rejects a listed package manifest whose `name:` does not match the package name in `claro.project`
|
||||
- `claro package doctor` compares the complete manifest `name:` value, so a prefix such as `math-tools-extra` cannot be accepted for package `math-tools`
|
||||
- `claro package doctor` requires exactly one complete supported package `manifest-version: 1` field; missing, blank, unsupported, or duplicate format fields get a `BAD package manifest version` diagnostic with the file path and a repair hint, rather than calling an existing file missing
|
||||
- `claro package doctor` compares the complete manifest `checksum:` value, so a valid checksum followed by extra text is rejected
|
||||
- `claro package doctor` requires the complete supported `manifest-version: 1` value in `claro.project`, so a prefix such as `manifest-version: 10` is rejected
|
||||
- `claro package doctor` requires the complete supported `version: 1` value in each local package manifest, so a prefix such as `version: 10` is rejected
|
||||
- `claro package doctor` requires exactly one current `version: v2026.10.20.160950` line in `claro.lock`, so stale, missing, blank, or duplicate lockfile release versions are rejected
|
||||
- `claro package doctor` requires the complete supported `source: local` value in each local package manifest, so a prefix such as `source: local-extra` is rejected
|
||||
- package project entries must be unique; `claro package doctor`, `list`, `add`, `init`, and `lock` reject duplicate names instead of generating ambiguous package or lockfile data
|
||||
- lockfile package entries must also be unique; `claro package doctor` reports duplicate lock entries instead of accepting ambiguous package/checksum data
|
||||
- package manifest `name:` entries must also be unique; `claro package doctor` reports `BAD package manifest name` instead of accepting ambiguous package identity data
|
||||
- package manifest `checksum:` entries must also be unique; `claro package doctor` reports `BAD package checksum` instead of accepting ambiguous integrity data
|
||||
- package manifest `version:` entries must also be unique and must contain exactly `1`; `claro package doctor` reports `BAD package version` instead of accepting ambiguous package metadata
|
||||
- project manifests must contain exactly one `manifest-version: 1` field; duplicate or prefix-matching values are rejected with `BAD project manifest version: expected 1`
|
||||
- project manifests must contain exactly one non-empty `name:` field; `claro package doctor` reports `BAD project manifest name: expected a non-empty name` when the project identity is missing or blank
|
||||
|
||||
Still needed:
|
||||
- install from local path
|
||||
- package version constraints
|
||||
- publish/export format
|
||||
- remote registry protocol
|
||||
- checksum/signature verification before downloads
|
||||
|
||||
Good starting docs:
|
||||
- `V1_16_PACKAGES_PROJECTS.md`
|
||||
- `PACKAGE_REGISTRY.md`
|
||||
|
||||
### Networking
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- beginner HTTP commands
|
||||
- offline `claro://` URLs for tests and lessons
|
||||
- `LASTHTTP`
|
||||
- real `http://` and `https://` through `curl` when available
|
||||
|
||||
Still needed:
|
||||
- web server syntax
|
||||
- routes
|
||||
- request/response helpers
|
||||
- localhost-safe server mode
|
||||
|
||||
Good starting docs:
|
||||
- `V1_17_NETWORKING.md`
|
||||
- `WEB_SERVER_PLAN.md`
|
||||
|
||||
### Concurrency and tasks
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- deterministic task/concurrency helpers where documented
|
||||
- beginner-safe direction toward cooperative tasks
|
||||
|
||||
Still needed:
|
||||
- full cooperative scheduler semantics
|
||||
- cancellation
|
||||
- timeouts
|
||||
- message passing
|
||||
- decision on whether native threads should ever be exposed
|
||||
|
||||
Good starting docs:
|
||||
- `CONCURRENCY.md`
|
||||
|
||||
### IDE/editor support
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- `claro ide`
|
||||
- metadata JSON
|
||||
- completion list, including the current typed-language keywords `RETURNS`, `CHECK`, and `TYPE`
|
||||
- diagnostics helper
|
||||
- Forgejo/Gitea CI coverage for the current release validation gates (`claro validate`, typecheck diagnostics validation, version convention validation, package security validation, compiler warning validation, and the CI workflow coverage check); the compiler warning validator rebuilds `src/claro.c` and rejects path-truncation warnings, while the workflow validator verifies these commands, including its own command, occur in executable `run` steps rather than comments
|
||||
|
||||
Still needed:
|
||||
- syntax highlighting package
|
||||
- editor extension
|
||||
- hover help
|
||||
- go-to-definition
|
||||
- code actions/fixes
|
||||
- full LSP server if/when useful
|
||||
|
||||
Good starting docs:
|
||||
- `IDE.md`
|
||||
- `EDITOR_EXTENSION_PLAN.md`
|
||||
|
||||
### Graphics and SDL
|
||||
|
||||
Status: **Experimental/planned**
|
||||
|
||||
Ready now:
|
||||
- placeholder/planned graphics documentation
|
||||
- experimental examples may exist for future SDL work
|
||||
|
||||
Not ready in the stable executable:
|
||||
- real SDL window/drawing support
|
||||
- beginner game lessons that run everywhere
|
||||
|
||||
Good starting docs:
|
||||
- `GRAPHICS.md`
|
||||
- `SDL12.md`
|
||||
|
||||
## Historical docs
|
||||
|
||||
Files named `RC*_NOTES.md`, `RC*_VALIDATION.md`, older `V1_*_VALIDATION.md`, and older release notes are kept for project history. They may mention old version numbers, old planned milestones, or old validation scripts. Use them to understand how Claro evolved; do not treat them as the current beginner path.
|
||||
|
||||
## Which docs should a new learner trust first?
|
||||
|
||||
For the current v2026.10.20.160950 package, read docs in this order:
|
||||
|
||||
1. `QUICK_START.md`, `FIRST_HOUR.md`, and `lessons/README.md` for first programs.
|
||||
2. `README.md` and this file for the current feature map.
|
||||
3. `ROADMAP.md` for the next v1 work.
|
||||
|
||||
Use feature docs with these expectations:
|
||||
|
||||
- **Ready for beginner lessons:** `SPEC.md`, `SIMPLE_FUNCTIONS.md`, `ERRORS.md`, `LINTER.md`, `TESTING.md`, `FORMATTER.md`.
|
||||
- **Foundation present:** `ADVANCED_STATIC_TYPING.md`, `V1_14_STATIC_TYPES.md`, `V1_15_OBJECTS_CLASSES.md`, `V1_16_PACKAGES_PROJECTS.md`, `V1_17_NETWORKING.md`, `CONCURRENCY.md`, `IDE.md`.
|
||||
- **Plans or experiments:** `PACKAGE_REGISTRY.md`, `WEB_SERVER_PLAN.md`, `EDITOR_EXTENSION_PLAN.md`, `GRAPHICS.md`, `SDL12.md`, `COMPLETE_PLATFORM_ROADMAP.md`, `FUTURE_FEATURES_ROADMAP.md`.
|
||||
|
||||
If a doc sounds more ambitious than this status map, treat this file as the current source of truth and update the older doc before teaching from it.
|
||||
|
||||
## 2026-09-28 random.int full-range arithmetic follow-up
|
||||
|
||||
`random.int` now calculates its inclusive range width and result in `long long`, avoiding signed-int overflow for the full accepted C `int` range. The regression fixture is `tests/43_random_int_extreme_bounds.claro`.
|
||||
|
||||
Verified: built with `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /home/ubuntu/.hermes/profiles/maxwell/cache/scratch/claro-rng-probe -lm`; ran `ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 /home/ubuntu/.hermes/profiles/maxwell/cache/scratch/claro-rng-probe tests/43_random_int_extreme_bounds.claro` (exit 0, no sanitizer report). Before the change, the same full-range call failed with UBSan signed integer overflow at `src/claro.c:347`. This validates the extreme-range runtime behavior under ASan/UBSan; `make -s all`, `./claro test` (0 failures, including this fixture), `./claro doctor`, `./claro validate`, and `git diff --check` pass.
|
||||
Follow the [small active roadmap](ROADMAP.md). The
|
||||
[previous detailed status](history/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md) and
|
||||
[hardening log](history/HARDENING_BEFORE_STATUS_CLEANUP.md) preserve older claims,
|
||||
implementation notes and investigations; they are not current acceptance criteria.
|
||||
|
||||
@@ -1,19 +1,55 @@
|
||||
# Claro Final Release Checklist
|
||||
# Release checklist
|
||||
|
||||
Before publishing `Claro v2026.10.20.160950` as the current stable release:
|
||||
This is an **unchecked template**, not a claim that every item is still missing
|
||||
from earlier verified builds. Record evidence for the exact candidate; see
|
||||
[release status](RELEASE_STATUS.md) for completed artifact checkpoints.
|
||||
|
||||
- Build from a clean unzip on Windows.
|
||||
- Build from a clean unzip on Linux or macOS.
|
||||
- Run `claro --version`.
|
||||
- Run `claro test`.
|
||||
- Run `claro validate`.
|
||||
- Run every lesson that does not require user input.
|
||||
- Manually test `examples/quiz.claro`.
|
||||
- Manually test `examples/simple_functions.claro`.
|
||||
- Manually test `examples/text_polish.claro`.
|
||||
- Confirm the README shows the Claro logo.
|
||||
- Confirm docs use the `v1.xx.yy` version format.
|
||||
- Confirm beginner docs teach simple syntax first.
|
||||
- Do not add large new systems before stable v1.
|
||||
## Identify the candidate
|
||||
|
||||
Avoid adding large new systems during a stability release; prioritize buildability, tests, documentation consistency, and beginner safety.
|
||||
- [ ] Review the working tree and preserve unrelated local changes.
|
||||
- [ ] Stamp a new run once with `python3 tools/stamp_version.py`, using UTC
|
||||
`vYYYY.MM.DD.HHMMSS`; keep that stamp through rebuilding and verification.
|
||||
- [ ] Confirm runtime, IDE metadata, generated starter/lockfiles and current docs
|
||||
agree. Never re-label old verified binaries with a new timestamp.
|
||||
- [ ] Name each intended target and distinguish build, Wine and native OS tests.
|
||||
|
||||
## Verify source and learning behavior
|
||||
|
||||
- [ ] Build both executable names required by the developer checks: `make -s all`.
|
||||
- [ ] Run `python3 tools/validate_v1_17.py` (the current full gate despite its name).
|
||||
- [ ] Run compiler-warning, CI-coverage and documentation-status checks from
|
||||
[testing](TESTING.md). Confirm expected failures, skips and actual exit codes.
|
||||
- [ ] Exercise interactive lessons and common mistakes with intended learners;
|
||||
automated examples alone do not validate accessibility.
|
||||
- [ ] Keep the recommended introductory syntax consistent and preserve aliases.
|
||||
- [ ] Describe trust boundaries and unverified behavior without claiming a sandbox.
|
||||
|
||||
## Verify deliverables, not just the checkout
|
||||
|
||||
- [ ] Build clean archives with [distribution tooling](PORTABILITY.md).
|
||||
- [ ] Extract each artifact, verify its inventory/hashes, and test its actual
|
||||
executable, lessons, files and paths containing spaces.
|
||||
- [ ] Record artifact/source SHA-256, compiler, environment, results and limitations.
|
||||
- [ ] Include the license and applicable runtime notices; exclude compiler/SDK
|
||||
bundles, credentials, caches and development build outputs.
|
||||
- [ ] For the VC6 target, compile through [build98.bat](../build98.bat) without
|
||||
a global `intptr_t` macro; inspect PE32, subsystem 4.10 and stack reserve.
|
||||
- [ ] If claiming Windows or Windows 98 SE runtime support, execute the tests on
|
||||
that actual OS. Otherwise label it unverified; Wine is not a substitute.
|
||||
- [ ] Confirm checksums identify exact files, not publisher authenticity/signatures.
|
||||
|
||||
The generic archive helper covers source, Linux x64 and MinGW Windows x64.
|
||||
The separately verified VC6 build ZIP has its own checksum manifest and report;
|
||||
do not claim it was produced or accepted by that helper.
|
||||
|
||||
## Publish only with authorization
|
||||
|
||||
- [ ] Review and test the exact proposed commit/package snapshot.
|
||||
- [ ] Obtain authorization before committing, pushing or publishing releases.
|
||||
- [ ] Verify the remote branch after a push and download/test hosted artifacts
|
||||
before claiming a hosted release or CI result.
|
||||
- [ ] Update the dated release ledger with the actual version, hashes, scope,
|
||||
publication location and remaining limitations. Keep older evidence intact.
|
||||
|
||||
Do not expand the language to complete a release. Reliability, clear learning
|
||||
instructions and honest platform claims take priority.
|
||||
|
||||
+3
-6
@@ -2,6 +2,9 @@
|
||||
|
||||
This is a simple first-hour lesson path for new learners. Keep each lesson short. Let the learner run the program after every change.
|
||||
|
||||
Use the same short forms throughout this guide. See [the syntax rules](SYNTAX.md)
|
||||
for a small reference; older forms are kept in [compatibility syntax](COMPATIBILITY.md).
|
||||
|
||||
## 1. Say hello
|
||||
|
||||
```claro
|
||||
@@ -15,8 +18,6 @@ ASK "What is your name?" name
|
||||
SAY "Hello " + name
|
||||
```
|
||||
|
||||
Older examples may use `ASK "What is your name?" AS name`; that still works, but the shorter form is easier for new learners.
|
||||
|
||||
## 3. Store a variable
|
||||
|
||||
```claro
|
||||
@@ -54,8 +55,6 @@ DO 3 TIMES
|
||||
END
|
||||
```
|
||||
|
||||
Older examples may use `DONE` for loop endings; that compatibility form still works.
|
||||
|
||||
## 7. Use a list
|
||||
|
||||
```claro
|
||||
@@ -85,8 +84,6 @@ Expected output:
|
||||
5
|
||||
```
|
||||
|
||||
Older examples may use `TEACH add TAKES a, b`, `LEARNED`, and `CALL add WITH 2, 3`; those forms still work.
|
||||
|
||||
## 9. Save and load text
|
||||
|
||||
```claro
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Claro Graphics Status
|
||||
|
||||
Current release: **Claro v2026.10.20.160950**
|
||||
Current release: **Claro v2026.10.20.223203**
|
||||
|
||||
Graphics support is intentionally separated from the stable beginner console workflow.
|
||||
|
||||
|
||||
+14
-116
@@ -1,119 +1,17 @@
|
||||
# Hardening Notes (Beta23)
|
||||
# Hardening notes: current entry point
|
||||
|
||||
## File loading safety
|
||||
Claro now reads source lines dynamically (no 4KB truncation).
|
||||
The earlier Beta23 notes and later cleanup investigation are **historical**.
|
||||
Their former failure reports, resource-cap claims and pending-work statements
|
||||
must not be read as the status of today's tested runtime.
|
||||
|
||||
Safety caps (to prevent memory abuse):
|
||||
- Maximum single line length: 65,535 characters
|
||||
- Maximum program lines: 500,000
|
||||
- [Current reliability checkpoint](RELIABILITY.md): repairs, maintained checks
|
||||
and their limitations, including the resolved OBJECT ownership regressions.
|
||||
- [Actual runtime guards](RUNTIME_LIMITS.md): implemented bounds and absent limits.
|
||||
- [Testing](TESTING.md): commands to reproduce current evidence.
|
||||
- [Preserved hardening log](history/HARDENING_BEFORE_STATUS_CLEANUP.md): earlier
|
||||
observations retained without rewriting them as modern acceptance results.
|
||||
|
||||
If a file exceeds these limits, the loader fails cleanly.
|
||||
|
||||
## Expression-token cleanup
|
||||
|
||||
Each expression now releases its token strings and token-array storage before returning. This is a narrow cleanup boundary; runtime-owned variables, loaded programs, and other allocations remain separate follow-up work.
|
||||
|
||||
Focused verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_memory_cleanup.py
|
||||
```
|
||||
|
||||
The validator builds an AddressSanitizer/UndefinedBehaviorSanitizer binary, runs a repeated variable-overwrite probe, and confirms LeakSanitizer no longer reports allocations from `tokenize`/`toks_add`. The interpreter remains a trusted-script runtime, not a sandbox.
|
||||
|
||||
## Overwritten-value cleanup
|
||||
|
||||
Runtime variables and map entries own deep copies of their values. Replacing an existing variable or map entry now releases the previous string, list, or map value before storing its replacement. This is intentionally limited to overwrite boundaries; final runtime teardown remains a follow-up cleanup slice.
|
||||
|
||||
## Split-argument cleanup
|
||||
|
||||
Command argument lists created by `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM` are temporary parser storage. They now share one cleanup helper, so repeated calls do not retain the duplicated argument strings or pointer array. The helper does not change argument evaluation or syntax compatibility.
|
||||
|
||||
Focused verification builds with AddressSanitizer/UndefinedBehaviorSanitizer, repeatedly exercises a four-argument `DO`, and checks the cleanup helper before confirming the existing string, list, and map overwrite behavior.
|
||||
|
||||
The `REMOVE` command now releases its evaluated needle and copied list/map value after updating runtime storage. Focused verification: `python3 tools/validate_remove_expression_cleanup.py` runs 2,000 discarded string needles under ASan/UBSan/LSan; it passed with no reported leaks. This slice does not yet address the separate ownership of an item removed from a populated copied list.
|
||||
|
||||
When `REMOVE` finds an item in its copied list, it now frees that removed item before shifting the remaining owned values left. `python3 tools/validate_remove_list_item_cleanup.py` repeatedly adds and removes strings under ASan/UBSan/LSan to check that the item ownership is released without changing the list update behavior.
|
||||
|
||||
The `PUT ... KEY ... VALUE ...` command now releases its evaluated map, key, and value copies after `map_put`/`rt_set` have copied the data they own. Focused verification: `python3 tools/validate_put_expression_cleanup.py` exercises 2,000 string-valued writes under ASan/UBSan/LSan and passes with no reported leaks. This covers expression-temporary ownership only; caught runtime diagnostic-value leaks are covered by the separate cleanup slice below.
|
||||
|
||||
Caught runtime errors release the previous owned error-message and source-path strings when `TRY` clears the error state. `python3 tools/validate_runtime_error_cleanup.py` covers repeated caught builtin-arity errors and an inverted `random.int` range under ASan/UBSan/LSan.
|
||||
|
||||
Verified 2026-10-06: `python3 tools/validate_runtime_error_cleanup.py` passes with LeakSanitizer enabled; `make -s all`, `./claro test` (0 failures), `./claro doctor`, and `./claro validate` pass. The focused validator is also part of `tools/validate_v1_17.py`. This addresses caught diagnostic-value ownership only; it does not claim every malformed-input path is leak-free.
|
||||
|
||||
The `GET ARGUMENT ... AS ...` command now releases both its evaluated index expression and temporary argument value after runtime storage has copied the result. `python3 tools/validate_get_argument_expression_cleanup.py` first reproduced 6,000 leaked bytes over 2,000 accesses, then passes with AddressSanitizer, UndefinedBehaviorSanitizer, and LeakSanitizer enabled. This is a focused ownership cleanup; other `GET` forms have separate temporary-value ownership boundaries.
|
||||
|
||||
`GET ENV ... AS ...` now releases its evaluated environment-name value after converting it to the lookup string. `python3 tools/validate_get_env_expression_cleanup.py` reproduced 1,688,000 leaked bytes across 2,000 lookups before the fix and checks the same repeated lookup under ASan/UBSan/LSan. This is a narrow ownership improvement; other `GET` expression branches remain separate boundaries.
|
||||
|
||||
|
||||
HTTP responses are capped at 1,048,576 bytes. Exceeding the cap produces a beginner-facing runtime error instead of retaining an unbounded response. The curl status suffix is taken from the final status marker, so a response body containing marker-like text is preserved. Existing `HTTP CHECK` URL safety rules remain unchanged.
|
||||
|
||||
Focused verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_http_hardening.py
|
||||
```
|
||||
|
||||
This validator uses a local HTTP server to check marker-like response text, status `200`, and the oversized-response diagnostic.
|
||||
|
||||
## READ FILE expression cleanup
|
||||
|
||||
`READ FILE ... AS ...` now releases its evaluated path value and the temporary file-content value after runtime storage copies the contents. `python3 tools/validate_read_file_expression_cleanup.py` first reproduced 204,000 leaked bytes across 2,000 reads under ASan/UBSan/LSan; after the cleanup it passes with the expected output and no reported leak. This is a narrow ownership fix; other file-command temporaries remain separate.
|
||||
|
||||
`WRITE FILE ... WITH ...` and `APPEND FILE ... WITH ...` now release their evaluated path and content values after writing. `python3 tools/validate_write_file_expression_cleanup.py` reproduced 206,000 leaked bytes across 2,000 writes before the cleanup; it verifies repeated writes under ASan/UBSan/LSan and checks the resulting file content.
|
||||
|
||||
## Verification
|
||||
|
||||
2026-10-07: `python3 tools/validate_read_file_expression_cleanup.py` passes under ASan/UBSan/LSan; `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `python3 tools/validate_v1_17.py` all pass. Runtime-verified narrow cleanup; full validation does not establish that every allocation path is leak-free.
|
||||
|
||||
## External command trust boundary
|
||||
|
||||
`RUN COMMAND` intentionally executes a shell command with the user's permissions. It is a trusted-code capability, not a sandbox or an untrusted-script safety feature. Claro does not attempt a fragile blacklist sanitizer; users must review scripts before running them.
|
||||
|
||||
Focused documentation verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_trusted_command_docs.py
|
||||
```
|
||||
|
||||
## Control-flow expression cleanup
|
||||
|
||||
Conditions evaluated by `IF` are temporary runtime values. The `IF` command now
|
||||
releases its condition after choosing a branch, including when the condition is
|
||||
a text expression. This is a narrow cleanup boundary; other expression
|
||||
temporaries remain separate follow-up work.
|
||||
|
||||
Focused verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_control_expression_cleanup.py
|
||||
```
|
||||
|
||||
The validator builds with AddressSanitizer/UndefinedBehaviorSanitizer,
|
||||
executes 2,000 temporary `IF` conditions under LeakSanitizer, and checks the
|
||||
expected output.
|
||||
|
||||
## ASK temporary-value cleanup
|
||||
|
||||
`ASK` releases the evaluated prompt value after converting it to display text,
|
||||
and releases the temporary input value after `rt_set_checked` copies it into
|
||||
runtime storage. This keeps prompt and input strings from accumulating during
|
||||
repeated input loops without changing prompt or input behavior.
|
||||
|
||||
Focused verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_ask_prompt_cleanup.py
|
||||
```
|
||||
|
||||
The validator runs 2,000 prompt/input operations with AddressSanitizer, UndefinedBehaviorSanitizer, and LeakSanitizer enabled. The dedicated validator passes. A separate malformed-input sanitizer smoke run (`gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o ... -lm` followed by the generated malformed script) exits 1 due to two existing leaked error-message strings (140 bytes total) allocated in `rt_error` at `src/claro.c:142`; this is outside the ASK cleanup slice and remains a blocker to sanitizer-clean malformed-input validation.
|
||||
|
||||
## GET KEY temporary-value cleanup
|
||||
|
||||
`GET ... KEY ... AS ...` releases its evaluated map copy and key value after storing the selected value. `python3 tools/validate_get_key_expression_cleanup.py` runs 2,000 lookups under ASan/UBSan/LSan; before the cleanup it reproduced 1,004,000 bytes leaked, and after it passes with the expected output and no reported leaks.
|
||||
|
||||
`GET ... AT ... AS ...` now releases its evaluated index expression after converting it to a list position. The regression validator `python3 tools/validate_get_at_expression_cleanup.py` repeats a string-valued index 2,000 times under ASan/UBSan/LSan; before the fix, LeakSanitizer reported 4,000 leaked bytes, and after it passes with the expected selected value.
|
||||
|
||||
## COPY/MOVE expression temporary cleanup
|
||||
|
||||
`COPY FILE ... TO ...` and `MOVE FILE ... TO ...` release both evaluated path values after converting them to owned path strings. `python3 tools/validate_copy_move_expression_cleanup.py` runs 1,000 copy/move pairs under ASan/UBSan/LSan; before the cleanup it reproduced 74,000 bytes leaked across 4,000 path-expression values, and after it passes with no reported leaks.
|
||||
Claro runs trusted scripts with the user's permissions and is **not a sandbox**.
|
||||
Passing maintained regressions does not establish universal leak freedom or
|
||||
untrusted-script safety. Treat newly reproduced failures as new findings with
|
||||
exact versions, inputs, diagnostics and regression tests.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Claro Performance and Optimization Notes
|
||||
|
||||
Current release: **Claro v2026.10.20.160950**
|
||||
Current release: **Claro v2026.10.20.223203**
|
||||
|
||||
Performance work should come after language behavior is stable and measured. Do not optimize blindly.
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Distribution and portability
|
||||
|
||||
See the dated [release ledger](RELEASE_STATUS.md) for exact verified archive
|
||||
versions and publication status. A development stamp is not a hosted release.
|
||||
|
||||
## Supported verification targets
|
||||
|
||||
- **Source:** one C89 implementation file, `src/claro.c`. The distribution checker
|
||||
compiles a copy in isolation, without the rest of the project, then builds and
|
||||
validates the full extracted source package.
|
||||
- **Linux x86-64:** a statically linked musl executable named `claro`. It does not
|
||||
depend on the build host's glibc or a separately installed musl shared library.
|
||||
- **Windows x86-64:** a genuine PE executable named `claro.exe`, built with MinGW.
|
||||
Current execution verification uses Wine on Linux, not a native Windows machine.
|
||||
|
||||
The Linux and Windows archives include lessons, examples, documentation, regression
|
||||
fixtures, source and tooling. No compiler or Python is required merely to run the
|
||||
included executable and lessons. Developer checks require Python and build tools.
|
||||
|
||||
## Build archives
|
||||
|
||||
From the repository root on the tested Debian/Ubuntu x86-64 build host:
|
||||
|
||||
```bash
|
||||
python3 tools/build_distribution.py --target source --output /path/to/releases
|
||||
python3 tools/build_distribution.py --target linux-x86_64 --output /path/to/releases
|
||||
python3 tools/build_distribution.py --target windows-x86_64 --output /path/to/releases
|
||||
```
|
||||
|
||||
Binary builds require `musl-tools` or the x64 MinGW cross-compiler respectively.
|
||||
The helper also includes installed toolchain copyright notices; its notice paths
|
||||
currently match the tested Debian/Ubuntu packages. It refuses to overwrite an archive.
|
||||
|
||||
Each archive has:
|
||||
|
||||
- `START_HERE.txt` with target-specific instructions.
|
||||
- `DISTRIBUTION.json` identifying the target, compiler, command and source hash.
|
||||
- `MANIFEST.json` listing SHA-256 hashes for every other packaged file.
|
||||
- The project license and, for binaries, third-party runtime notices.
|
||||
|
||||
A checksum detects changes; it is not a digital signature or proof of publisher identity.
|
||||
Generated executables/caches from the development tree are not copied into releases.
|
||||
In particular, the Linux archive does not contain a misleading Linux `claro.exe`.
|
||||
|
||||
## Windows 98 SE / MSVC6 compilation
|
||||
|
||||
Use the dedicated [Windows 98 build guide](WINDOWS98.md) and `build98.bat`.
|
||||
This is a separate x86 build, not the MinGW x64 archive target. Compiler and
|
||||
Wine verification are distinct from native Windows 98 SE acceptance. The separately
|
||||
verified VC6 build ZIP carries its own checksum manifest/report; it is not a
|
||||
target accepted by the generic builder or distribution checker below.
|
||||
|
||||
## Windows stack reserve
|
||||
|
||||
The default MinGW PE stack reserve was too small for the existing 256-call
|
||||
recursion limit: the initial Windows test crashed before reaching Claro's guard.
|
||||
A 16 MiB stack reserve allowed the call-depth regression and full golden suite
|
||||
to complete under Wine. Windows build scripts and the archive builder now set:
|
||||
|
||||
```text
|
||||
-Wl,--stack,16777216
|
||||
```
|
||||
|
||||
This reserves address space; pages are committed as needed. Use the Windows build
|
||||
scripts, or include this setting when cross-compiling directly. It is a linker
|
||||
requirement, not a claim that C89 source alone establishes OS portability.
|
||||
|
||||
## Verify the actual archives
|
||||
|
||||
```bash
|
||||
python3 tools/validate_distribution.py /path/to/archive.zip --report /path/to/report.json
|
||||
```
|
||||
|
||||
For Windows execution from Linux, first initialize a separate Wine prefix and supply
|
||||
`--wine-prefix /path/to/isolated-prefix`. The checker will not silently use the default
|
||||
Wine profile. Reports must have a new filename.
|
||||
|
||||
The checker extracts into a directory containing spaces, verifies every manifest
|
||||
entry, checks executable format (including the Windows stack reserve), and executes:
|
||||
|
||||
- The executable alone, with a tiny script and no project files.
|
||||
- `--version`, `doctor`, all packaged golden fixtures, and built-in validation.
|
||||
- The exact introductory learning path and all nine First Hour examples.
|
||||
- Full Python release validation for the source archive.
|
||||
|
||||
Source archive validation rebuilds within the extracted copy; it does not rely on
|
||||
the development checkout's executable. Binary tests run the shipped binaries.
|
||||
|
||||
## Limits of the evidence
|
||||
|
||||
Wine verifies Windows executable/CRT behavior in a compatibility environment. It
|
||||
is not native Windows acceptance, Windows 98/XP support, or an installer test.
|
||||
The PE header's minimum subsystem version does not establish compatibility.
|
||||
|
||||
Likewise, static Linux linking removes a glibc-version dependency but does not prove
|
||||
operation on every distribution, old kernel, CPU or architecture. ARM, 32-bit Linux, macOS, DOS and real older Windows systems require separate
|
||||
builds and tests. The separate x86 MSVC6 compilation path does not establish
|
||||
Windows 98 SE execution.
|
||||
|
||||
Real HTTP still requires curl. Offline networking fixtures need no Internet access.
|
||||
Claro remains a trusted-script runtime, not a sandbox. Full validation is evidence
|
||||
for the exercised behavior, not a claim that every possible script is correct.
|
||||
+24
-12
@@ -6,6 +6,17 @@ Programming Made Simple
|
||||
|
||||
Claro is a small scripting language designed for beginners. It uses readable commands like `SAY`, `ASK`, `SET`, and `TEACH`.
|
||||
|
||||
## Before you start
|
||||
|
||||
Build Claro with `make` from the project folder, or use its C89 build command
|
||||
in the [README](../README.md). Save the first example below as `hello.claro`
|
||||
in that folder. Run the command shown beneath it. Try the later examples one
|
||||
at a time, saving your changes before running again.
|
||||
|
||||
Use one spelling throughout these lessons: short `ASK` and `SET`, `TEACH`
|
||||
with `DO`, and `END` for choices, counted loops, and functions. The
|
||||
[syntax guide](SYNTAX.md) keeps these rules in one place.
|
||||
|
||||
## 1. Show a message
|
||||
|
||||
```claro
|
||||
@@ -21,25 +32,27 @@ Run it:
|
||||
## 2. Ask a question
|
||||
|
||||
```claro
|
||||
ASK "What is your name?" AS name
|
||||
ASK "What is your name?" name
|
||||
SAY "Hello " + name
|
||||
```
|
||||
|
||||
## 3. Remember information
|
||||
|
||||
```claro
|
||||
SET score TO 10
|
||||
SET score 10
|
||||
SAY score
|
||||
```
|
||||
|
||||
## 4. Make a choice
|
||||
|
||||
```claro
|
||||
SET score 8
|
||||
|
||||
IF score >= 5
|
||||
SAY "Good job!"
|
||||
ELSE
|
||||
SAY "Try again."
|
||||
ENDIF
|
||||
END
|
||||
```
|
||||
|
||||
## 5. Repeat something
|
||||
@@ -47,29 +60,28 @@ ENDIF
|
||||
```claro
|
||||
DO 3 TIMES
|
||||
SAY "Practice"
|
||||
DONE
|
||||
END
|
||||
```
|
||||
|
||||
## 6. Use a list
|
||||
|
||||
```claro
|
||||
SET items TO LIST
|
||||
SET items LIST
|
||||
ADD "milk" TO items
|
||||
ADD "bread" TO items
|
||||
|
||||
FOR EACH item IN items
|
||||
SAY item
|
||||
DONE
|
||||
END
|
||||
```
|
||||
|
||||
## 7. Teach a function
|
||||
|
||||
```claro
|
||||
TEACH greet TAKES name
|
||||
TEACH greet name
|
||||
RETURN "Hello " + name
|
||||
LEARNED
|
||||
|
||||
CALL greet WITH "Alex"
|
||||
END
|
||||
DO greet "Alex"
|
||||
SAY RESULT
|
||||
```
|
||||
|
||||
@@ -85,13 +97,13 @@ SAY note
|
||||
|
||||
```claro
|
||||
SAY "Tiny Quiz"
|
||||
ASK "What is 2 + 2?" AS answer
|
||||
ASK "What is 2 + 2?" answer
|
||||
|
||||
IF answer = 4
|
||||
SAY "Correct!"
|
||||
ELSE
|
||||
SAY "The answer is 4."
|
||||
ENDIF
|
||||
END
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
+22
-8
@@ -7,8 +7,17 @@
|
||||
3. [Ten lessons](../lessons/README.md) — small programs in order.
|
||||
4. [Examples](../examples/) — try a quiz, guessing game, or shopping list.
|
||||
|
||||
## Status, evidence and safety
|
||||
|
||||
- [Current status](CURRENT_STATUS.md) — the short, current scope and next priorities.
|
||||
- [Release status](RELEASE_STATUS.md) — dated artifact verification and publication status.
|
||||
- [Classroom pilot](CLASSROOM_READINESS.md) — learner testing, not accessibility certification.
|
||||
- [Reliability](RELIABILITY.md) and [runtime guards](RUNTIME_LIMITS.md) — tested boundaries, not sandbox guarantees.
|
||||
- [Portability](PORTABILITY.md) and [Windows 98 / VC6](WINDOWS98.md) — compilation versus target execution.
|
||||
|
||||
## Look something up
|
||||
|
||||
- [Beginner syntax](SYNTAX.md) and [older compatibility forms](COMPATIBILITY.md)
|
||||
- [Commands](CLI.md)
|
||||
- [Errors and repairs](ERRORS.md)
|
||||
- [Simple functions](SIMPLE_FUNCTIONS.md)
|
||||
@@ -17,16 +26,21 @@
|
||||
|
||||
## For contributors
|
||||
|
||||
- [Build and test](../CONTRIBUTING.md)
|
||||
- [Learning-first rules](LEARNING_FIRST.md)
|
||||
- [Current roadmap](ROADMAP.md)
|
||||
- [Detailed implementation status](CURRENT_STATUS.md)
|
||||
- [Build and contribute](../CONTRIBUTING.md)
|
||||
- [Testing](TESTING.md) and [release checklist](FINAL_RELEASE_CHECKLIST.md)
|
||||
- [Learning-first rules](LEARNING_FIRST.md) and [small active roadmap](ROADMAP.md)
|
||||
- [Language specification](SPEC.md)
|
||||
- [Versioning](VERSIONING.md)
|
||||
|
||||
## History and optional features
|
||||
|
||||
Older RC notes, versioned validation reports, and platform-expansion plans
|
||||
remain in this directory for reference. They are not prerequisites for learning
|
||||
and are not the active roadmap. The [previous roadmap](history/ROADMAP_BEFORE_LEARNING_FIRST.md)
|
||||
records the earlier direction. Existing features have not been removed.
|
||||
Older RC notes, versioned reports and expansion plans are reference, not the
|
||||
active roadmap or prerequisites for learners. Existing features remain supported;
|
||||
they have not been removed to simplify the teaching path.
|
||||
|
||||
- [Previous roadmap](history/ROADMAP_BEFORE_LEARNING_FIRST.md)
|
||||
- [Previous detailed status](history/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md)
|
||||
- [Preserved hardening log](history/HARDENING_BEFORE_STATUS_CLEANUP.md)
|
||||
|
||||
If an older plan conflicts with the current status or learning-first rules,
|
||||
treat it as historical rather than as a promised feature.
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Release status and verified builds
|
||||
|
||||
## Publication snapshot — 2026-10-20
|
||||
|
||||
The accumulated learning-syntax, reliability, distribution and Windows 98 build
|
||||
changes are **not published** to Gitea `main` in the check performed for this cleanup.
|
||||
The local HEAD and a read-only `git ls-remote --heads gitea main` both returned:
|
||||
|
||||
```text
|
||||
8ea2f772cd8552d4651bf551bed9a8a03b4ed2a9
|
||||
```
|
||||
|
||||
That commit is the earlier single-file C89 refactor, not the current dirty tree.
|
||||
This is a dated branch check, not a live remote badge. Hosted tags, release pages
|
||||
and hosted CI were not audited by that check; no new hosted release or CI pass
|
||||
is claimed. Refresh the remote state before any authorized publication.
|
||||
|
||||
A development timestamp is not a stable-release designation. Use
|
||||
[current status](CURRENT_STATUS.md) for the learning scope and
|
||||
[the release checklist](FINAL_RELEASE_CHECKLIST.md) before publishing.
|
||||
|
||||
## Immutable artifact checkpoints
|
||||
|
||||
These are verified build artifacts previously delivered for testing, **not a
|
||||
claim of a hosted release**. Their reports/logs were delivered outside Git.
|
||||
The filenames and SHA-256 values identify the exact archives; they are not
|
||||
public download URLs or digital signatures. Do not substitute a newly stamped
|
||||
binary for one of these archived checkpoints.
|
||||
|
||||
### Source — v2026.10.20.184951
|
||||
|
||||
- Archive: `Claro-v2026.10.20.184951-source.zip`.
|
||||
- SHA-256: `3531d2fc82e2d008fee8affc82864a56e7d8f5c917ef5556f5cfac0b3b828d38`.
|
||||
- Extracted-source verification on Linux: isolated strict C89 compilation, make,
|
||||
golden/learning/First Hour checks and the full release gate, including 35
|
||||
maintained reliability checks. This is not proof for an arbitrary OS/compiler.
|
||||
- Report: `source-verification.json`.
|
||||
|
||||
### Linux x86-64 — v2026.10.20.180131
|
||||
|
||||
- Archive: `Claro-v2026.10.20.180131-linux-x86_64.zip`.
|
||||
- SHA-256: `9f53394643e1bd9360940ed90e77af89a43ad160f976bf12fabe500bda0d16e6`.
|
||||
- Static-musl ELF; extracted payload passed all 51 golden fixtures, learning
|
||||
checks, nine First Hour examples, built-in validation and standalone checks
|
||||
on the Linux x86-64 build host. Other distributions/kernels/CPUs are unverified.
|
||||
- Report: `final-linux-verification.json`.
|
||||
|
||||
### MinGW Windows x86-64 — v2026.10.20.180131
|
||||
|
||||
- Archive: `Claro-v2026.10.20.180131-windows-x86_64.zip`.
|
||||
- SHA-256: `59b452ccfe96848fa45db3915aa7d03610cc5fba35cf6a294718d1cff67c6ec0`.
|
||||
- Genuine PE x64, 16 MiB stack; extracted executable passed all 51 golden fixtures,
|
||||
learning checks, nine First Hour examples and built-in/standalone checks under
|
||||
**Wine on Linux**. Native Windows execution remains **unverified**.
|
||||
- Report: `final-windows-verification.json`.
|
||||
|
||||
### MSVC600 / Windows 98 SE compilation — v2026.10.20.184951
|
||||
|
||||
- Archive: `Claro-v2026.10.20.184951-Win98SE-VC6-build.zip`.
|
||||
- SHA-256: `914d14373ec6f202fea4ff74ed0ffbde20e0ff3a9d708b9a2ac72ddf37c488cd`.
|
||||
- Actual Microsoft compiler **12.00.8804** compiled/linked through `build98.bat`
|
||||
without an `intptr_t` alias flag. PE32 x86, subsystem 4.10, static VC6 CRT,
|
||||
16 MiB stack reserve, only KERNEL32.dll imported.
|
||||
- The executable passed all 51 golden fixtures, the introductory checker, nine
|
||||
First Hour examples and built-in validation under **Wine**. The final ZIP was
|
||||
re-extracted, its manifest/source/binary hashes checked, and its golden suite,
|
||||
doctor, version and built-in validation rerun.
|
||||
- **Actual Windows 98 SE execution remains unverified.** PE headers and Wine
|
||||
behavior do not establish the real OS loader, CRT, console or filesystem behavior.
|
||||
- Reports: `WINDOWS98_BUILD_REPORT.md` and `package-verification.json`.
|
||||
- Frozen source SHA-256: `b3efcf9f3d60627a2b98159d3638c3b3639cb329998017c163d815046c32d1bf`.
|
||||
|
||||
## Reproduce or extend evidence
|
||||
|
||||
[Testing](TESTING.md), [portability](PORTABILITY.md) and the
|
||||
[Windows 98 build guide](WINDOWS98.md) describe the commands and boundaries.
|
||||
The standard archive helper covers source/Linux x64/MinGW x64. The separate VC6
|
||||
build ZIP uses its own checksum manifest and report, not the generic validator.
|
||||
|
||||
macOS, ARM, native modern Windows, Windows 98 SE/XP, DOS and other targets need
|
||||
separate execution evidence. Accessibility needs intended-learner feedback;
|
||||
automated tests alone do not establish it. Claro is trusted code, not a sandbox.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Reliability checkpoint
|
||||
|
||||
Use [release status](RELEASE_STATUS.md) for artifact-specific evidence and
|
||||
publication state. This page describes tested boundaries, not a universal pass.
|
||||
|
||||
The known OBJECT cleanup investigation is resolved for the maintained regression
|
||||
cases. Historical notes in `HARDENING.md` describe earlier failures, not the current
|
||||
status of those tested boundaries.
|
||||
|
||||
## Repairs and coverage
|
||||
|
||||
- Borrowed type/class metadata no longer creates abandoned deep copies.
|
||||
- Checked assignments, object copies, construction, and OBJECT result temporaries
|
||||
release owned values after storage has copied them.
|
||||
- Function declaration and typechecker parameter arrays, per-file type metadata,
|
||||
text transforms, sorting, and REPL teardown have explicit cleanup.
|
||||
- A lone `!` no longer leaves the tokenizer at the same input position until memory
|
||||
is exhausted. It produces a `NOT` repair hint; `!=` remains supported.
|
||||
- Missing quotes/parentheses/operands and trailing expression tokens are diagnosed.
|
||||
- Expression recursion and nested control blocks have bounded guardrails. Deep
|
||||
block input no longer writes beyond the checker's fixed array.
|
||||
- Existing function recursion, HTTP response-size, and trusted-command boundaries
|
||||
retain their focused checks.
|
||||
|
||||
## Reproduce verification
|
||||
|
||||
```bash
|
||||
make -s all
|
||||
python3 tools/validate_reliability.py
|
||||
python3 tools/validate_v1_17.py
|
||||
```
|
||||
|
||||
The current reliability runner executes 35 maintained checks and records each exit
|
||||
status and log in a scratch result directory. It includes the full introductory
|
||||
learning-path checker and First Hour under ASan/UBSan/LeakSanitizer, with leak
|
||||
detection enabled. Full release validation invokes this runner; CI has explicit
|
||||
reliability, single-file C89, and learning-path checks.
|
||||
|
||||
The OBJECT regression verifies class name, field count and field content, not a
|
||||
fictional printed list representation. Five isolated ownership tests separate
|
||||
metadata, field storage and object construction so one leak cannot hide another.
|
||||
Malformed-expression and nesting tests check failure diagnostics and valid inputs.
|
||||
|
||||
## Scope
|
||||
|
||||
A passing run establishes the exercised boundaries, not universal leak freedom,
|
||||
security or correctness for arbitrary programs. POSIX sanitizer coverage does not
|
||||
establish native Windows behavior. See [runtime limits](RUNTIME_LIMITS.md) for
|
||||
implemented guards and [portability](PORTABILITY.md) for distribution evidence.
|
||||
Intentional infinite loops and untrusted-script execution are not made safe by
|
||||
these checks. Historical beta environment-limit claims are not current guarantees.
|
||||
+25
-26
@@ -1,33 +1,32 @@
|
||||
# Runtime Guards & Limits (Beta25)
|
||||
# Current runtime guards
|
||||
|
||||
Claro includes optional safety limits to prevent hangs and memory abuse.
|
||||
These are implemented guardrails, not a sandbox or a promise that arbitrary
|
||||
scripts have bounded resource use.
|
||||
|
||||
## 1) Step limit (runaway protection)
|
||||
Environment variable:
|
||||
- `CLARO_STEP_LIMIT=200000`
|
||||
- **Function/method calls:** at most 256 active calls. Excessive recursion raises
|
||||
a Claro error. Other limits can stop a program earlier.
|
||||
- **Expressions:** at most 128 nested parser levels, covering parentheses and
|
||||
unary operations. Excessive nesting produces a diagnostic instead of
|
||||
continuing recursive parsing.
|
||||
- **Control blocks:** at most 128 additional nested control-block frames. The
|
||||
runtime and syntax checker reject deeper blocks. Function-call frames have
|
||||
their own guard.
|
||||
- **HTTP response bodies:** limited to 1,048,576 bytes by the existing HTTP path.
|
||||
|
||||
Stops a program if it executes too many steps/lines.
|
||||
Malformed expressions such as a lone `!`, an unclosed quote/parenthesis,
|
||||
a missing operand, or trailing expression tokens are rejected by the runtime.
|
||||
Use `NOT` for logical negation; `!=` remains a supported comparison.
|
||||
|
||||
## 2) File size limit
|
||||
Environment variable:
|
||||
- `CLARO_MAX_FILE_BYTES=16777216` (default 16 MiB)
|
||||
## Not provided
|
||||
|
||||
Applies to:
|
||||
- `READ FILE ...`
|
||||
- `WRITE FILE ... WITH ...`
|
||||
- `APPEND FILE ... WITH ...`
|
||||
The former beta document described `CLARO_STEP_LIMIT`, `CLARO_MAX_FILE_BYTES`,
|
||||
and `CLARO_MAX_JSON_BYTES`. Those environment controls are not implemented in
|
||||
the current `src/claro.c`; do not depend on them. The original document is
|
||||
preserved under [history](history/RUNTIME_LIMITS_BETA25.md).
|
||||
|
||||
## 3) JSON size limit
|
||||
Environment variable:
|
||||
- `CLARO_MAX_JSON_BYTES=8388608` (default 8 MiB)
|
||||
Ordinary intentional loops can still run indefinitely. This checkpoint does
|
||||
not establish a general JSON recursion limit, global file/output-size limit,
|
||||
CPU budget, or untrusted-script protection. Scripts and `RUN COMMAND` execute
|
||||
with the user's permissions.
|
||||
|
||||
Applies to:
|
||||
- `PARSE JSON ...`
|
||||
- `MAKE JSON ...`
|
||||
- `MAKE JSON PRETTY ...`
|
||||
|
||||
## 4) Error output
|
||||
Runtime errors include:
|
||||
- `file:line: message`
|
||||
- stack trace (CALL chain)
|
||||
- and JSON parse errors include `Near: ...` snippet
|
||||
See [reliability verification](RELIABILITY.md) for the tested scope.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
SDL 1.2 support is **not included in the current stable executable**.
|
||||
|
||||
The `examples/experimental_sdl/` files are design experiments / future examples only. In Claro v2026.10.20.160950, running them with the included `claro.exe` will fail with:
|
||||
The `examples/experimental_sdl/` files are design experiments / future examples only. In Claro v2026.10.20.223203, running them with the included `claro.exe` will fail with:
|
||||
|
||||
```text
|
||||
Unknown command: SDL
|
||||
|
||||
@@ -12,12 +12,6 @@ END
|
||||
DO greet "Jon"
|
||||
```
|
||||
|
||||
Older compatible style:
|
||||
|
||||
```claro
|
||||
TEACH greet TAKES name
|
||||
SAY "Hello " + name
|
||||
LEARNED
|
||||
|
||||
CALL greet WITH "Jon"
|
||||
```
|
||||
`TEACH` defines an action. `DO` runs it. `END` closes the definition.
|
||||
Older programs are still supported; see [compatibility syntax](COMPATIBILITY.md)
|
||||
only when reading one.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Claro Language Specification
|
||||
|
||||
Current package: **Claro v2026.10.20.160950**
|
||||
Current package: **Claro v2026.10.20.223203**
|
||||
Status: **Living v1 specification**
|
||||
|
||||
This document defines the intended behavior of the Claro language. It is written to reduce breaking changes and keep the v1 line teachable and predictable.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Claro's beginner syntax
|
||||
|
||||
Learn one spelling for each action. Use these same forms in the Quick Start,
|
||||
First Hour, numbered lessons, and introductory examples.
|
||||
|
||||
- Show text: `SAY "Hello!"`
|
||||
- Ask for input: `ASK "What is your name?" name`
|
||||
- Store a value: `SET score 0`
|
||||
- Change a value: `SET score score + 1`
|
||||
- Make a list: `SET items LIST`
|
||||
- Add an item: `ADD "milk" TO items`
|
||||
- Make a choice: `IF condition`, optional `ELSE`, then `END`.
|
||||
- Repeat a fixed number of times: `DO 3 TIMES`, then `END`.
|
||||
- Visit list items: `FOR EACH item IN items`, then `END`.
|
||||
- Define an action: `TEACH greet name`, then `END`.
|
||||
- Run an action: `DO greet "Ada"`.
|
||||
- Return an answer from an action: `RETURN value`; read it as `RESULT` after `DO`.
|
||||
- Save text: `WRITE FILE "note.txt" WITH "Hello"`.
|
||||
- Read text: `READ FILE "note.txt" AS note`.
|
||||
|
||||
## Small rules
|
||||
|
||||
1. Put one command on each line. Write keywords in uppercase in lessons to make
|
||||
them easy to spot; the interpreter also accepts lowercase keywords.
|
||||
2. Put literal text in double quotes. Do not quote a variable when you want its value.
|
||||
3. Use a simple name such as `score`, `name`, or `items` consistently.
|
||||
4. Indent the body of a choice, loop, or action by four spaces for readability.
|
||||
5. Close introductory `IF`, `DO ... TIMES`, `FOR`, and `TEACH` blocks with `END`.
|
||||
6. Separate function parameters and simple call arguments with spaces.
|
||||
|
||||
`AS` is still part of commands that name a result, such as `READ FILE`.
|
||||
`TO` is still part of `ADD item TO list`. The short forms above do not mean
|
||||
removing those words from every command.
|
||||
|
||||
The optional `REPEAT` loop finishes with `UNTIL condition`, not `END`;
|
||||
learn it after the introductory loops. Other advanced commands are outside this
|
||||
first learning path.
|
||||
|
||||
Follow [First Hour](FIRST_HOUR.md) for complete programs and the
|
||||
[numbered lessons](../lessons/README.md) for practice. Older spellings still run;
|
||||
[compatibility syntax](COMPATIBILITY.md) is only needed when reading older programs.
|
||||
+55
-35
@@ -1,47 +1,67 @@
|
||||
# Testing Claro
|
||||
|
||||
Run the built-in regression suite:
|
||||
Run from the repository root. Rebuild rather than trusting an old executable.
|
||||
The developer gate below is exercised on Linux; Windows sanitizer checks may
|
||||
skip and do not establish native Windows or Win98 SE acceptance.
|
||||
|
||||
## Full developer gate
|
||||
|
||||
```bash
|
||||
make -s all
|
||||
python3 tools/validate_v1_17.py
|
||||
python3 tools/validate_compiler_warnings.py
|
||||
python3 tools/validate_ci_workflow.py
|
||||
python3 tools/validate_documentation_status.py
|
||||
```
|
||||
|
||||
`validate_v1_17.py` is the current full gate despite its historical filename.
|
||||
It compiles current source and runs strict C89, VC6 source/build settings, golden
|
||||
fixtures, built-in validation, First Hour, introductory syntax/behavior, editor
|
||||
metadata, deterministic task helpers, package security, typecheck diagnostics,
|
||||
reliability, version consistency and documentation-status checks.
|
||||
|
||||
The [reliability runner](RELIABILITY.md) includes leak-enabled ASan/UBSan learning
|
||||
coverage. Read each exit status and skip; a disabled sanitizer or platform skip
|
||||
is not a pass for that boundary. No test suite proves arbitrary-program safety.
|
||||
|
||||
## Small changes and teaching examples
|
||||
|
||||
```bash
|
||||
./claro --version
|
||||
./claro test
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
```text
|
||||
PASS: 0 failure(s)
|
||||
```
|
||||
|
||||
Check a lesson:
|
||||
|
||||
```bash
|
||||
./claro check lessons/01_hello.claro
|
||||
```
|
||||
|
||||
Validate the current v1.18.26 package after building:
|
||||
|
||||
```bash
|
||||
./claro doctor
|
||||
./claro validate
|
||||
python tools/validate_typecheck_diagnostics.py
|
||||
python tools/validate_version_convention.py
|
||||
python tools/validate_ci_workflow.py
|
||||
python3 tools/validate_learning_syntax.py
|
||||
python3 tools/validate_first_hour.py
|
||||
```
|
||||
|
||||
Useful focused validation scripts:
|
||||
A successful built-in golden run ends with `PASS: 0 failure(s)`. The learning
|
||||
checkers execute input-dependent examples with fixed input and compare output;
|
||||
a syntax-only check is not sufficient. `CLARO_EXE` can select a specific actual
|
||||
executable through a launcher, including a sanitizer or Wine build.
|
||||
|
||||
```bash
|
||||
python tools/validate_v1_17.py
|
||||
python tools/validate_v1_16.py
|
||||
python tools/validate_v1_15.py
|
||||
python tools/validate_v1_14.py
|
||||
python tools/validate_concurrency.py
|
||||
python tools/validate_ide_metadata.py
|
||||
python tools/validate_lsp_helper.py
|
||||
python tools/validate_package_security.py
|
||||
```
|
||||
Run `./claro check lessons/01_hello.claro` for a focused syntax check. Still test
|
||||
interaction and recovery with learners using the [pilot checklist](CLASSROOM_READINESS.md).
|
||||
|
||||
The Forgejo/Gitea CI workflow is expected to run the current release gates above, not only the older smoke tests. This includes `tools/validate_compiler_warnings.py`, which rebuilds the C source as an object file and rejects the known path-truncation warning class. `tools/validate_ci_workflow.py` checks that coverage so CI does not drift away from the documented package validation path.
|
||||
## Target and archive acceptance
|
||||
|
||||
Older `validate_rc*.py` scripts are kept for historical release notes. They are not the recommended current package validation path.
|
||||
Use [distribution verification](PORTABILITY.md) for standard source/Linux x64/
|
||||
MinGW Windows x64 archives. It rebuilds or executes extracted payloads, checks
|
||||
manifests, and records exact artifact results. Source builds are not evidence
|
||||
that a shipped binary is correct.
|
||||
|
||||
On Windows, after `build.bat` or `build.ps1`, run the same Python commands from the project root.
|
||||
For [VC6 / Windows 98 SE](WINDOWS98.md), `build98.bat` builds the x86 executable;
|
||||
run `claro98.exe test` and `claro98.exe validate`. Modern Python developer tooling
|
||||
is not a Windows 98 prerequisite. The legacy build ZIP uses its own checksum
|
||||
manifest and report rather than the generic distribution validator.
|
||||
|
||||
Wine results must be labeled Wine results. Claim native Windows/Win98 support
|
||||
only after running the target tests there. [Release status](RELEASE_STATUS.md)
|
||||
records the dated scope; [the checklist](FINAL_RELEASE_CHECKLIST.md) governs publication.
|
||||
|
||||
## Historical tools
|
||||
|
||||
Older `validate_rc*.py` and versioned validators remain for history and focused
|
||||
compatibility checks. They are not alternative current release entry points.
|
||||
CI coverage is checked from executable `run` steps, not comments. Hosted CI
|
||||
success must be verified separately from these local commands.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Claro v2026.10.20.160950 - Packages and Projects
|
||||
# Claro v2026.10.20.223203 - Packages and Projects
|
||||
|
||||
Claro v2026.10.20.160950 makes projects and packages safer and more useful while keeping the beginner syntax simple.
|
||||
Claro v2026.10.20.223203 makes projects and packages safer and more useful while keeping the beginner syntax simple.
|
||||
|
||||
## Create a project
|
||||
|
||||
@@ -28,7 +28,7 @@ README.md
|
||||
manifest-version: 1
|
||||
name: MyProject
|
||||
main: main.claro
|
||||
version: v2026.10.20.160950
|
||||
version: v2026.10.20.223203
|
||||
packages:
|
||||
package: text
|
||||
package: net_tools
|
||||
@@ -77,10 +77,10 @@ If `package doctor` reports `BAD lock checksum: math-tools`, check the `math-too
|
||||
BAD lock version: expected exactly one lock-version: 1 line
|
||||
```
|
||||
|
||||
The lockfile must also contain exactly one current release line, `version: v2026.10.20.160950`. A missing, stale, blank, or duplicate release line fails with:
|
||||
The lockfile must also contain exactly one current release line, `version: v2026.10.20.223203`. A missing, stale, blank, or duplicate release line fails with:
|
||||
|
||||
```text
|
||||
BAD lock release version: expected exactly one version: v2026.10.20.160950 line
|
||||
BAD lock release version: expected exactly one version: v2026.10.20.223203 line
|
||||
```
|
||||
|
||||
The doctor leaves your files unchanged. After reviewing your project and package manifests, run `claro package lock` to regenerate the lockfile, then run `claro package doctor` again. Spaces around values and capitalization of the lock-format field name are accepted, as with other lockfile fields. Valid generated lockfiles continue to work unchanged; this check does not add registry downloads or verify package content hashes.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Claro Web Server Plan
|
||||
|
||||
Current release: **Claro v2026.10.20.160950**
|
||||
Current release: **Claro v2026.10.20.223203**
|
||||
|
||||
Claro currently has beginner HTTP client commands and offline `claro://` test URLs. A web server API is not complete yet.
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Windows 98 SE compilation (Visual C++ 6.0)
|
||||
|
||||
Claro has a dedicated **32-bit x86, MSVC6** build. It keeps the runtime in the
|
||||
single `src/claro.c` file. This is separate from the modern MinGW x64 download.
|
||||
|
||||
## Build on a configured VC6 system
|
||||
|
||||
1. Extract the project to a short path such as `C:\CLARO`.
|
||||
2. Open the compiler environment installed with Visual C++ 6.0, or run that
|
||||
installation's original `VCVARS32.BAT`. Check `cl` reports compiler 12.00.
|
||||
Compiler, include and library paths remain installation-specific; the project
|
||||
does not bundle Microsoft's toolchain.
|
||||
3. Change to the Claro folder and run:
|
||||
|
||||
```bat
|
||||
build98.bat
|
||||
```
|
||||
|
||||
The output is `claro98.exe`. Try:
|
||||
|
||||
```bat
|
||||
claro98.exe --version
|
||||
claro98.exe lessons\01_hello.claro
|
||||
claro98.exe test
|
||||
```
|
||||
|
||||
`build98.bat` and `build98.rsp` use 8.3-compatible names. The batch file uses no
|
||||
`SETLOCAL`, `%~dp0`, `EXIT /B`, `WHERE`, or command-chaining extensions. Compiler
|
||||
options live in a response file to avoid COMMAND.COM's short command-line limit.
|
||||
Both files use CRLF line endings. Modernized toolchain environment wrappers may
|
||||
use CMD-only syntax; use the VC6 environment appropriate to the actual host.
|
||||
|
||||
## Target settings
|
||||
|
||||
- `/TC /W3`: compile as C with normal warnings.
|
||||
- `/ML /O1`: VC6 static single-threaded CRT, optimized for size. No newer Visual
|
||||
C++ runtime DLL needs to be installed for this build.
|
||||
- `WINVER=0x0410`, `_WIN32_WINDOWS=0x0410`: Windows 98 header target selection.
|
||||
- `/MACHINE:I386 /SUBSYSTEM:CONSOLE,4.10`: 32-bit x86 console executable with a
|
||||
Windows 98-era subsystem version.
|
||||
- `/STACK:16777216`: 16 MiB reserve so nested calls reach Claro's existing
|
||||
recursion guard instead of overflowing the default Windows stack first.
|
||||
|
||||
The source selects `long` for `_findfirst` handles on pre-VC7 MSVC, matching the
|
||||
VC6 CRT declaration. Modern compilers retain their pointer-sized handle type.
|
||||
No `/Dintptr_t=long` workaround is used.
|
||||
|
||||
## Verification boundary
|
||||
|
||||
The build is exercised with the actual MSVC600 compiler and linker under Wine.
|
||||
The source/build-policy check is `python3 tools/validate_msvc6_source.py`, also
|
||||
included in full release validation and CI. It does not replace the actual VC6
|
||||
compiler test.
|
||||
|
||||
A PE header and a successful Wine test are not proof of execution on Windows
|
||||
98 SE. Native Win98 SE loader/CRT, filesystem, console, shell, encoding and device
|
||||
behavior still need testing on that OS or a faithful emulator. Some static VC6
|
||||
CRT imports include Wide-character API entry points and fallback paths; their
|
||||
presence must not be mistaken for a modern-NT-only requirement or for proof of
|
||||
Win98 correctness without a real target test.
|
||||
|
||||
Real HTTP requires a separately compatible curl executable. No modern TLS stack,
|
||||
GUI installer, Microsoft SDK or runtime DLL bundle is supplied. Run only trusted
|
||||
scripts. See [portability](PORTABILITY.md) and [runtime limits](RUNTIME_LIMITS.md).
|
||||
@@ -0,0 +1,353 @@
|
||||
# Historical record — not current status
|
||||
|
||||
Preserved before the documentation cleanup. Claims and pending-work notes below
|
||||
are dated history, not current acceptance results. Use [current status](../CURRENT_STATUS.md)
|
||||
and [release status](../RELEASE_STATUS.md) instead.
|
||||
|
||||
# Claro v2026.10.20.220528 Current Status
|
||||
|
||||
This file is the beginner-safe status map for the current package. It separates what is ready to use from what is historical, experimental, or still planned.
|
||||
|
||||
## How to read the status labels
|
||||
|
||||
- **Stable foundation**: good enough for normal examples and beginner learning.
|
||||
- **Foundation present**: usable, but still missing important polish or coverage.
|
||||
- **Experimental/planned**: do not rely on it in beginner lessons yet.
|
||||
- **Historical**: kept for release history, not current instructions.
|
||||
|
||||
## Security and correctness review progress
|
||||
|
||||
The v2026.10.20.220528 review identified missing-argument reads in standard-library built-ins. The current runtime now validates required argument counts centrally before any builtin indexes `args[]`. Covered calls include `math.abs`, `math.clamp`, `random.seed`, `random.int`, text helpers, CSV helpers, path helpers, and collection helpers. Missing arguments produce a beginner-facing `needs N arguments` runtime error.
|
||||
|
||||
Focused regression coverage: `tests/38_builtin_arity.claro`, `tests/39_random_inverted_range.claro`, and `tests/40_call_depth_guard.claro`.
|
||||
|
||||
The runtime now rejects inverted `random.int` ranges before modulo arithmetic with: `random.int needs the lower bound to be less than or equal to the upper bound.` This prevents invalid ranges from producing incorrect values or a divide-by-zero signal.
|
||||
|
||||
User-defined function and object-method calls now have a documented maximum active call depth of 256. Exceeding it produces: `Claro function call depth exceeded the safe limit of 256. Simplify the recursion or add a stopping condition.` Ordinary non-recursive beginner programs are unaffected.
|
||||
|
||||
Verified in this checkout on 2026-09-23:
|
||||
|
||||
- `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /tmp/claro-call-depth-asan -lm` plus `ASAN_OPTIONS=detect_leaks=0 /tmp/claro-call-depth-asan tests/40_call_depth_guard.claro`: expected diagnostic; no AddressSanitizer or UndefinedBehaviorSanitizer report.
|
||||
- `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /tmp/claro-arity-asan -lm` plus `ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 /tmp/claro-arity-asan tests/38_builtin_arity.claro`: all four missing-argument diagnostics; no sanitizer report.
|
||||
- `make -s all`: rebuilt both `claro` and `claro.exe` from current source.
|
||||
- `./claro test`: `PASS: 0 failure(s)`.
|
||||
- `./claro doctor`: all checks `OK`.
|
||||
- `./claro validate`: validation passed.
|
||||
|
||||
The memory cleanup slices release the previous deep value when a runtime variable or map entry is overwritten, release temporary split argument arrays and strings from `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM`, release loaded program paths, lines, and pointer arrays at the end of each script run, and now release the remaining runtime-owned variables, functions, modules, classes, import paths, captured output, return value, and error strings before the interpreter exits. The expression evaluator now releases discarded intermediate `Value` operands and command boundaries release evaluated `SET`/`SAY` values after copying or printing them. `FOR EACH` now releases its copied collection after iteration, preventing discarded list/map copies from accumulating in long scripts. `IF` and `DO ... TIMES` now release their temporary control-expression values after branch/loop selection. `ASK` now releases evaluated prompt values and temporary input values after converting/copying them into runtime storage. Focused coverage is `tools/validate_memory_cleanup.py`, `tools/validate_expression_cleanup.py`, `tools/validate_control_flow_cleanup.py`, `tools/validate_control_expression_cleanup.py`, and `tools/validate_ask_prompt_cleanup.py`; each focused cleanup validator runs an ASan/UBSan build with LeakSanitizer checking. Verified on 2026-09-24: `python3 tools/validate_ask_prompt_cleanup.py` passes (2,000 prompt/input operations under ASan/UBSan/LSan); `make -s all`, `./claro test`, `./claro doctor`, and `./claro validate` all pass. Caught `TRY` errors now release their previous message and source-path values when cleared; `python3 tools/validate_runtime_error_cleanup.py` confirms caught builtin-arity and inverted-range errors pass ASan/UBSan/LSan. Additional malformed-input leak sites may remain, so this does not claim all runtime allocations are leak-free. The `COUNT ... AS` command now releases its evaluated list/map value after extracting the item count. `python3 tools/validate_count_expression_cleanup.py` first reproduced a 28,000-... [truncated]
|
||||
|
||||
The `FIND ... IN ... AS ...` command now releases its evaluated search value and copied list/map after storing the result. Focused coverage is `tools/validate_find_expression_cleanup.py`; it runs 2,000 searches under ASan/UBSan with LeakSanitizer enabled. Verified on 2026-09-24: `python3 tools/validate_find_expression_cleanup.py` passes with no reported leaks; `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This slice is runtime-verified under sanitizers; broader malformed-input sanitizer coverage remains blocked by the previously documented `rt_error` message leak.
|
||||
|
||||
`REMOVE` now releases its evaluated needle and copied list/map value after updating runtime storage. `python3 tools/validate_remove_expression_cleanup.py` passed with 2,000 repeated string-needle operations under ASan/UBSan/LSan. Removed elements from populated copied lists remain a separate ownership case. This slice is runtime-verified under sanitizers; malformed-input diagnostics still have the previously recorded `rt_error` leak.
|
||||
|
||||
`ADD ... TO ...` now releases both the evaluated item and copied list after `list_add` and `rt_set` have made their owned copies. `python3 tools/validate_add_expression_cleanup.py` first reproduced leaks under ASan/UBSan/LSan (2,000 repeated string concatenations), then passed after the ownership cleanup. Full checks passed: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, all existing focused cleanup validators, and `git diff --check`. This slice is runtime-verified under sanitizers; malformed-input diagnostics still have the previously recorded `rt_error` leak.
|
||||
|
||||
`PUT ... KEY ... VALUE ...` now releases the evaluated map, key, and value copies after runtime storage has copied them. `python3 tools/validate_put_expression_cleanup.py` passes with 2,000 repeated string writes under ASan/UBSan/LSan. Verified 2026-09-26: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, the focused sanitizer validator, and `git diff --check` pass. Separate malformed-argument sanitizer smoke tests for the existing builtin-arity and inverted-range diagnostics do not report out-of-bounds access, but LeakSanitizer reports the already documented `rt_error` diagnostic-string leaks (552 bytes/14 allocations for arity cases; 79 bytes/2 allocations for the inverted-range case). This slice is runtime-verified; those error-path leaks remain a separate blocker to claiming sanitizer-clean diagnostics.
|
||||
|
||||
The `GET ARGUMENT ... AS ...` command now releases its evaluated index and copied temporary argument values after storage takes ownership. `tools/validate_get_argument_expression_cleanup.py` confirmed and fixed a 6,000-byte leak across 2,000 repetitions under ASan/UBSan/LSan; `GET ENV ... AS ...` now also releases its evaluated name value after conversion, with `tools/validate_get_env_expression_cleanup.py` detecting 1,688,000 leaked bytes across 2,000 lookups before the fix. Both focused checks are wired into the v1.17 validation script. This is runtime-verified narrow ownership work; other GET expression branches remain separate boundaries.
|
||||
|
||||
The `RUN COMMAND` path now decodes POSIX `pclose()` wait status before storing `LASTEXIT`, so a child that exits with code 3 exposes `3` rather than the encoded status 768. Focused coverage is `tests/42_last_exit_code.claro`. HTTP responses now have a 1,048,576-byte cap and marker-like response bodies are preserved while extracting the final HTTP status marker. Focused coverage is `tools/validate_http_hardening.py`. Remaining memory-growth areas include other expression temporaries. Claro remains a trusted-script interpreter, not a sandbox. `RUN COMMAND` now releases its evaluated command expression and temporary captured-output value after copying output into runtime storage. The new `tools/validate_run_command_expression_cleanup.py` first detected 12,000 bytes leaked across 2,000 command executions, then passed under ASan/UBSan/LSan after the ownership fix.
|
||||
|
||||
`RUN COMMAND` is documented and validated as a trusted-code capability: it executes shell commands with the user's permissions and is not a sandbox. Claro does not claim untrusted-script safety or use a fragile blacklist sanitizer. Focused documentation coverage is `tools/validate_trusted_command_docs.py`.
|
||||
|
||||
`REMOVE` also releases the removed element from its copied list before shifting later elements. Focused verification: `python3 tools/validate_remove_list_item_cleanup.py` passed with 2,000 add/remove pairs under ASan/UBSan/LSan and no reported leaks. `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` also passed. This is a narrow, runtime-verified ownership cleanup; it does not change `REMOVE` syntax or behavior.
|
||||
|
||||
`GET ... KEY ... AS ...` now releases its temporary map and key values after lookup. `python3 tools/validate_get_key_expression_cleanup.py` reproduced 1,004,000 leaked bytes before the change and passes after it with ASan/UBSan/LSan enabled. `EXISTS FILE ... AS ...` now releases its evaluated path value after checking it; `python3 tools/validate_exists_expression_cleanup.py` first reproduced 62,000 leaked bytes across 2,000 calls, then passed with ASan/UBSan/LSan enabled. These are narrow expression-temporary cleanup slices, not a claim that all runtime allocations are leak-free.
|
||||
|
||||
`GET ... AT ... AS ...` now releases its evaluated index expression after converting it to a list position. `python3 tools/validate_get_at_expression_cleanup.py` first reproduced 4,000 leaked bytes across 2,000 string-valued indices, then passed under ASan/UBSan/LSan with the selected value preserved. The validator is wired into `tools/validate_v1_17.py`; this is a narrow ownership fix, not a claim that all runtime allocations are leak-free.
|
||||
|
||||
`LIST FOLDER ... AS ...` now releases its evaluated path and the temporary folder-list value after runtime storage copies the list. `python3 tools/validate_list_folder_expression_cleanup.py` reproduced 220,000 leaked bytes across 2,000 operations under ASan/UBSan/LSan before the change and passes after it with no leak report. The working-tree checks `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This is a narrow ownership improvement, not a claim that all runtime allocations are leak-free.
|
||||
The `DELETE FILE ...` and `DELETE FOLDER ...` commands now release their evaluated path values after converting the paths to owned strings. `python3 tools/validate_delete_expression_cleanup.py` first reproduced 178,000 leaked bytes across 2,000 `DELETE FILE` expression evaluations under ASan/UBSan/LSan, then passed after cleanup. This check covers expression ownership; malformed-input diagnostic paths still have the previously documented `rt_error` message leak. The `COPY FILE` and `MOVE FILE` commands now also release their evaluated source/destination path values after conversion to strings; `python3 tools/validate_copy_move_expression_cleanup.py` reproduced 74,000 bytes leaked across 1,000 copy/move pairs before the fix and is the focused ASan/UBSan/LSan regression gate. `CREATE FOLDER ...` now releases its evaluated path value after converting it to an owned path string; `python3 tools/validate_create_folder_expression_cleanup.py` reproduced 200,000 leaked bytes across 2,000 calls before the fix and passes with ASan/UBSan/LSan enabled. This narrow cleanup slice does not establish that all runtime allocations are leak-free.
|
||||
|
||||
`typecheck` now frees its temporary type environment on return, including the owned variable names and type-string values; replacing an inferred type releases the previous value first. `python3 tools/validate_typecheck_token_cleanup.py` reproduced 479 bytes of leaked type-environment allocations before the change, then passed under ASan/UBSan/LSan with the fix. The validator is named for the original local work file; it specifically checks type-environment cleanup, not tokenizer allocations. This is a narrow cleanup slice and does not establish that all typecheck metadata allocations are leak-free.
|
||||
|
||||
`claro check` now releases expression-token arrays and strings after variable-name analysis, and frees its temporary variable environment at the end of each check. The untracked focused regression validator `tools/validate_check_token_cleanup.py` first reproduced 310,447 leaked bytes across 2,000 repeated-string expressions; after the cleanup, `python3 tools/validate_check_token_cleanup.py` passes under ASan/UBSan/LSan. Fresh verification: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This is a narrow, runtime-verified cleanup for check mode; it does not establish that all allocations are leak-free.
|
||||
|
||||
`READ FILE ... AS ...` now releases both its evaluated path and the temporary file-content value after copying contents into runtime storage. `python3 tools/validate_read_file_expression_cleanup.py` reproduced 204,000 leaked bytes across 2,000 reads before the fix, then passed under ASan/UBSan/LSan. The check is wired into `tools/validate_v1_17.py`; this is one verified cleanup boundary, not a claim that all file-command temporaries are leak-free.
|
||||
|
||||
`WRITE FILE` and `APPEND FILE` now release their evaluated path and content values after the operation. `python3 tools/validate_write_file_expression_cleanup.py` reproduced 206,000 leaked bytes across 2,000 writes before the fix; the focused ASan/UBSan/LSan validator is wired into `tools/validate_v1_17.py`. Other file-command temporary ownership remains separate.
|
||||
|
||||
## Introductory syntax consistency
|
||||
|
||||
The introductory learning path recommends short `ASK` and `SET`, `TEACH` with
|
||||
`DO`, and `END` for `IF`, counted/`FOR` loops, and functions. Quick Start,
|
||||
First Hour, all ten numbered lessons, ten selected introductory examples,
|
||||
generated starters, REPL help, and beginner editor snippets follow that policy.
|
||||
See [syntax rules](../SYNTAX.md); older spellings remain supported in the separate
|
||||
[compatibility reference](../COMPATIBILITY.md). `READ FILE ... AS` and
|
||||
`ADD ... TO` retain their command-specific words; `REPEAT` still ends in `UNTIL`.
|
||||
|
||||
The syntax checker now shares the interpreter's function-parameter parser, and
|
||||
modern `DO` type checking uses the same space-separated argument splitting as
|
||||
the runtime. Bare `SET items LIST` (and existing `MAP` construction) is treated
|
||||
as a container value, not an incomplete type annotation. This repairs checker
|
||||
rejections of valid teaching programs without removing compatibility syntax.
|
||||
Missing introductory block endings recommend `END`; `REPEAT` still recommends
|
||||
`UNTIL`.
|
||||
|
||||
`python3 tools/validate_learning_syntax.py` enforces the introductory spelling
|
||||
policy and executes the numbered lessons, Quick Start blocks, selected examples,
|
||||
starter, snippets, common-mistake hints, and compatibility program. The full
|
||||
release validator runs this checker alongside the executable First Hour checks.
|
||||
This coverage does not claim every optional advanced reference uses only the
|
||||
introductory syntax.
|
||||
|
||||
## Feature matrix
|
||||
|
||||
### Beginner scripting core
|
||||
|
||||
Status: **Stable foundation**
|
||||
|
||||
Ready now:
|
||||
- variables with `SET`
|
||||
- output with `SAY`
|
||||
- input with `ASK`
|
||||
- choices with `IF` / `ELSE` / `END`
|
||||
- loops
|
||||
- lists and maps
|
||||
- files, JSON, text helpers, and tested path/collection library helpers
|
||||
- friendly checking with `claro check`
|
||||
|
||||
Good starting docs:
|
||||
- `QUICK_START.md`
|
||||
- `FIRST_HOUR.md`
|
||||
- `CLI.md`
|
||||
- `SPEC.md`
|
||||
|
||||
### Functions
|
||||
|
||||
Status: **Stable foundation**
|
||||
|
||||
Ready now:
|
||||
- simple modern form: `TEACH name arg` ... `END`
|
||||
- compatibility form: `TEACH name TAKES arg` ... `LEARNED`
|
||||
- calls with `DO` or older `CALL ... WITH`
|
||||
|
||||
Good starting docs:
|
||||
- `SIMPLE_FUNCTIONS.md`
|
||||
- `README.md`
|
||||
|
||||
### Static type safety
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- typed variables such as `SET score NUMBER 10`
|
||||
- `TYPE OF` and `CHECK TYPE`
|
||||
- `CHECK TYPE` gives a direct repair hint when the learner forgets the `IS` keyword, for example `CHECK TYPE score` reports `CHECK TYPE needs IS. Try: CHECK TYPE score IS NUMBER.`
|
||||
- `CHECK TYPE` gives a direct repair hint when `IS` has no expected type, for example `CHECK TYPE score IS` reports `CHECK TYPE score needs a type after IS. Try: CHECK TYPE score IS NUMBER.`
|
||||
- `CHECK TYPE` explains when the learner leaves out the expression as well, for example bare `CHECK TYPE` reports `CHECK TYPE needs an expression, IS, and a type. Try: CHECK TYPE score IS NUMBER.`
|
||||
- typed function and method returns reject simple extra tokens after a return value with a direct repair hint, such as `RETURN amount extra` reporting `Keep only one expression after RETURN.` This is covered for modern functions and both modern and compatibility object methods.
|
||||
- `CHECK TYPE` explains when `IS` is present but the expression is missing, for example `CHECK TYPE IS NUMBER` reports `CHECK TYPE needs an expression before IS. Try: CHECK TYPE score IS NUMBER.`
|
||||
- `CHECK TYPE` rejects extra words after a valid expected type with a direct repair hint, so `CHECK TYPE score IS NUMBER TEXT` explains that only one type belongs in the check
|
||||
- `CHECK TYPE` rejects unknown expected type names with a beginner-facing list of supported types
|
||||
- typed function return diagnostics also identify an unknown return expression in compatibility `TAKES` / `LEARNED` functions, matching the modern function and object-method guidance
|
||||
- `TYPE OF` reports a direct repair hint when the learner forgets `AS`, for example `TYPE OF score` suggests `TYPE OF score AS kind`
|
||||
- bare `TYPE OF` reports that the expression, `AS`, and result name are all required, with a complete repair example
|
||||
- `TYPE OF` reports a direct repair hint when the learner forgets the expression before `AS`, for example `TYPE OF AS kind` reports `TYPE OF needs an expression before AS. Try: TYPE OF score AS kind.`
|
||||
- `TYPE OF` prioritizes the missing expression diagnostic even when extra words follow the incomplete form, so `TYPE OF AS kind extra` still points the learner to the missing expression first.
|
||||
- `TYPE OF` reports a direct repair hint when the learner forgets the result name after `AS`, for example `TYPE OF score AS` reports `TYPE OF needs a result name after AS. Try: TYPE OF score AS kind.`
|
||||
- A valid `TYPE OF score AS kind` statement has a focused positive typecheck fixture, so the complete form is protected from diagnostic-only regression coverage.
|
||||
- `TYPE OF` remains case-insensitive like other Claro keywords, with focused positive coverage for lowercase `type of score as kind` syntax.
|
||||
- `CHECK TYPE` remains case-insensitive like other Claro keywords, with focused positive coverage for lowercase `check type score is number` syntax.
|
||||
- Object declarations and field checks remain case-insensitive like other Claro keywords, with focused positive coverage for lowercase `class`, `has`, `end`, `new`, `set`, and `check type` in one typed object-field example.
|
||||
- `TYPE OF` rejects extra words after its result name with a direct repair hint, so `TYPE OF score AS kind extra` explains that only one result name belongs there; typed `TEACH ... RETURNS TYPE` declarations likewise reject extra words after the return type with a direct repair hint
|
||||
- typed `TEACH ... RETURNS` declarations reject a missing return type with a direct repair hint, such as `Function greet needs a return type after RETURNS. Add a type such as NUMBER.` The same diagnostic names the complete class and method, including compatibility `TAKES` / `LEARNED` methods: `Method Player.score needs a return type after RETURNS. Add a type such as NUMBER.`
|
||||
- typed list/map checks through `claro typecheck`, including a nested `LIST OF MAP` insertion example
|
||||
- compatibility `TAKES` / `LEARNED` functions also have focused positive numeric-addition, subtraction, division, and multiplication return fixtures, keeping arithmetic return coverage aligned with modern functions and compatibility methods.
|
||||
- compatibility `TAKES` / `LEARNED` functions have positive complete-branch return coverage beside the incomplete-branch diagnostic, so older conditional examples are checked for both accepted and rejected paths.
|
||||
- a narrow function/method argument check: `CHECK TYPE parameter IS TYPE` inside a function or simple object method lets `claro typecheck` accept correct checked calls, catch mismatched `DO`, `CALL ... WITH`, `DO object.method ...`, and compatibility `CALL ... WITH` arguments, including checked methods that appear after another method in the same class, report missing checked function and method arguments, report missing unchecked arguments for simple functions and simple object methods in both modern `DO` and compatibility `CALL ... WITH` forms, treat empty compatibility calls such as `CALL greet WITH` and `CALL player.rename WITH` as missing-argument mistakes, catch extra arguments to simple functions and checked methods even when the checked method appears after another method in the same class, catch modern `DO` and compatibility `CALL ... WITH` calls to undeclared simple functions, explain when `DO object.method ...` or compatibility `CALL ...` happens before the object is created with `NEW` even if the method name is also wrong, and catch modern plus compatibility calls to undeclared object methods with a class-specific `TEACH` hint
|
||||
- simple function and object-method return checks with `TEACH name ... RETURNS TYPE` now include a compatibility `TAKES` / `LEARNED` function whose conditional returns are incomplete, preserving the same every-path diagnostic as modern functions.
|
||||
- function and method declarations now reject repeated parameter names with a direct repair hint before calls are checked, including modern and compatibility `TAKES` / `LEARNED` functions and methods; object methods with the same name are also rejected within a class with a rename hint in both modern and compatibility method syntax
|
||||
- top-level function declarations now reject repeated names with a direct repair hint, so two `TEACH greet` blocks cannot silently compete for one callable function; a missing function name after `TEACH` gets a direct beginner-facing repair hint, and a missing method name inside `CLASS` names the class and gives a method example
|
||||
- class declarations now reject a missing class name after `CLASS` with a direct beginner-facing repair hint
|
||||
- class declarations now reject repeated field names within the same class with a direct repair hint before field types are used, so two `HAS score ...` lines cannot silently choose conflicting metadata; different classes may independently use the same field name
|
||||
- class field declarations now reject missing names such as bare `HAS`, missing types such as `HAS score`, unknown types such as `HAS score BANANA`, and extra tokens such as `HAS score NUMBER TEXT` with the field name, class name, supported type examples, and a repair hint before object-field checks use that metadata
|
||||
- class declarations now reject repeated class names with a direct repair hint, so two `CLASS Player` blocks cannot silently compete for the same name; extra words after a class name also get a direct repair hint instead of becoming part of the class identity
|
||||
- object creation now rejects repeated object names with a direct repair hint, so two `NEW Player player` statements cannot silently replace one another during type checking; when a script declares at least one class, a misspelled `NEW` class name gets a direct declaration hint without changing the older permissive no-class form; a missing class name or object name gets a direct example repair; extra words after the object name get a direct repair hint instead of being silently ignored
|
||||
- a narrow object-field assignment/check-type check for simple `NEW Class object` plus direct `SET object.field value` and `CHECK TYPE object.field IS TYPE` cases when the class declares `HAS field TYPE`; validation now covers correct NUMBER, TEXT, and YESNO direct assignments, dedicated positive and explicitly typed NUMBER/TEXT field assignments, missing values in untyped direct field assignments with a repair hint, explicitly typed method-body assignments to undeclared fields with a `HAS field TYPE` repair hint in modern and compatibility method syntax, dedicated positive NUMBER/TEXT/YESNO `CHECK TYPE` metadata fixtures, simple and chained object aliases for both field assignments and `CHECK TYPE` metadata (including a chained TEXT-field check), aliased object-method calls including chained aliases in modern `DO` and compatibility `CALL ... WITH` forms (with a dedicated positive modern `DO` chained-alias fixture), field-to-field, compound arithmetic field expressions, and arithmetic/text expression result types, negative NUMBER/TEXT/YESNO field `CHECK TYPE` metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type diagnostics, field collection when a `HAS` field appears after a simple method, NUMBER/TEXT/YESNO-valued unknown-field diagnostics for direct assignments to undeclared fields including explicitly typed assignments, numeric and text results from simple arithmetic/text expressions in object-field assignments, plus method-body field assignment and `CHECK TYPE` diagnostics for NUMBER, TEXT, and YESNO fields in modern and compatibility syntax, including compatibility YESNO `CHECK TYPE` metadata coverage, direct method assignments to undeclared fields now produce a beginner-facing missing-field diagnostic when the assigned expression has a known type, and a plain beginner-facing unknown-field diagnostic when the assigned expression is not inferable yet in both modern and compatibility method syntax; method-body `CHECK TYPE` now reports an unknown field with a repair hint in both method syntaxes, including compatibility `TAKES` / `LEARNED` methods; explicitly typed assignments to declared method fields now validate the class-declared field type instead of trusting only the inline type annotation, so a wrong value such as `SET score TEXT \"oops\"` reports the method and field in the diagnostic.
|
||||
- inline method-field annotations are checked against the class `HAS` declaration in modern and compatibility methods; conflicting known types and unknown annotation names produce field-specific repair guidance, with positive and negative focused fixtures for both syntaxes.
|
||||
- direct object-field `AS ... TO` assignments reject extra words after the value with a repair hint, so `SET player.score AS NUMBER TO 10 extra` cannot silently ignore the trailing text; they explain when `TO` is missing after the type and when the value is missing after `TO`; short explicitly typed assignments such as `SET player.score NUMBER 10 extra` receive the same repair-oriented check and explain when the value is missing after the type
|
||||
- Direct object-field validation also has positive coverage for explicit `NUMBER`, `TEXT`, and `YESNO` annotations, such as `SET player.ready YESNO YES`, plus the compatibility-shaped `AS ... TO` form such as `SET player.score AS NUMBER TO 10`, while the shorter unannotated form remains supported; method-body field assignments have matching positive coverage for both modern and compatibility `TEACH` forms using `SET score AS NUMBER TO 10`, and those two `AS ... TO` method fixtures are now included in `claro validate`; conflicting annotations such as `SET player.score TEXT "oops"` explain the class-declared type and how to repair it, and unknown annotations such as `SET player.score AS BANANA TO 10` or `SET player.score BANANA 10` identify the invalid type and suggest the declared field type.
|
||||
|
||||
Still needed:
|
||||
- Keep the focused typecheck validator's fixture list complete as new positive and negative examples are added. `claro validate` now executes the complete focused fixture matrix as well, so release validation cannot silently omit a listed typecheck example. Short explicitly typed field assignments inside modern and compatibility methods also have focused coverage: `SET score NUMBER` explains that one value expression is required after the type, matching direct object-field assignment guidance.
|
||||
- The expression checker now carries a known TEXT operand through all arithmetic operators so object-field and typed-function-return diagnostics do not hide addition, subtraction, multiplication, or division mistakes; focused numeric-subtraction, numeric-division, and numeric-multiplication positives plus text-operand addition, subtraction, multiplication, and division negatives protect this behavior. Typed-function return diagnostics now have focused subtraction and division positives alongside multiplication coverage. Text concatenation into a TEXT field has focused positive fixtures for both operand orders, including concatenation of two TEXT object fields. Each numeric operator names the text operand and explains that it needs NUMBER values.
|
||||
- Method return validation now has focused positive subtraction and division fixtures alongside the existing arithmetic return coverage, including compatibility `TAKES` / `LEARNED` subtraction and division cases, so checked NUMBER method parameters and numeric subtraction or division remain accepted by the release validator in both method spellings.
|
||||
- Modern and compatibility `TAKES` / `LEARNED` methods have positive NUMBER, TEXT, and YESNO field-assignment coverage, and modern plus compatibility method-body `CHECK TYPE` metadata now have positive YESNO coverage beside the wrong-value diagnostics.
|
||||
- Modern method-body TEXT concatenation has positive coverage in both operand orders, such as `SET first first + "!"` and `SET last "Dr. " + last`.
|
||||
- Compatibility method-body TEXT concatenation has matching positive coverage in both operand orders, so `TAKES` / `LEARNED` methods keep the same beginner-safe field update behavior.
|
||||
- richer typed function signatures and return values beyond the focused simple function/method `RETURNS TYPE` check
|
||||
- type checking through branches and loops
|
||||
- richer object field type checking beyond simple direct assignments
|
||||
- typed imports/modules
|
||||
|
||||
|
||||
Good starting docs:
|
||||
- `ADVANCED_STATIC_TYPING.md`
|
||||
- `V1_14_STATIC_TYPES.md` (feature history plus examples)
|
||||
|
||||
### Objects and classes
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- `CLASS`
|
||||
- typed `HAS` fields
|
||||
- `NEW`
|
||||
- field access such as `player.score`
|
||||
- simple methods, including static diagnostics for checked method parameters
|
||||
- direct object-field assignments with a narrow static diagnostic for wrong value types
|
||||
- object helper commands
|
||||
|
||||
Still needed:
|
||||
- broader object field type checking across aliases, methods, and more complex object flows
|
||||
- constructor/default-value polish
|
||||
- clearer object debugging and teaching examples
|
||||
|
||||
Good starting docs:
|
||||
- `V1_15_OBJECTS_CLASSES.md`
|
||||
- `README.md`
|
||||
|
||||
### Package and project workflow
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- `claro new`
|
||||
- `claro package init`
|
||||
- `claro package add/list/remove/doctor/lock`
|
||||
- local project files such as `claro.project`, `claro.lock`, and `packages/`
|
||||
- starter projects from `claro new` now use the same manifest-version and lock-version headers as `claro package init`
|
||||
- the default `make` target builds both `claro` and `claro.exe`, so cross-platform release validators exercise freshly built executables
|
||||
- project-name safety checks for `claro new`, so unsafe names such as `../bad` are rejected before Claro creates folders
|
||||
- package-name safety checks when adding packages, including the 64-character package-name limit, when `claro package doctor` audits an existing `claro.project`, when `claro package lock` writes lockfile data, when `claro package list` shows existing packages, when `claro package init` sees unsafe names already present in `claro.project`, when `claro package add` sees unsafe names already present in `claro.project`, when `claro package remove` refreshes the lockfile after an edit, when learners need to remove an unsafe package entry that is already present, when `claro package doctor` verifies lockfile checksums for listed packages, and when `claro package doctor` rejects lockfile package entries that are not listed in `claro.project`
|
||||
- `claro package doctor` rejects a listed package manifest whose `name:` does not match the package name in `claro.project`
|
||||
- `claro package doctor` compares the complete manifest `name:` value, so a prefix such as `math-tools-extra` cannot be accepted for package `math-tools`
|
||||
- `claro package doctor` requires exactly one complete supported package `manifest-version: 1` field; missing, blank, unsupported, or duplicate format fields get a `BAD package manifest version` diagnostic with the file path and a repair hint, rather than calling an existing file missing
|
||||
- `claro package doctor` compares the complete manifest `checksum:` value, so a valid checksum followed by extra text is rejected
|
||||
- `claro package doctor` requires the complete supported `manifest-version: 1` value in `claro.project`, so a prefix such as `manifest-version: 10` is rejected
|
||||
- `claro package doctor` requires the complete supported `version: 1` value in each local package manifest, so a prefix such as `version: 10` is rejected
|
||||
- `claro package doctor` requires exactly one current `version: v2026.10.20.220528` line in `claro.lock`, so stale, missing, blank, or duplicate lockfile release versions are rejected
|
||||
- `claro package doctor` requires the complete supported `source: local` value in each local package manifest, so a prefix such as `source: local-extra` is rejected
|
||||
- package project entries must be unique; `claro package doctor`, `list`, `add`, `init`, and `lock` reject duplicate names instead of generating ambiguous package or lockfile data
|
||||
- lockfile package entries must also be unique; `claro package doctor` reports duplicate lock entries instead of accepting ambiguous package/checksum data
|
||||
- package manifest `name:` entries must also be unique; `claro package doctor` reports `BAD package manifest name` instead of accepting ambiguous package identity data
|
||||
- package manifest `checksum:` entries must also be unique; `claro package doctor` reports `BAD package checksum` instead of accepting ambiguous integrity data
|
||||
- package manifest `version:` entries must also be unique and must contain exactly `1`; `claro package doctor` reports `BAD package version` instead of accepting ambiguous package metadata
|
||||
- project manifests must contain exactly one `manifest-version: 1` field; duplicate or prefix-matching values are rejected with `BAD project manifest version: expected 1`
|
||||
- project manifests must contain exactly one non-empty `name:` field; `claro package doctor` reports `BAD project manifest name: expected a non-empty name` when the project identity is missing or blank
|
||||
|
||||
Still needed:
|
||||
- install from local path
|
||||
- package version constraints
|
||||
- publish/export format
|
||||
- remote registry protocol
|
||||
- checksum/signature verification before downloads
|
||||
|
||||
Good starting docs:
|
||||
- `V1_16_PACKAGES_PROJECTS.md`
|
||||
- `PACKAGE_REGISTRY.md`
|
||||
|
||||
### Networking
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- beginner HTTP commands
|
||||
- offline `claro://` URLs for tests and lessons
|
||||
- `LASTHTTP`
|
||||
- real `http://` and `https://` through `curl` when available
|
||||
|
||||
Still needed:
|
||||
- web server syntax
|
||||
- routes
|
||||
- request/response helpers
|
||||
- localhost-safe server mode
|
||||
|
||||
Good starting docs:
|
||||
- `V1_17_NETWORKING.md`
|
||||
- `WEB_SERVER_PLAN.md`
|
||||
|
||||
### Concurrency and tasks
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- deterministic task/concurrency helpers where documented
|
||||
- beginner-safe direction toward cooperative tasks
|
||||
|
||||
Still needed:
|
||||
- full cooperative scheduler semantics
|
||||
- cancellation
|
||||
- timeouts
|
||||
- message passing
|
||||
- decision on whether native threads should ever be exposed
|
||||
|
||||
Good starting docs:
|
||||
- `CONCURRENCY.md`
|
||||
|
||||
### IDE/editor support
|
||||
|
||||
Status: **Foundation present**
|
||||
|
||||
Ready now:
|
||||
- `claro ide`
|
||||
- metadata JSON
|
||||
- completion list, including the current typed-language keywords `RETURNS`, `CHECK`, and `TYPE`
|
||||
- diagnostics helper
|
||||
- Forgejo/Gitea CI coverage for the current release validation gates (`claro validate`, typecheck diagnostics validation, version convention validation, package security validation, compiler warning validation, and the CI workflow coverage check); the compiler warning validator rebuilds `src/claro.c` and rejects path-truncation warnings, while the workflow validator verifies these commands, including its own command, occur in executable `run` steps rather than comments
|
||||
|
||||
Still needed:
|
||||
- syntax highlighting package
|
||||
- editor extension
|
||||
- hover help
|
||||
- go-to-definition
|
||||
- code actions/fixes
|
||||
- full LSP server if/when useful
|
||||
|
||||
Good starting docs:
|
||||
- `IDE.md`
|
||||
- `EDITOR_EXTENSION_PLAN.md`
|
||||
|
||||
### Graphics and SDL
|
||||
|
||||
Status: **Experimental/planned**
|
||||
|
||||
Ready now:
|
||||
- placeholder/planned graphics documentation
|
||||
- experimental examples may exist for future SDL work
|
||||
|
||||
Not ready in the stable executable:
|
||||
- real SDL window/drawing support
|
||||
- beginner game lessons that run everywhere
|
||||
|
||||
Good starting docs:
|
||||
- `GRAPHICS.md`
|
||||
- `SDL12.md`
|
||||
|
||||
## Historical docs
|
||||
|
||||
Files named `RC*_NOTES.md`, `RC*_VALIDATION.md`, older `V1_*_VALIDATION.md`, and older release notes are kept for project history. They may mention old version numbers, old planned milestones, or old validation scripts. Use them to understand how Claro evolved; do not treat them as the current beginner path.
|
||||
|
||||
## Which docs should a new learner trust first?
|
||||
|
||||
For the current v2026.10.20.220528 package, read docs in this order:
|
||||
|
||||
1. `QUICK_START.md`, `FIRST_HOUR.md`, and `lessons/README.md` for first programs.
|
||||
2. `README.md` and this file for the current feature map.
|
||||
3. `ROADMAP.md` for the next v1 work.
|
||||
|
||||
Use feature docs with these expectations:
|
||||
|
||||
- **Ready for beginner lessons:** `SPEC.md`, `SIMPLE_FUNCTIONS.md`, `ERRORS.md`, `LINTER.md`, `TESTING.md`, `FORMATTER.md`.
|
||||
- **Foundation present:** `ADVANCED_STATIC_TYPING.md`, `V1_14_STATIC_TYPES.md`, `V1_15_OBJECTS_CLASSES.md`, `V1_16_PACKAGES_PROJECTS.md`, `V1_17_NETWORKING.md`, `CONCURRENCY.md`, `IDE.md`.
|
||||
- **Plans or experiments:** `PACKAGE_REGISTRY.md`, `WEB_SERVER_PLAN.md`, `EDITOR_EXTENSION_PLAN.md`, `GRAPHICS.md`, `SDL12.md`, `COMPLETE_PLATFORM_ROADMAP.md`, `FUTURE_FEATURES_ROADMAP.md`.
|
||||
|
||||
If a doc sounds more ambitious than this status map, treat this file as the current source of truth and update the older doc before teaching from it.
|
||||
|
||||
## 2026-09-28 random.int full-range arithmetic follow-up
|
||||
|
||||
`random.int` now calculates its inclusive range width and result in `long long`, avoiding signed-int overflow for the full accepted C `int` range. The regression fixture is `tests/43_random_int_extreme_bounds.claro`.
|
||||
|
||||
Verified: built with `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /home/ubuntu/.hermes/profiles/maxwell/cache/scratch/claro-rng-probe -lm`; ran `ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 /home/ubuntu/.hermes/profiles/maxwell/cache/scratch/claro-rng-probe tests/43_random_int_extreme_bounds.claro` (exit 0, no sanitizer report). Before the change, the same full-range call failed with UBSan signed integer overflow at `src/claro.c:347`. This validates the extreme-range runtime behavior under ASan/UBSan; `make -s all`, `./claro test` (0 failures, including this fixture), `./claro doctor`, `./claro validate`, and `git diff --check` pass.
|
||||
@@ -0,0 +1,142 @@
|
||||
# Historical record — not current status
|
||||
|
||||
Preserved before the documentation cleanup. Claims and pending-work notes below
|
||||
are dated history, not current acceptance results. Use [current status](../CURRENT_STATUS.md)
|
||||
and [release status](../RELEASE_STATUS.md) instead.
|
||||
|
||||
# Hardening Notes (Beta23)
|
||||
|
||||
## File loading safety
|
||||
Claro now reads source lines dynamically (no 4KB truncation).
|
||||
|
||||
Safety caps (to prevent memory abuse):
|
||||
- Maximum single line length: 65,535 characters
|
||||
- Maximum program lines: 500,000
|
||||
|
||||
If a file exceeds these limits, the loader fails cleanly.
|
||||
|
||||
## Expression-token cleanup
|
||||
|
||||
Each expression now releases its token strings and token-array storage before returning. This is a narrow cleanup boundary; runtime-owned variables, loaded programs, and other allocations remain separate follow-up work.
|
||||
|
||||
Focused verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_memory_cleanup.py
|
||||
```
|
||||
|
||||
The validator builds an AddressSanitizer/UndefinedBehaviorSanitizer binary, runs a repeated variable-overwrite probe, and confirms LeakSanitizer no longer reports allocations from `tokenize`/`toks_add`. The interpreter remains a trusted-script runtime, not a sandbox.
|
||||
|
||||
## Overwritten-value cleanup
|
||||
|
||||
Runtime variables and map entries own deep copies of their values. Replacing an existing variable or map entry now releases the previous string, list, or map value before storing its replacement. This is intentionally limited to overwrite boundaries; final runtime teardown remains a follow-up cleanup slice.
|
||||
|
||||
## Split-argument cleanup
|
||||
|
||||
Command argument lists created by `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM` are temporary parser storage. They now share one cleanup helper, so repeated calls do not retain the duplicated argument strings or pointer array. The helper does not change argument evaluation or syntax compatibility.
|
||||
|
||||
Focused verification builds with AddressSanitizer/UndefinedBehaviorSanitizer, repeatedly exercises a four-argument `DO`, and checks the cleanup helper before confirming the existing string, list, and map overwrite behavior.
|
||||
|
||||
The `REMOVE` command now releases its evaluated needle and copied list/map value after updating runtime storage. Focused verification: `python3 tools/validate_remove_expression_cleanup.py` runs 2,000 discarded string needles under ASan/UBSan/LSan; it passed with no reported leaks. This slice does not yet address the separate ownership of an item removed from a populated copied list.
|
||||
|
||||
When `REMOVE` finds an item in its copied list, it now frees that removed item before shifting the remaining owned values left. `python3 tools/validate_remove_list_item_cleanup.py` repeatedly adds and removes strings under ASan/UBSan/LSan to check that the item ownership is released without changing the list update behavior.
|
||||
|
||||
The `PUT ... KEY ... VALUE ...` command now releases its evaluated map, key, and value copies after `map_put`/`rt_set` have copied the data they own. Focused verification: `python3 tools/validate_put_expression_cleanup.py` exercises 2,000 string-valued writes under ASan/UBSan/LSan and passes with no reported leaks. This covers expression-temporary ownership only; caught runtime diagnostic-value leaks are covered by the separate cleanup slice below.
|
||||
|
||||
Caught runtime errors release the previous owned error-message and source-path strings when `TRY` clears the error state. `python3 tools/validate_runtime_error_cleanup.py` covers repeated caught builtin-arity errors and an inverted `random.int` range under ASan/UBSan/LSan.
|
||||
|
||||
Verified 2026-10-06: `python3 tools/validate_runtime_error_cleanup.py` passes with LeakSanitizer enabled; `make -s all`, `./claro test` (0 failures), `./claro doctor`, and `./claro validate` pass. The focused validator is also part of `tools/validate_v1_17.py`. This addresses caught diagnostic-value ownership only; it does not claim every malformed-input path is leak-free.
|
||||
|
||||
The `GET ARGUMENT ... AS ...` command now releases both its evaluated index expression and temporary argument value after runtime storage has copied the result. `python3 tools/validate_get_argument_expression_cleanup.py` first reproduced 6,000 leaked bytes over 2,000 accesses, then passes with AddressSanitizer, UndefinedBehaviorSanitizer, and LeakSanitizer enabled. This is a focused ownership cleanup; other `GET` forms have separate temporary-value ownership boundaries.
|
||||
|
||||
`GET ENV ... AS ...` now releases its evaluated environment-name value after converting it to the lookup string. `python3 tools/validate_get_env_expression_cleanup.py` reproduced 1,688,000 leaked bytes across 2,000 lookups before the fix and checks the same repeated lookup under ASan/UBSan/LSan. This is a narrow ownership improvement; other `GET` expression branches remain separate boundaries.
|
||||
|
||||
|
||||
HTTP responses are capped at 1,048,576 bytes. Exceeding the cap produces a beginner-facing runtime error instead of retaining an unbounded response. The curl status suffix is taken from the final status marker, so a response body containing marker-like text is preserved. Existing `HTTP CHECK` URL safety rules remain unchanged.
|
||||
|
||||
Focused verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_http_hardening.py
|
||||
```
|
||||
|
||||
This validator uses a local HTTP server to check marker-like response text, status `200`, and the oversized-response diagnostic.
|
||||
|
||||
## READ FILE expression cleanup
|
||||
|
||||
`READ FILE ... AS ...` now releases its evaluated path value and the temporary file-content value after runtime storage copies the contents. `python3 tools/validate_read_file_expression_cleanup.py` first reproduced 204,000 leaked bytes across 2,000 reads under ASan/UBSan/LSan; after the cleanup it passes with the expected output and no reported leak. This is a narrow ownership fix; other file-command temporaries remain separate.
|
||||
|
||||
`WRITE FILE ... WITH ...` and `APPEND FILE ... WITH ...` now release their evaluated path and content values after writing. `python3 tools/validate_write_file_expression_cleanup.py` reproduced 206,000 leaked bytes across 2,000 writes before the cleanup; it verifies repeated writes under ASan/UBSan/LSan and checks the resulting file content.
|
||||
|
||||
## Verification
|
||||
|
||||
2026-10-07: `python3 tools/validate_read_file_expression_cleanup.py` passes under ASan/UBSan/LSan; `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `python3 tools/validate_v1_17.py` all pass. Runtime-verified narrow cleanup; full validation does not establish that every allocation path is leak-free.
|
||||
|
||||
## External command trust boundary
|
||||
|
||||
`RUN COMMAND` intentionally executes a shell command with the user's permissions. It is a trusted-code capability, not a sandbox or an untrusted-script safety feature. Claro does not attempt a fragile blacklist sanitizer; users must review scripts before running them.
|
||||
|
||||
Focused documentation verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_trusted_command_docs.py
|
||||
```
|
||||
|
||||
## Control-flow expression cleanup
|
||||
|
||||
Conditions evaluated by `IF` are temporary runtime values. The `IF` command now
|
||||
releases its condition after choosing a branch, including when the condition is
|
||||
a text expression. This is a narrow cleanup boundary; other expression
|
||||
temporaries remain separate follow-up work.
|
||||
|
||||
Focused verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_control_expression_cleanup.py
|
||||
```
|
||||
|
||||
The validator builds with AddressSanitizer/UndefinedBehaviorSanitizer,
|
||||
executes 2,000 temporary `IF` conditions under LeakSanitizer, and checks the
|
||||
expected output.
|
||||
|
||||
## ASK temporary-value cleanup
|
||||
|
||||
`ASK` releases the evaluated prompt value after converting it to display text,
|
||||
and releases the temporary input value after `rt_set_checked` copies it into
|
||||
runtime storage. This keeps prompt and input strings from accumulating during
|
||||
repeated input loops without changing prompt or input behavior.
|
||||
|
||||
Focused verification:
|
||||
|
||||
```text
|
||||
python3 tools/validate_ask_prompt_cleanup.py
|
||||
```
|
||||
|
||||
The validator runs 2,000 prompt/input operations with AddressSanitizer, UndefinedBehaviorSanitizer, and LeakSanitizer enabled. The dedicated validator passes. A separate malformed-input sanitizer smoke run (`gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o ... -lm` followed by the generated malformed script) exits 1 due to two existing leaked error-message strings (140 bytes total) allocated in `rt_error` at `src/claro.c:142`; this is outside the ASK cleanup slice and remains a blocker to sanitizer-clean malformed-input validation.
|
||||
|
||||
## Recursive call-depth guard
|
||||
|
||||
User-defined function and object-method calls stop at 256 active calls with a clear runtime diagnostic instead of continuing into an uncontrolled stack overflow. `python3 tools/validate_call_depth_guard.py` runs an intentionally unbounded recursive function and checks the diagnostic. This limit does not affect ordinary non-recursive programs; it is a documented safety bound, not a sandbox guarantee.
|
||||
|
||||
## OBJECT expression cleanup investigation (blocked)
|
||||
|
||||
The current uncommitted `OBJECT CLASS` / `OBJECT FIELDS` cleanup probe is not
|
||||
verified. On 2026-10-12, `python3 tools/validate_object_expression_cleanup.py`
|
||||
compiled and ran under ASan/UBSan/LSan but failed: LeakSanitizer reported
|
||||
58,996 bytes in 10,016 allocations over the 2,000-iteration object probe.
|
||||
Stacks include evaluated `OBJECT` values and `object_class_name`'s copied
|
||||
`map_get` result, but also allocations from object creation and checked runtime
|
||||
storage. This shows the probe crosses multiple ownership boundaries; no claim
|
||||
of leak-free OBJECT expressions is made. Preserve the working tree and isolate
|
||||
one ownership boundary with a narrower reproduction before changing more code.
|
||||
The normal golden suite passed (`python3 tools/run_tests.py`, 0 failures). A narrower class-only probe (`CLASS Person`; 2,000 repetitions of `OBJECT CLASS person AS kind`) also fails LeakSanitizer: 58,024 bytes in 8,002 allocations. The trace isolates 14,000 bytes from the copied `class` value returned by `map_get` in `object_class_name` (2,000 allocations), 14,000 bytes from each result's `v_str`, and 10,000 bytes in 2,000 `rt_set_checked` result temporaries; baseline object creation additionally leaks. The existing combined validator therefore cannot prove this expression boundary independently. No further ownership change is made until the `rt_set_checked`/runtime-storage ownership and baseline object lifetime are isolated. The attempted OBJECT cleanup remains uncommitted and must not be described as verified.
|
||||
|
||||
## GET KEY temporary-value cleanup
|
||||
|
||||
`GET ... KEY ... AS ...` releases its evaluated map copy and key value after storing the selected value. `python3 tools/validate_get_key_expression_cleanup.py` runs 2,000 lookups under ASan/UBSan/LSan; before the cleanup it reproduced 1,004,000 bytes leaked, and after it passes with the expected output and no reported leaks.
|
||||
|
||||
`GET ... AT ... AS ...` now releases its evaluated index expression after converting it to a list position. The regression validator `python3 tools/validate_get_at_expression_cleanup.py` repeats a string-valued index 2,000 times under ASan/UBSan/LSan; before the fix, LeakSanitizer reported 4,000 leaked bytes, and after it passes with the expected selected value.
|
||||
|
||||
## COPY/MOVE expression temporary cleanup
|
||||
|
||||
`COPY FILE ... TO ...` and `MOVE FILE ... TO ...` release both evaluated path values after converting them to owned path strings. `python3 tools/validate_copy_move_expression_cleanup.py` runs 1,000 copy/move pairs under ASan/UBSan/LSan; before the cleanup it reproduced 74,000 bytes leaked across 4,000 path-expression values, and after it passes with no reported leaks.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Runtime Guards & Limits (Beta25)
|
||||
|
||||
Claro includes optional safety limits to prevent hangs and memory abuse.
|
||||
|
||||
## 1) Step limit (runaway protection)
|
||||
Environment variable:
|
||||
- `CLARO_STEP_LIMIT=200000`
|
||||
|
||||
Stops a program if it executes too many steps/lines.
|
||||
|
||||
## 2) File size limit
|
||||
Environment variable:
|
||||
- `CLARO_MAX_FILE_BYTES=16777216` (default 16 MiB)
|
||||
|
||||
Applies to:
|
||||
- `READ FILE ...`
|
||||
- `WRITE FILE ... WITH ...`
|
||||
- `APPEND FILE ... WITH ...`
|
||||
|
||||
## 3) JSON size limit
|
||||
Environment variable:
|
||||
- `CLARO_MAX_JSON_BYTES=8388608` (default 8 MiB)
|
||||
|
||||
Applies to:
|
||||
- `PARSE JSON ...`
|
||||
- `MAKE JSON ...`
|
||||
- `MAKE JSON PRETTY ...`
|
||||
|
||||
## 4) Error output
|
||||
Runtime errors include:
|
||||
- `file:line: message`
|
||||
- stack trace (CALL chain)
|
||||
- and JSON parse errors include `Near: ...` snippet
|
||||
@@ -1,6 +1,6 @@
|
||||
SAY "Claro Calculator"
|
||||
ASK "First number?" AS a
|
||||
ASK "Second number?" AS b
|
||||
ASK "First number?" a
|
||||
ASK "Second number?" b
|
||||
|
||||
SAY "Sum:"
|
||||
SAY a + b
|
||||
|
||||
@@ -2,20 +2,19 @@
|
||||
# Shows how to use DO TIMES and REPEAT loops
|
||||
|
||||
SAY "Counting with DO TIMES:"
|
||||
SET counter TO 1
|
||||
SET counter 1
|
||||
|
||||
DO 5 TIMES
|
||||
SAY counter
|
||||
SET counter TO counter + 1
|
||||
DONE
|
||||
|
||||
SET counter counter + 1
|
||||
END
|
||||
SAY ""
|
||||
SAY "Counting down with REPEAT:"
|
||||
SET n TO 5
|
||||
SET n 5
|
||||
|
||||
REPEAT
|
||||
SAY n
|
||||
SET n TO n - 1
|
||||
SET n n - 1
|
||||
UNTIL n = 0
|
||||
|
||||
SAY "Blastoff!"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
SAY "Guess the Number Game"
|
||||
|
||||
SET secret TO 7
|
||||
SET secret 7
|
||||
|
||||
ASK "Guess a number from 1 to 10:" AS guess
|
||||
ASK "Guess a number from 1 to 10:" guess
|
||||
|
||||
IF guess = secret
|
||||
SAY "You got it!"
|
||||
ELSE
|
||||
SAY "Not this time. The number was:"
|
||||
SAY secret
|
||||
ENDIF
|
||||
END
|
||||
@@ -1,5 +1,5 @@
|
||||
SAY "Name Input Example"
|
||||
|
||||
ASK "What is your name?" AS name
|
||||
ASK "What is your name?" name
|
||||
|
||||
SAY "Hello " + name + "!"
|
||||
|
||||
+9
-11
@@ -1,28 +1,26 @@
|
||||
SAY "Welcome to the Claro quiz!"
|
||||
|
||||
ASK "What is your name?" AS name
|
||||
ASK "What is your name?" name
|
||||
SAY "Hello " + name + "!"
|
||||
|
||||
SET score TO 0
|
||||
SET score 0
|
||||
|
||||
ASK "What is 2 + 2?" AS answer
|
||||
ASK "What is 2 + 2?" answer
|
||||
|
||||
IF answer = 4
|
||||
SAY "Correct!"
|
||||
SET score TO score + 1
|
||||
SET score score + 1
|
||||
ELSE
|
||||
SAY "Not quite. The answer is 4."
|
||||
ENDIF
|
||||
|
||||
ASK "What is 5 + 5?" AS answer2
|
||||
END
|
||||
ASK "What is 5 + 5?" answer2
|
||||
|
||||
IF answer2 = 10
|
||||
SAY "Correct!"
|
||||
SET score TO score + 1
|
||||
SET score score + 1
|
||||
ELSE
|
||||
SAY "Not quite. The answer is 10."
|
||||
ENDIF
|
||||
|
||||
END
|
||||
SAY "Your final score is:"
|
||||
SAY score
|
||||
|
||||
@@ -30,4 +28,4 @@ IF score = 2
|
||||
SAY "Great job!"
|
||||
ELSE
|
||||
SAY "Good try. Keep practicing!"
|
||||
ENDIF
|
||||
END
|
||||
@@ -1,6 +1,6 @@
|
||||
SAY "Shopping List Example"
|
||||
|
||||
SET items TO LIST
|
||||
SET items LIST
|
||||
ADD "milk" TO items
|
||||
ADD "bread" TO items
|
||||
ADD "apples" TO items
|
||||
@@ -9,4 +9,4 @@ SAY "Items to buy:"
|
||||
|
||||
FOR EACH item IN items
|
||||
SAY item
|
||||
DONE
|
||||
END
|
||||
@@ -1,3 +1,3 @@
|
||||
SAY "Lesson 2: Ask a question"
|
||||
ASK "What is your name?" AS name
|
||||
ASK "What is your name?" name
|
||||
SAY "Hello " + name
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SAY "Lesson 3: Variables"
|
||||
SET name TO "Alex"
|
||||
SET score TO 5
|
||||
SET name "Alex"
|
||||
SET score 5
|
||||
SAY name
|
||||
SAY score
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SAY "Lesson 4: Math"
|
||||
SET apples TO 3
|
||||
SET oranges TO 2
|
||||
SET total TO apples + oranges
|
||||
SET apples 3
|
||||
SET oranges 2
|
||||
SET total apples + oranges
|
||||
SAY total
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
SAY "Lesson 5: Choices"
|
||||
SET score TO 8
|
||||
SET score 8
|
||||
|
||||
IF score >= 5
|
||||
SAY "You passed!"
|
||||
ELSE
|
||||
SAY "Try again."
|
||||
ENDIF
|
||||
END
|
||||
@@ -1,8 +1,7 @@
|
||||
SAY "Lesson 6: Loops"
|
||||
DO 3 TIMES
|
||||
SAY "Practice"
|
||||
DONE
|
||||
|
||||
END
|
||||
FOR i FROM 1 TO 3
|
||||
SAY i
|
||||
DONE
|
||||
END
|
||||
@@ -1,8 +1,8 @@
|
||||
SAY "Lesson 7: Lists"
|
||||
SET items TO LIST
|
||||
SET items LIST
|
||||
ADD "milk" TO items
|
||||
ADD "bread" TO items
|
||||
|
||||
FOR EACH item IN items
|
||||
SAY item
|
||||
DONE
|
||||
END
|
||||
@@ -1,8 +1,7 @@
|
||||
SAY "Lesson 8: Functions with TEACH"
|
||||
|
||||
TEACH greet TAKES name
|
||||
TEACH greet name
|
||||
RETURN "Hello " + name
|
||||
LEARNED
|
||||
|
||||
CALL greet WITH "Alex"
|
||||
END
|
||||
DO greet "Alex"
|
||||
SAY RESULT
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
SAY "Lesson 10: Tiny Quiz"
|
||||
ASK "What is 2 + 2?" AS answer
|
||||
ASK "What is 2 + 2?" answer
|
||||
|
||||
IF answer = 4
|
||||
SAY "Correct!"
|
||||
ELSE
|
||||
SAY "The answer is 4."
|
||||
ENDIF
|
||||
END
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
These are small lesson programs for the first hour of learning Claro.
|
||||
|
||||
These lessons use short `ASK` and `SET`, `TEACH` with `DO`, and `END` for
|
||||
choices, loops, and functions. See [the syntax guide](../docs/SYNTAX.md).
|
||||
|
||||
Recommended order:
|
||||
|
||||
1. `01_hello.claro` - show text with `SAY`
|
||||
|
||||
+194
-80
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Claro v2026.10.20.160950 - Networking Reliability Release
|
||||
Claro v2026.10.20.223203 - Networking Reliability Release
|
||||
|
||||
A compact, buildable Claro interpreter focused on the beginner-friendly
|
||||
teaching core: readable commands, simple expressions, functions, collections,
|
||||
@@ -45,8 +45,10 @@ int pclose(FILE *stream);
|
||||
#define F_OK 0
|
||||
#endif
|
||||
|
||||
#define CLARO_VERSION "Claro v2026.10.20.160950"
|
||||
#define CLARO_VERSION "Claro v2026.10.20.223203"
|
||||
#define CLARO_MAX_CALL_DEPTH 256
|
||||
#define CLARO_MAX_EXPR_DEPTH 128
|
||||
#define CLARO_MAX_BLOCK_DEPTH 128
|
||||
|
||||
static void *xmalloc(size_t n) {
|
||||
void *p = malloc(n ? n : 1);
|
||||
@@ -575,6 +577,7 @@ typedef struct Runtime {
|
||||
int returning;
|
||||
Value ret;
|
||||
int call_depth;
|
||||
int range_depth;
|
||||
Program **programs;
|
||||
int pcount, pcap;
|
||||
char **import_stack;
|
||||
@@ -758,11 +761,13 @@ static void type_key_for(const char *name, char *out, size_t n) {
|
||||
claro_snprintf(out, n, "__type_%s", name ? name : "");
|
||||
}
|
||||
static const char *runtime_type_for(Runtime *rt, const char *name, char *buf, size_t n) {
|
||||
Value tv;
|
||||
Var *entry;
|
||||
type_key_for(name, buf, n);
|
||||
tv = rt_get(rt, buf);
|
||||
if (tv.type == V_STR && tv.str && tv.str[0])
|
||||
return tv.str;
|
||||
entry = env_find(rt->locals, buf);
|
||||
if (!entry)
|
||||
entry = env_find(rt->globals, buf);
|
||||
if (entry && entry->val.type == V_STR && entry->val.str && entry->val.str[0])
|
||||
return entry->val.str;
|
||||
return NULL;
|
||||
}
|
||||
static int rt_set_checked(Runtime *rt, const char *file, int line, const char *name,
|
||||
@@ -773,8 +778,11 @@ static int rt_set_checked(Runtime *rt, const char *file, int line, const char *n
|
||||
if (!name || !*name)
|
||||
return 0;
|
||||
if (type && *type) {
|
||||
Value metadata;
|
||||
type_key_for(name, key, sizeof(key));
|
||||
rt_set(rt, key, v_str(type));
|
||||
metadata = v_str(type);
|
||||
rt_set(rt, key, metadata);
|
||||
value_free(metadata);
|
||||
oldtype = type;
|
||||
} else
|
||||
oldtype = runtime_type_for(rt, name, oldbuf, sizeof(oldbuf));
|
||||
@@ -791,10 +799,13 @@ static int rt_set_checked(Runtime *rt, const char *file, int line, const char *n
|
||||
claro_snprintf(objname, sizeof(objname), "%.*s", (int)(dot - name), name);
|
||||
claro_snprintf(field, sizeof(field), "%s", dot + 1);
|
||||
obj = rt_get(rt, objname);
|
||||
if (obj.type != V_MAP)
|
||||
if (obj.type != V_MAP) {
|
||||
value_free(obj);
|
||||
obj = v_map();
|
||||
}
|
||||
map_put(obj.map, field, val);
|
||||
rt_set(rt, objname, obj);
|
||||
value_free(obj);
|
||||
}
|
||||
rt_set(rt, name, val);
|
||||
return 1;
|
||||
@@ -932,12 +943,13 @@ static void class_add_field(ClassDef *c, const char *name, const char *type) {
|
||||
c->fields = f;
|
||||
}
|
||||
static const char *object_class_name(Value obj) {
|
||||
Value cv;
|
||||
int index;
|
||||
if (obj.type != V_MAP || !obj.map)
|
||||
return NULL;
|
||||
cv = map_get(obj.map, "class");
|
||||
if (cv.type == V_STR && cv.str && cv.str[0])
|
||||
return cv.str;
|
||||
index = map_index(obj.map, "class");
|
||||
if (index >= 0 && obj.map->vals[index].type == V_STR &&
|
||||
obj.map->vals[index].str && obj.map->vals[index].str[0])
|
||||
return obj.map->vals[index].str;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -945,6 +957,8 @@ static const char *object_class_name(Value obj) {
|
||||
typedef struct {
|
||||
char **t;
|
||||
int n, cap, pos;
|
||||
int depth;
|
||||
const char *error;
|
||||
} Tokens;
|
||||
static void toks_add(Tokens *ts, const char *s, size_t n) {
|
||||
if (ts->n >= ts->cap) {
|
||||
@@ -993,6 +1007,8 @@ static Tokens tokenize(const char *s) {
|
||||
}
|
||||
if (s[i] == '"')
|
||||
i++;
|
||||
else if (!ts.error)
|
||||
ts.error = "Text is missing a closing quote. Add a double quote at the end.";
|
||||
str_ch(&q, '\001');
|
||||
str_add(&q, b.s ? b.s : "");
|
||||
toks_add(&ts, q.s ? q.s : "", strlen(q.s ? q.s : ""));
|
||||
@@ -1010,7 +1026,9 @@ static Tokens tokenize(const char *s) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (strchr("+-*/=<>", s[i])) {
|
||||
if (strchr("!+-*/=<>", s[i])) {
|
||||
if (s[i] == '!' && !ts.error)
|
||||
ts.error = "Use NOT instead of ! to reverse a condition.";
|
||||
toks_add(&ts, s + i, 1);
|
||||
i++;
|
||||
continue;
|
||||
@@ -1048,13 +1066,21 @@ static Value parse_expr(Runtime *rt, Tokens *ts);
|
||||
static Value parse_primary(Runtime *rt, Tokens *ts) {
|
||||
char *t = tok_next(ts);
|
||||
Value v;
|
||||
if (!t)
|
||||
if (!t) {
|
||||
if (ts->n)
|
||||
rt_error(rt, "", 0, "This expression needs a value after the operator.");
|
||||
return v_none();
|
||||
}
|
||||
if (strcmp(t, "(") == 0) {
|
||||
v = parse_expr(rt, ts);
|
||||
tok_match(ts, ")");
|
||||
if (!tok_match(ts, ")"))
|
||||
rt_error(rt, "", 0, "This expression needs a closing parenthesis: ).");
|
||||
return v;
|
||||
}
|
||||
if (strcmp(t, ")") == 0) {
|
||||
rt_error(rt, "", 0, "This expression needs a value before the closing parenthesis.");
|
||||
return v_none();
|
||||
}
|
||||
if (t[0] == '\001')
|
||||
return v_str(t + 1);
|
||||
if (ci_eq(t, "YES"))
|
||||
@@ -1088,19 +1114,24 @@ static Value parse_primary(Runtime *rt, Tokens *ts) {
|
||||
return v_str(t);
|
||||
}
|
||||
static Value parse_unary(Runtime *rt, Tokens *ts) {
|
||||
Value v, out;
|
||||
if (ts->depth >= CLARO_MAX_EXPR_DEPTH) {
|
||||
rt_error(rt, "", 0, "Expression nesting exceeds the safe limit of 128. Simplify the expression.");
|
||||
return v_none();
|
||||
}
|
||||
ts->depth++;
|
||||
if (tok_match(ts, "NOT")) {
|
||||
Value v = parse_unary(rt, ts);
|
||||
Value out = v_bool(!v_truth(v));
|
||||
v = parse_unary(rt, ts);
|
||||
out = v_bool(!v_truth(v));
|
||||
value_free(v);
|
||||
return out;
|
||||
}
|
||||
if (tok_match(ts, "-")) {
|
||||
Value v = parse_unary(rt, ts);
|
||||
Value out = v_num(-v_number(v));
|
||||
} else if (tok_match(ts, "-")) {
|
||||
v = parse_unary(rt, ts);
|
||||
out = v_num(-v_number(v));
|
||||
value_free(v);
|
||||
return out;
|
||||
}
|
||||
return parse_primary(rt, ts);
|
||||
} else
|
||||
out = parse_primary(rt, ts);
|
||||
ts->depth--;
|
||||
return out;
|
||||
}
|
||||
static Value parse_mul(Runtime *rt, Tokens *ts) {
|
||||
Value v = parse_unary(rt, ts);
|
||||
@@ -1225,7 +1256,14 @@ static Value parse_expr(Runtime *rt, Tokens *ts) {
|
||||
}
|
||||
static Value eval_expr(Runtime *rt, const char *expr) {
|
||||
Tokens ts = tokenize(expr);
|
||||
Value v = parse_expr(rt, &ts);
|
||||
Value v = v_none();
|
||||
if (ts.error)
|
||||
rt_error(rt, "", 0, "%s", ts.error);
|
||||
else {
|
||||
v = parse_expr(rt, &ts);
|
||||
if (!rt->error && !tok_end(&ts))
|
||||
rt_error(rt, "", 0, "Unexpected text after this expression: %s.", tok_peek(&ts));
|
||||
}
|
||||
tokens_free(&ts);
|
||||
return v;
|
||||
}
|
||||
@@ -2590,6 +2628,7 @@ static void scan_functions(Runtime *rt, Program *p, const char *prefix) {
|
||||
add_function(rt, fname, params, pcnt, p, j + 1,
|
||||
mend < 0 ? (end < 0 ? p->count : end) : mend);
|
||||
}
|
||||
split_args_free(params, pcnt);
|
||||
if (mend >= 0)
|
||||
j = mend;
|
||||
}
|
||||
@@ -2609,6 +2648,7 @@ static void scan_functions(Runtime *rt, Program *p, const char *prefix) {
|
||||
claro_snprintf(fname, sizeof(fname), "%s%s", prefix ? prefix : "", name);
|
||||
if (!find_function(rt, fname))
|
||||
add_function(rt, fname, params, pcnt, p, i + 1, end < 0 ? p->count : end);
|
||||
split_args_free(params, pcnt);
|
||||
if (end >= 0)
|
||||
i = end;
|
||||
}
|
||||
@@ -2825,21 +2865,27 @@ static void do_import(Runtime *rt, const char *path, const char *ns, const char
|
||||
}
|
||||
static void create_object_value(Runtime *rt, const char *file, int line, const char *cls,
|
||||
const char *var) {
|
||||
Value obj = v_map();
|
||||
ClassDef *c = find_class(rt, cls);
|
||||
Value obj, class_name;
|
||||
ClassDef *c;
|
||||
FieldDef *fd;
|
||||
char fullname[256];
|
||||
if (!cls || !*cls || !var || !*var) {
|
||||
rt_error(rt, file, line, "NEW needs a class and a name. Try: NEW Player player");
|
||||
return;
|
||||
}
|
||||
map_put(obj.map, "class", v_str(cls));
|
||||
obj = v_map();
|
||||
c = find_class(rt, cls);
|
||||
class_name = v_str(cls);
|
||||
map_put(obj.map, "class", class_name);
|
||||
value_free(class_name);
|
||||
rt_set_checked(rt, file, line, var, "OBJECT", obj);
|
||||
value_free(obj);
|
||||
if (c) {
|
||||
for (fd = c->fields; fd; fd = fd->next) {
|
||||
Value def = claro_default_for_type(fd->type);
|
||||
claro_snprintf(fullname, sizeof(fullname), "%s.%s", var, fd->name);
|
||||
rt_set_checked(rt, file, line, fullname, fd->type, def);
|
||||
value_free(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3088,7 +3134,10 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
char *var = xstrdup(trim_inplace((char *)as + 2));
|
||||
Value ov = eval_expr(rt, oe);
|
||||
const char *cn = object_class_name(ov);
|
||||
rt_set_checked(rt, p->path, pc + 1, var, "TEXT", v_str(cn ? cn : ""));
|
||||
Value class_name = v_str(cn ? cn : "");
|
||||
rt_set_checked(rt, p->path, pc + 1, var, "TEXT", class_name);
|
||||
value_free(class_name);
|
||||
value_free(ov);
|
||||
free(oe);
|
||||
free(var);
|
||||
} else if (as && starts_ci(t + 6, " FIELDS")) {
|
||||
@@ -3100,11 +3149,16 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
if (ov.type == V_MAP && ov.map) {
|
||||
for (i = 0; i < ov.map->count; i++) {
|
||||
if (strcmp(ov.map->keys[i], "class") != 0 &&
|
||||
!starts_ci(ov.map->keys[i], "__type_"))
|
||||
list_add(arr.list, v_str(ov.map->keys[i]));
|
||||
!starts_ci(ov.map->keys[i], "__type_")) {
|
||||
Value field_name = v_str(ov.map->keys[i]);
|
||||
list_add(arr.list, field_name);
|
||||
value_free(field_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
rt_set_checked(rt, p->path, pc + 1, var, "LIST", arr);
|
||||
value_free(ov);
|
||||
value_free(arr);
|
||||
free(oe);
|
||||
free(var);
|
||||
} else
|
||||
@@ -3879,15 +3933,21 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
char *txt = v_to_string(v);
|
||||
if (!strcmp(restup, "UPPER")) {
|
||||
char *r = text_upper_copy(txt);
|
||||
rt_set(rt, var, v_str(r));
|
||||
Value result = v_str(r);
|
||||
rt_set(rt, var, result);
|
||||
value_free(result);
|
||||
free(r);
|
||||
} else if (!strcmp(restup, "LOWER")) {
|
||||
char *r = text_lower_copy(txt);
|
||||
rt_set(rt, var, v_str(r));
|
||||
Value result = v_str(r);
|
||||
rt_set(rt, var, result);
|
||||
value_free(result);
|
||||
free(r);
|
||||
} else if (!strcmp(restup, "TRIM")) {
|
||||
char *r = text_trim_copy(txt);
|
||||
rt_set(rt, var, v_str(r));
|
||||
Value result = v_str(r);
|
||||
rt_set(rt, var, result);
|
||||
value_free(result);
|
||||
free(r);
|
||||
} else if (!strcmp(restup, "LENGTH")) {
|
||||
rt_set(rt, var, v_num((double)strlen(txt)));
|
||||
@@ -3918,6 +3978,7 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
free(left);
|
||||
free(right);
|
||||
}
|
||||
value_free(v);
|
||||
free(txt);
|
||||
free(ex);
|
||||
}
|
||||
@@ -3985,6 +4046,7 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
Value v = rt_get(rt, name);
|
||||
value_list_sort(v);
|
||||
rt_set(rt, name, v);
|
||||
value_free(v);
|
||||
free(name);
|
||||
return;
|
||||
}
|
||||
@@ -4076,10 +4138,18 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
}
|
||||
static void exec_range(Runtime *rt, Program *p, int start, int end) {
|
||||
int pc;
|
||||
/* Function calls have their own guard; count additional control-block frames. */
|
||||
if (rt->range_depth - rt->call_depth > CLARO_MAX_BLOCK_DEPTH) {
|
||||
rt_error(rt, p->path, start + 1,
|
||||
"Block nesting exceeds the safe limit of 128. Simplify the nested blocks.");
|
||||
return;
|
||||
}
|
||||
rt->range_depth++;
|
||||
if (end < 0 || end > p->count)
|
||||
end = p->count;
|
||||
for (pc = start; pc < end && !rt->error && !rt->returning; pc++)
|
||||
exec_line(rt, p, &pc, p->lines[pc]);
|
||||
rt->range_depth--;
|
||||
}
|
||||
static int run_file(Runtime *rt, const char *path) {
|
||||
Program *p = load_program(rt, path);
|
||||
@@ -4284,6 +4354,18 @@ static int check_file(const char *path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sp >= CLARO_MAX_BLOCK_DEPTH &&
|
||||
(!strcmp(up, "IF") || !strcmp(up, "TEACH") || !strcmp(up, "CLASS") ||
|
||||
!strcmp(up, "FOR") || !strcmp(up, "TRY") || !strcmp(up, "COMMENT") ||
|
||||
!strcmp(up, "REPEAT") || (!strcmp(up, "DO") && find_word_ci(t, "TIMES")) ||
|
||||
(!strcmp(up, "START") && starts_ci(t + 5, " TASK")))) {
|
||||
printf("%s:%d: Block nesting exceeds the safe limit of 128. Simplify the nested blocks.\n",
|
||||
path, line_no);
|
||||
errs++;
|
||||
sp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!strcmp(up, "IF")) {
|
||||
if (!*trim_inplace(t + 2)) {
|
||||
printf("%s:%d: IF needs a condition. Try: IF score = 10\n", path, line_no);
|
||||
@@ -4320,33 +4402,14 @@ static int check_file(const char *path) {
|
||||
line_no);
|
||||
errs++;
|
||||
} else {
|
||||
const char *takes = find_word_ci(t, "TAKES");
|
||||
const char *usingw = find_word_ci(t, "USING");
|
||||
const char *params = takes ? takes + 5 : (usingw ? usingw + 5 : NULL);
|
||||
if (!params) {
|
||||
char *after = t + 5;
|
||||
while (*after && isspace((unsigned char)*after))
|
||||
after++;
|
||||
while (*after && !isspace((unsigned char)*after))
|
||||
after++;
|
||||
params = after;
|
||||
}
|
||||
if (params) {
|
||||
char *pc = xstrdup(params);
|
||||
char *pp = pc;
|
||||
while (pp && *pp) {
|
||||
char *comma = strchr(pp, ',');
|
||||
if (comma) {
|
||||
*comma = 0;
|
||||
check_add_var(&vars, pp);
|
||||
pp = comma + 1;
|
||||
} else {
|
||||
check_add_var(&vars, pp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(pc);
|
||||
}
|
||||
char *function_name = NULL;
|
||||
char **parameters = NULL;
|
||||
int count = 0, index;
|
||||
parse_teach_parts(t, &function_name, ¶meters, &count);
|
||||
for (index = 0; index < count; index++)
|
||||
check_add_var(&vars, parameters[index]);
|
||||
split_args_free(parameters, count);
|
||||
/* function_name points into the borrowed line buffer. */
|
||||
}
|
||||
stack[sp++] = check_block("LEARNED", "TEACH", line_no);
|
||||
} else if (!strcmp(up, "DO")) {
|
||||
@@ -4435,8 +4498,7 @@ static int check_file(const char *path) {
|
||||
const char *pcur = t + 3;
|
||||
char *name = unquote_token(&pcur);
|
||||
if (!*trim_inplace(name) || !*trim_inplace((char *)pcur)) {
|
||||
printf("%s:%d: SET needs a name and a value. Try: SET name TO \"Alex\" or SET "
|
||||
"name \"Alex\"\n",
|
||||
printf("%s:%d: SET needs a name and a value. Try: SET name \"Alex\"\n",
|
||||
path, line_no);
|
||||
errs++;
|
||||
} else
|
||||
@@ -4445,7 +4507,7 @@ static int check_file(const char *path) {
|
||||
} else {
|
||||
char *name = substr(t + 3, to);
|
||||
if (!*trim_inplace(name)) {
|
||||
printf("%s:%d: SET needs a variable name. Try: SET score TO 0\n", path,
|
||||
printf("%s:%d: SET needs a variable name. Try: SET score 0\n", path,
|
||||
line_no);
|
||||
errs++;
|
||||
} else
|
||||
@@ -4536,6 +4598,10 @@ static int check_file(const char *path) {
|
||||
const char *expr = t + strlen(w);
|
||||
Tokens ts = tokenize(expr);
|
||||
int i;
|
||||
if (ts.error) {
|
||||
printf("%s:%d: %s\n", path, line_no, ts.error);
|
||||
errs++;
|
||||
}
|
||||
for (i = 0; i < ts.n; i++) {
|
||||
char *tok = ts.t[i];
|
||||
if (isalpha((unsigned char)tok[0]) && !ci_eq(tok, "YES") && !ci_eq(tok, "NO") &&
|
||||
@@ -4569,7 +4635,11 @@ static int check_file(const char *path) {
|
||||
fclose(f);
|
||||
while (sp > 0) {
|
||||
CheckBlock b = stack[--sp];
|
||||
printf("%s:%d: %s block opened here needs %s\n", path, b.line, b.open, b.expect);
|
||||
const char *ending = b.expect;
|
||||
if (!strcmp(b.open, "IF") || !strcmp(b.open, "TEACH") ||
|
||||
!strcmp(b.open, "DO") || !strcmp(b.open, "FOR"))
|
||||
ending = "END";
|
||||
printf("%s:%d: %s block opened here needs %s\n", path, b.line, b.open, ending);
|
||||
errs++;
|
||||
}
|
||||
if (errs == 0)
|
||||
@@ -4819,7 +4889,10 @@ static void parse_set_for_typecheck2(const char *t, char **name, char **type, ch
|
||||
if (isalpha((unsigned char)*q) && !isupper((unsigned char)*q))
|
||||
uppercase_candidate = 0;
|
||||
*name = xstrdup(trim_inplace(var));
|
||||
if (claro_is_type_word(mt)) {
|
||||
if (!*remaining && (ci_eq(mt, "LIST") || ci_eq(mt, "MAP"))) {
|
||||
/* A bare container word constructs a value, not an empty annotation. */
|
||||
*expr = xstrdup(mt);
|
||||
} else if (claro_is_type_word(mt)) {
|
||||
*type = xstrdup(mt);
|
||||
*expr = xstrdup(remaining);
|
||||
} else if (strchr(trim_inplace(var), '.') && *remaining && strchr(mt, ' ') == NULL &&
|
||||
@@ -5070,6 +5143,7 @@ static TypeParamCheck *collect_function_param_type_checks(FILE *f) {
|
||||
else
|
||||
claro_snprintf(full_name, sizeof(full_name), "%s", fname);
|
||||
collect_teach_param_checks_from_body(&checks, f, full_name, params, pcnt);
|
||||
split_args_free(params, pcnt);
|
||||
}
|
||||
}
|
||||
rewind(f);
|
||||
@@ -5114,6 +5188,7 @@ static TypeArityCheck *collect_function_arity_checks(FILE *f) {
|
||||
else
|
||||
claro_snprintf(full_name, sizeof(full_name), "%s", fname);
|
||||
add_type_arity_check(&checks, full_name, pcnt);
|
||||
split_args_free(params, pcnt);
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char inner[4096], iup[64], iw[128];
|
||||
char *it;
|
||||
@@ -5179,6 +5254,7 @@ static TypeReturnCheck *collect_function_return_type_checks(FILE *f) {
|
||||
add_type_return_check(&checks, full_name, first_ret);
|
||||
}
|
||||
}
|
||||
split_args_free(params, pcnt);
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char inner[4096], iup[64], iw[128];
|
||||
char *it;
|
||||
@@ -5284,6 +5360,7 @@ static TypeMethodCheck *collect_class_method_type_checks(FILE *f) {
|
||||
teachline[sizeof(teachline) - 1] = 0;
|
||||
parse_teach_parts(teachline, &fname, ¶ms, &pcnt);
|
||||
add_type_method_check(&checks, current_class, trim_inplace(fname));
|
||||
split_args_free(params, pcnt);
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char inner[4096], iup[64], iw[128];
|
||||
char *it;
|
||||
@@ -5302,6 +5379,31 @@ static TypeMethodCheck *collect_class_method_type_checks(FILE *f) {
|
||||
rewind(f);
|
||||
return checks;
|
||||
}
|
||||
static void type_checks_free(TypeParamCheck *params, TypeArityCheck *arities,
|
||||
TypeReturnCheck *returns, TypeFieldCheck *fields,
|
||||
TypeMethodCheck *methods) {
|
||||
while (params) {
|
||||
TypeParamCheck *next = params->next;
|
||||
free(params->func); free(params->param); free(params->type); free(params);
|
||||
params = next;
|
||||
}
|
||||
while (arities) {
|
||||
TypeArityCheck *next = arities->next;
|
||||
free(arities->func); free(arities); arities = next;
|
||||
}
|
||||
while (returns) {
|
||||
TypeReturnCheck *next = returns->next;
|
||||
free(returns->func); free(returns->type); free(returns); returns = next;
|
||||
}
|
||||
while (fields) {
|
||||
TypeFieldCheck *next = fields->next;
|
||||
free(fields->cls); free(fields->field); free(fields->type); free(fields); fields = next;
|
||||
}
|
||||
while (methods) {
|
||||
TypeMethodCheck *next = methods->next;
|
||||
free(methods->cls); free(methods->method); free(methods); methods = next;
|
||||
}
|
||||
}
|
||||
static int typecheck_file(const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
char line[4096];
|
||||
@@ -5551,6 +5653,7 @@ static int typecheck_file(const char *path) {
|
||||
} else if (declared_return)
|
||||
claro_snprintf(current_func, sizeof(current_func), "%s", full_name);
|
||||
}
|
||||
split_args_free(params, pcnt);
|
||||
} else if (!strcmp(up, "IF")) {
|
||||
if (in_teach) {
|
||||
if (nested_blocks < 64) {
|
||||
@@ -6165,7 +6268,7 @@ static int typecheck_file(const char *path) {
|
||||
const char *pcur = t + 2;
|
||||
fname = unquote_token(&pcur);
|
||||
if (*trim_inplace((char *)pcur))
|
||||
ac = split_args(pcur, &parts);
|
||||
ac = split_simple_args(pcur, &parts);
|
||||
} else {
|
||||
char *rest = t + 4;
|
||||
const char *with = find_word_ci(rest, "WITH");
|
||||
@@ -6265,6 +6368,7 @@ static int typecheck_file(const char *path) {
|
||||
errs++;
|
||||
}
|
||||
}
|
||||
split_args_free(parts, ac);
|
||||
free(fname);
|
||||
} else if (!strcmp(up, "CHECK") && starts_ci(t, "CHECK TYPE")) {
|
||||
const char *is = find_word_ci(t, "IS");
|
||||
@@ -6369,6 +6473,7 @@ static int typecheck_file(const char *path) {
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
type_checks_free(param_checks, arity_checks, return_checks, field_checks, method_checks);
|
||||
type_env_free(&types);
|
||||
if (errs == 0)
|
||||
printf("Type check OK\n");
|
||||
@@ -6451,7 +6556,13 @@ static int run_tests(void) {
|
||||
closedir(d);
|
||||
#else
|
||||
struct _finddata_t fd;
|
||||
intptr_t h = _findfirst("tests/*.claro", &fd);
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1300
|
||||
/* The VC6 CRT returns long here and does not define intptr_t. */
|
||||
long h;
|
||||
#else
|
||||
intptr_t h;
|
||||
#endif
|
||||
h = _findfirst("tests/*.claro", &fd);
|
||||
if (h == -1) {
|
||||
fprintf(stderr, "No tests directory\n");
|
||||
return 1;
|
||||
@@ -6512,19 +6623,19 @@ static int create_new_project(const char *name) {
|
||||
claro_snprintf(path, sizeof(path), "%s/main.claro", name);
|
||||
write_text_file_simple(
|
||||
path,
|
||||
"SAY \"Welcome to Claro!\"\n\nSET name TO \"Learner\"\nSAY \"Hello \" + name\n\nTEACH "
|
||||
"SAY \"Welcome to Claro!\"\n\nSET name \"Learner\"\nSAY \"Hello \" + name\n\nTEACH "
|
||||
"greet person\n SAY \"Nice to meet you, \" + person\nEND\n\nDO greet \"Friend\"\n");
|
||||
claro_snprintf(path, sizeof(path), "%s/packages", name);
|
||||
make_folder(path);
|
||||
claro_snprintf(path, sizeof(path), "%s/claro.project", name);
|
||||
claro_snprintf(
|
||||
text, sizeof(text),
|
||||
"manifest-version: 1\nname: %s\nmain: main.claro\nversion: v2026.10.20.160950\npackages:\n",
|
||||
"manifest-version: 1\nname: %s\nmain: main.claro\nversion: v2026.10.20.223203\npackages:\n",
|
||||
name);
|
||||
write_text_file_simple(path, text);
|
||||
claro_snprintf(path, sizeof(path), "%s/claro.lock", name);
|
||||
write_text_file_simple(path,
|
||||
"# Claro package lock\nlock-version: 1\nversion: v2026.10.20.160950\n");
|
||||
"# Claro package lock\nlock-version: 1\nversion: v2026.10.20.223203\n");
|
||||
claro_snprintf(path, sizeof(path), "%s/README.md", name);
|
||||
write_text_file_simple(
|
||||
path, "# My Claro Project\n\nBuild Claro, then run this project with:\n\n```bash\nclaro "
|
||||
@@ -6563,7 +6674,7 @@ static int run_repl(void) {
|
||||
break;
|
||||
if (ci_eq(t, "HELP")) {
|
||||
printf("Try: SAY \"Hello\"\n");
|
||||
printf("Try: SET name TO \"Jon\"\n");
|
||||
printf("Try: SET name \"Jon\"\n");
|
||||
printf("Try: SAY name\n");
|
||||
continue;
|
||||
}
|
||||
@@ -6578,6 +6689,9 @@ static int run_repl(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
runtime_free(&rt);
|
||||
free(p.path);
|
||||
free(p.lines);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6618,7 +6732,7 @@ static void ensure_project_file(void) {
|
||||
if (!file_exists_simple("claro.project"))
|
||||
write_text_file_simple("claro.project",
|
||||
"manifest-version: 1\nname: ClaroProject\nmain: "
|
||||
"main.claro\nversion: v2026.10.20.160950\npackages:\n");
|
||||
"main.claro\nversion: v2026.10.20.223203\npackages:\n");
|
||||
}
|
||||
static char *project_value(const char *key) {
|
||||
char *txt = read_file_text("claro.project");
|
||||
@@ -6744,7 +6858,7 @@ static int write_package_lock(void) {
|
||||
free(txt);
|
||||
return 0;
|
||||
}
|
||||
fputs("# Claro package lock\nlock-version: 1\nversion: v2026.10.20.160950\n", f);
|
||||
fputs("# Claro package lock\nlock-version: 1\nversion: v2026.10.20.223203\n", f);
|
||||
p = txt;
|
||||
while (*p) {
|
||||
char *line = p;
|
||||
@@ -7227,7 +7341,7 @@ static int package_doctor(void) {
|
||||
}
|
||||
if (txt && !package_lock_release_version_matches(txt)) {
|
||||
printf(
|
||||
" BAD lock release version: expected exactly one version: v2026.10.20.160950 line\n");
|
||||
" BAD lock release version: expected exactly one version: v2026.10.20.223203 line\n");
|
||||
ok = 0;
|
||||
}
|
||||
free(txt);
|
||||
@@ -7391,7 +7505,7 @@ static int run_package_cmd(int argc, char **argv, int arg) {
|
||||
static int print_ide_info(void) {
|
||||
printf("{\n");
|
||||
printf(" \"language\": \"Claro\",\n");
|
||||
printf(" \"version\": \"v2026.10.20.160950\",\n");
|
||||
printf(" \"version\": \"v2026.10.20.223203\",\n");
|
||||
printf(" \"file_extensions\": [\".claro\"],\n");
|
||||
printf(" \"keywords\": "
|
||||
"[\"SAY\",\"ASK\",\"SET\",\"IF\",\"ELSE\",\"ENDIF\",\"END\",\"REPEAT\",\"UNTIL\","
|
||||
@@ -7417,9 +7531,9 @@ static int print_ide_info(void) {
|
||||
printf(" \"snippets\": [\n");
|
||||
printf(" {\"name\": \"hello\", \"prefix\": \"say\", \"body\": \"SAY \\\"Hello!\\\"\"},\n");
|
||||
printf(" {\"name\": \"if_else\", \"prefix\": \"if\", \"body\": \"IF condition\\n SAY "
|
||||
"\\\"Yes\\\"\\nELSE\\n SAY \\\"No\\\"\\nENDIF\"},\n");
|
||||
"\\\"Yes\\\"\\nELSE\\n SAY \\\"No\\\"\\nEND\"},\n");
|
||||
printf(" {\"name\": \"function\", \"prefix\": \"teach\", \"body\": \"TEACH name item\\n "
|
||||
"SAY item\\nLEARNED\"},\n");
|
||||
"SAY item\\nEND\"},\n");
|
||||
printf(" {\"name\": \"typed_list\", \"prefix\": \"listof\", \"body\": \"SET names AS LIST "
|
||||
"OF TEXT TO LIST\\nADD \\\"Ada\\\" TO names\"},\n");
|
||||
printf(" {\"name\": \"task\", \"prefix\": \"task\", \"body\": \"START TASK helloTask\\n "
|
||||
@@ -7741,7 +7855,7 @@ static int run_validate(void) {
|
||||
printf(
|
||||
"%s\n",
|
||||
fails ? "Validation found problems."
|
||||
: "Validation passed. Claro v2026.10.20.160950 foundation checks are ready for use.");
|
||||
: "Validation passed. Claro v2026.10.20.223203 foundation checks are ready for use.");
|
||||
return fails ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"hello.claro": [
|
||||
"",
|
||||
"Hello from Claro RC2!\n"
|
||||
],
|
||||
"name_input.claro": [
|
||||
"Ada\n",
|
||||
"Name Input Example\nWhat is your name?\nHello Ada!\n"
|
||||
],
|
||||
"quiz.claro": [
|
||||
"Ada\n4\n10\n",
|
||||
"Welcome to the Claro quiz!\nWhat is your name?\nHello Ada!\nWhat is 2 + 2?\nCorrect!\nWhat is 5 + 5?\nCorrect!\nYour final score is:\n2\nGreat job!\n"
|
||||
],
|
||||
"calculator.claro": [
|
||||
"2\n3\n",
|
||||
"Claro Calculator\nFirst number?\nSecond number?\nSum:\n5\n"
|
||||
],
|
||||
"guessing_game.claro": [
|
||||
"7\n",
|
||||
"Guess the Number Game\nGuess a number from 1 to 10:\nYou got it!\n"
|
||||
],
|
||||
"shopping_list.claro": [
|
||||
"",
|
||||
"Shopping List Example\nItems to buy:\nmilk\nbread\napples\n"
|
||||
],
|
||||
"save_and_load_file.claro": [
|
||||
"",
|
||||
"Save and Load Example\nThe file says:\nClaro saved this file.\n"
|
||||
],
|
||||
"simple_functions.claro": [
|
||||
"",
|
||||
"Simple functions in Claro\nHello Jon\nHello Friend\n"
|
||||
],
|
||||
"text_and_lists.claro": [
|
||||
"",
|
||||
"Jon\nJON\n"
|
||||
],
|
||||
"counting.claro": [
|
||||
"",
|
||||
"Counting with DO TIMES:\n1\n2\n3\n4\n5\n\nCounting down with REPEAT:\n5\n4\n3\n2\n1\nBlastoff!\n"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build clean source/Linux-musl/Windows-x64 archives from the current tree."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DIRECTORIES = ['.forgejo', 'src', 'docs', 'lessons', 'examples', 'lib', 'tests', 'tools', 'assets', 'support']
|
||||
FILES = ['README.md', 'LICENSE', 'CHANGELOG.md', 'CONTRIBUTING.md', 'Makefile', 'build.bat', 'build.ps1', 'build98.bat', 'build98.rsp', 'run_tests.bat']
|
||||
FLAGS = ['-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-O2', '-static']
|
||||
|
||||
|
||||
def sha256(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--target', required=True, choices=['source', 'linux-x86_64', 'windows-x86_64'])
|
||||
parser.add_argument('--output', required=True, type=Path)
|
||||
args = parser.parse_args()
|
||||
match = re.search(r'#define CLARO_VERSION "Claro (v[^"]+)"', (ROOT / 'src/claro.c').read_text())
|
||||
if match is None:
|
||||
raise SystemExit('Cannot read the runtime version from src/claro.c')
|
||||
version = match[1]
|
||||
args.output.mkdir(parents=True, exist_ok=True)
|
||||
name = f'Claro-{version}-{args.target}'
|
||||
destination = args.output / (name + '.zip')
|
||||
if destination.exists():
|
||||
raise SystemExit(f'Refusing to replace existing archive: {destination}')
|
||||
with tempfile.TemporaryDirectory(prefix='claro-package-', dir=os.environ.get('TMPDIR')) as tmp:
|
||||
stage = Path(tmp) / name
|
||||
stage.mkdir()
|
||||
for directory in DIRECTORIES:
|
||||
for source in sorted((ROOT / directory).rglob('*')):
|
||||
if not source.is_file() or source.is_symlink() or '__pycache__' in source.parts or source.suffix in ['.pyc', '.o']:
|
||||
continue
|
||||
target = stage / source.relative_to(ROOT)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source, target)
|
||||
for filename in FILES:
|
||||
shutil.copy2(ROOT / filename, stage / filename)
|
||||
command = []
|
||||
compiler = None
|
||||
if args.target != 'source':
|
||||
if args.target == 'linux-x86_64':
|
||||
if platform.machine().lower() not in ['x86_64', 'amd64']:
|
||||
raise SystemExit('Linux x64 package requires an x86-64 musl toolchain')
|
||||
compiler, executable = 'musl-gcc', 'claro'
|
||||
notices = {'musl.txt': '/usr/share/doc/musl/copyright', 'GCC-runtime.txt': '/usr/share/doc/gcc-15-base/copyright'}
|
||||
else:
|
||||
compiler, executable = 'x86_64-w64-mingw32-gcc', 'claro.exe'
|
||||
notices = {'MinGW-runtime.txt': '/usr/share/doc/mingw-w64-common/copyright', 'GCC-runtime.txt': '/usr/share/doc/gcc-mingw-w64-x86-64-win32/copyright'}
|
||||
if not shutil.which(compiler):
|
||||
raise SystemExit(f'Missing distribution compiler: {compiler}')
|
||||
command = [compiler] + FLAGS + ['src/claro.c', '-o', executable, '-lm']
|
||||
if args.target == 'windows-x86_64':
|
||||
command += ['-Wl,--stack,16777216']
|
||||
subprocess.run(command, cwd=stage, check=True)
|
||||
third_party = stage / 'THIRD_PARTY_NOTICES'
|
||||
third_party.mkdir()
|
||||
for filename, source in notices.items():
|
||||
if not Path(source).is_file():
|
||||
raise SystemExit(f'Missing required toolchain notice: {source}')
|
||||
shutil.copy2(source, third_party / filename)
|
||||
metadata = {'version': version, 'target': args.target, 'source_sha256': sha256(stage / 'src/claro.c'), 'build_command': command}
|
||||
if compiler:
|
||||
metadata['compiler'] = subprocess.check_output([compiler, '--version'], text=True).splitlines()[0]
|
||||
(stage / 'DISTRIBUTION.json').write_text(json.dumps(metadata, indent=2) + '\n')
|
||||
start = 'Extract this entire archive before using Claro. Open a terminal in the extracted folder.\n\n'
|
||||
if args.target == 'windows-x86_64':
|
||||
start += 'Run: .\\claro.exe lessons\\01_hello.claro\nThis is a genuine Windows x64 executable, not the Linux build with an .exe suffix.\n'
|
||||
elif args.target == 'linux-x86_64':
|
||||
start += 'Run: ./claro lessons/01_hello.claro\nIf extraction removed execute permission: chmod +x claro\nThis is a static musl build; it does not require the builder host glibc.\n'
|
||||
else:
|
||||
start += 'Build: make\nOr compile src/claro.c directly with the strict C89 command in README.md.\nOn Windows, use build.bat/build.ps1; the executable needs the documented stack reserve.\n'
|
||||
start += '\nRead docs/QUICK_START.md and docs/PORTABILITY.md. Run only trusted scripts.\nReal HTTP requires curl; the basic lessons and offline networking need no network.\n'
|
||||
(stage / 'START_HERE.txt').write_text(start)
|
||||
entries = {str(path.relative_to(stage)).replace(os.sep, '/'): sha256(path) for path in sorted(stage.rglob('*')) if path.is_file()}
|
||||
(stage / 'MANIFEST.json').write_text(json.dumps({'sha256': entries}, indent=2) + '\n')
|
||||
with zipfile.ZipFile(destination, 'w', compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
for path in sorted(stage.rglob('*')):
|
||||
if path.is_file():
|
||||
archive.write(path, path.relative_to(stage.parent))
|
||||
print(json.dumps({'archive': str(destination.resolve()), 'sha256': sha256(destination), 'target': args.target}, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify recursive Claro calls stop with the documented runtime diagnostic."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
EXPECTED = (
|
||||
"Claro function call depth exceeded the safe limit of 256. "
|
||||
"Simplify the recursion or add a stopping condition."
|
||||
)
|
||||
SCRIPT = """TEACH loop
|
||||
CALL loop
|
||||
ENDTEACH
|
||||
TRY
|
||||
CALL loop
|
||||
CATCH
|
||||
SAY LASTERROR
|
||||
ENDTRY
|
||||
"""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
binary = ROOT / ("claro.exe" if sys.platform == "win32" else "claro")
|
||||
if not binary.exists():
|
||||
print(f"FAIL: build Claro first; missing {binary}")
|
||||
return 1
|
||||
with tempfile.TemporaryDirectory(prefix="claro-call-depth-") as tmp:
|
||||
program = Path(tmp) / "recursive.claro"
|
||||
program.write_text(SCRIPT, encoding="utf-8")
|
||||
run = subprocess.run([str(binary), str(program)], cwd=ROOT, text=True,
|
||||
capture_output=True, timeout=30)
|
||||
output = run.stdout + run.stderr
|
||||
if EXPECTED not in output:
|
||||
print("FAIL: recursion did not produce the documented call-depth error")
|
||||
print(output, end="")
|
||||
return 1
|
||||
if run.returncode == 0:
|
||||
print("FAIL: runtime error unexpectedly exited successfully")
|
||||
return 1
|
||||
print("PASS: unbounded recursion stops with the documented call-depth error")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -12,6 +12,11 @@ REQUIRED_COMMANDS = [
|
||||
"python3 tools/validate_version_convention.py",
|
||||
"python3 tools/validate_package_security.py",
|
||||
"python3 tools/validate_compiler_warnings.py",
|
||||
"python3 tools/validate_reliability.py",
|
||||
"python3 tools/validate_learning_syntax.py",
|
||||
"python3 tools/validate_c89.py",
|
||||
"python3 tools/validate_msvc6_source.py",
|
||||
"python3 tools/validate_documentation_status.py",
|
||||
"python3 tools/validate_ci_workflow.py",
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract a distribution, verify hashes, and execute its actual payload."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import stat
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
|
||||
def digest(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('archive', type=Path)
|
||||
parser.add_argument('--wine-prefix', type=Path)
|
||||
parser.add_argument('--report', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
if args.report.exists():
|
||||
raise SystemExit('Refusing to replace existing verification report')
|
||||
results = {'archive': str(args.archive.resolve()), 'sha256': digest(args.archive), 'checks': []}
|
||||
logs = args.report.parent / (args.report.stem + '-logs')
|
||||
logs.mkdir(parents=True, exist_ok=False)
|
||||
env = dict(os.environ)
|
||||
|
||||
def run(label, command, cwd, timeout=180):
|
||||
result = subprocess.run(command, cwd=cwd, env=env, capture_output=True, text=True, timeout=timeout)
|
||||
log = logs / (label + '.log')
|
||||
log.write_text(result.stdout + result.stderr, encoding='utf-8')
|
||||
results['checks'].append({'name': label, 'exit': result.returncode, 'log': str(log.resolve())})
|
||||
assert result.returncode == 0, (label, result.returncode, (result.stdout + result.stderr)[-3000:])
|
||||
print('PASS:', label)
|
||||
return result.stdout
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory(prefix='claro-unpack-', dir=os.environ.get('TMPDIR')) as tmp:
|
||||
work = Path(tmp) / 'unpacked package with spaces'
|
||||
work.mkdir()
|
||||
with zipfile.ZipFile(args.archive) as archive:
|
||||
names = archive.namelist()
|
||||
assert len(names) == len(set(names)), 'Duplicate archive entries'
|
||||
assert len({PurePosixPath(name).parts[0] for name in names}) == 1, 'Expected one archive root'
|
||||
for info in archive.infolist():
|
||||
path = PurePosixPath(info.filename)
|
||||
assert not path.is_absolute() and '..' not in path.parts and '\\' not in info.filename, 'Unsafe archive path'
|
||||
mode = info.external_attr >> 16
|
||||
assert not stat.S_ISLNK(mode), 'Archive must not contain symlinks'
|
||||
target = work.joinpath(*path.parts)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(archive.read(info))
|
||||
if os.name != 'nt' and mode:
|
||||
target.chmod(mode & 0o777)
|
||||
root = next(work.iterdir())
|
||||
manifest = json.loads((root / 'MANIFEST.json').read_text())['sha256']
|
||||
actual = {str(p.relative_to(root)).replace(os.sep, '/'): digest(p) for p in root.rglob('*') if p.is_file() and p.name != 'MANIFEST.json'}
|
||||
assert actual == manifest, 'Package manifest mismatch'
|
||||
assert not any('__pycache__' in name or name.endswith('.pyc') or 'claro-asan' in name for name in actual), 'Development leftovers in package'
|
||||
metadata = json.loads((root / 'DISTRIBUTION.json').read_text())
|
||||
assert metadata['source_sha256'] == digest(root / 'src/claro.c')
|
||||
results.update(target=metadata['target'], version=metadata['version'], manifest_files=len(manifest), source_sha256=metadata['source_sha256'])
|
||||
print('PASS: archive inventory, source hash and manifest')
|
||||
target = metadata['target']
|
||||
if target == 'source':
|
||||
assert not (root / 'claro').exists() and not (root / 'claro.exe').exists()
|
||||
isolated = work / 'standalone'
|
||||
isolated.mkdir()
|
||||
shutil.copy2(root / 'src/claro.c', isolated / 'claro.c')
|
||||
run('isolated-c89-build', ['gcc', '-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', 'claro.c', '-o', 'claro', '-lm'], isolated)
|
||||
run('isolated-version', [str(isolated / 'claro'), '--version'], isolated)
|
||||
run('source-make', ['make', '-s', 'all'], root)
|
||||
executable = root / ('claro.exe' if os.name == 'nt' else 'claro')
|
||||
runner = [str(executable)]
|
||||
elif target == 'linux-x86_64':
|
||||
executable = root / 'claro'
|
||||
assert not (root / 'claro.exe').exists(), 'Linux archive contains misleading .exe'
|
||||
data = executable.read_bytes()
|
||||
assert data[:5] == b'\x7fELF\x02' and struct.unpack_from('<H', data, 18)[0] == 62
|
||||
headers = run('elf-inspection', ['readelf', '-l', str(executable)], root)
|
||||
assert 'INTERP' not in headers, 'Linux release is not static'
|
||||
runner = [str(executable)]
|
||||
elif target == 'windows-x86_64':
|
||||
executable = root / 'claro.exe'
|
||||
assert not (root / 'claro').exists()
|
||||
data = executable.read_bytes()
|
||||
assert data[:2] == b'MZ'
|
||||
pe = struct.unpack_from('<I', data, 60)[0]
|
||||
assert data[pe:pe+4] == b'PE\0\0' and struct.unpack_from('<H', data, pe+4)[0] == 0x8664
|
||||
reserve = struct.unpack_from('<Q', data, pe+24+72)[0]
|
||||
assert reserve >= 16777216, 'Windows recursion guard requires a sufficient stack reserve'
|
||||
results['windows_stack_reserve'] = reserve
|
||||
pe_text = run('pe-inspection', ['x86_64-w64-mingw32-objdump', '-p', str(executable)], root)
|
||||
results['windows_dll_imports'] = re.findall(r'DLL Name:\s*(\S+)', pe_text)
|
||||
if os.name == 'nt':
|
||||
runner = [str(executable)]
|
||||
else:
|
||||
assert args.wine_prefix and args.wine_prefix.is_dir(), 'Supply an initialized isolated --wine-prefix'
|
||||
env.update(WINEPREFIX=str(args.wine_prefix.resolve()), WINEDEBUG='-all')
|
||||
runner = ['wine', str(executable)]
|
||||
else:
|
||||
raise AssertionError('Unknown distribution target')
|
||||
version = run('version', runner + ['--version'], root)
|
||||
assert metadata['version'] in version
|
||||
standalone = work / 'single executable with spaces'
|
||||
standalone.mkdir()
|
||||
isolated_binary = standalone / executable.name
|
||||
shutil.copy2(executable, isolated_binary)
|
||||
(standalone / 'hello.claro').write_text('SAY "Standalone Claro works"\n')
|
||||
isolated_runner = (['wine', str(isolated_binary)] if runner[0] == 'wine' else [str(isolated_binary)])
|
||||
output = run('standalone-runtime', isolated_runner + ['hello.claro'], standalone)
|
||||
assert output == 'Standalone Claro works\n'
|
||||
run('doctor', runner + ['doctor'], root)
|
||||
golden = run('golden-tests', runner + ['test'], root)
|
||||
results['golden_pass_count'] = len(re.findall(r'^PASS tests/', golden, re.M))
|
||||
assert results['golden_pass_count'] == len(list((root / 'tests').glob('*.out'))), 'Golden runner did not cover all packaged fixtures'
|
||||
run('builtin-validation', runner + ['validate'], root)
|
||||
if runner[0] == 'wine':
|
||||
launcher = work / 'wine-launcher'
|
||||
launcher.write_text('#!/bin/sh\nexec wine ' + shlex.quote(str(executable)) + ' "$@"\n')
|
||||
launcher.chmod(0o755)
|
||||
env['CLARO_EXE'] = str(launcher)
|
||||
else:
|
||||
env['CLARO_EXE'] = str(executable)
|
||||
run('learning-path', [sys.executable, str(root / 'tools/validate_learning_syntax.py')], root, timeout=600 if runner[0] == 'wine' else 180)
|
||||
run('first-hour', [sys.executable, str(root / 'tools/validate_first_hour.py')], root)
|
||||
if target == 'source':
|
||||
run('full-source-validation', [sys.executable, str(root / 'tools/validate_v1_17.py')], root, timeout=360)
|
||||
results['status'] = 'passed'
|
||||
except Exception as error:
|
||||
results.update(status='failed', error=str(error))
|
||||
raise
|
||||
finally:
|
||||
args.report.write_text(json.dumps(results, indent=2) + '\n', encoding='utf-8')
|
||||
print('Verified:', args.report)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check active status/navigation without treating historical claims as current."""
|
||||
import ast
|
||||
from pathlib import Path
|
||||
import re
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ACTIVE = (
|
||||
'README.md', 'docs/README.md', 'docs/CURRENT_STATUS.md',
|
||||
'docs/RELEASE_STATUS.md', 'docs/FINAL_RELEASE_CHECKLIST.md',
|
||||
'docs/CLASSROOM_READINESS.md', 'docs/TESTING.md', 'docs/HARDENING.md',
|
||||
'docs/PORTABILITY.md', 'docs/RELIABILITY.md',
|
||||
'docs/history/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md',
|
||||
'docs/history/HARDENING_BEFORE_STATUS_CLEANUP.md',
|
||||
)
|
||||
|
||||
|
||||
def validate():
|
||||
match = re.search(r'#define CLARO_VERSION "Claro ([^"]+)"',
|
||||
(ROOT / 'src/claro.c').read_text())
|
||||
assert match, 'Missing runtime version'
|
||||
version = match.group(1)
|
||||
readme = (ROOT / 'README.md').read_text()
|
||||
assert 'Current release:' not in readme, 'A development stamp is not a published release'
|
||||
assert f'Development version: **{version}**' in readme, 'README must match the runtime development stamp'
|
||||
status = (ROOT / 'docs/CURRENT_STATUS.md').read_text()
|
||||
assert version in status and 'RELEASE_STATUS.md' in status
|
||||
assert len(status.splitlines()) <= 100, 'Keep the current status concise; archive detailed history'
|
||||
assert 'not a sandbox' in status.lower()
|
||||
release = (ROOT / 'docs/RELEASE_STATUS.md').read_text()
|
||||
assert 'not published' in release.lower() and 'wine' in release.lower()
|
||||
assert 'unverified' in release.lower() and 'MSVC600' in release
|
||||
checklist = (ROOT / 'docs/FINAL_RELEASE_CHECKLIST.md').read_text()
|
||||
assert 'v1.xx.yy' not in checklist and 'vYYYY.MM.DD.HHMMSS' in checklist
|
||||
assert '[ ]' in checklist, 'Release actions must be explicit, unchecked gates'
|
||||
classroom = (ROOT / 'docs/CLASSROOM_READINESS.md').read_text().lower()
|
||||
assert 'learner testing' in classroom and 'not yet validated' in classroom
|
||||
hardening = (ROOT / 'docs/HARDENING.md').read_text()
|
||||
assert 'historical' in hardening.lower() and 'RELIABILITY.md' in hardening
|
||||
for name in ['CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md', 'HARDENING_BEFORE_STATUS_CLEANUP.md']:
|
||||
assert (ROOT / 'docs/history' / name).is_file(), ('Missing historical record', name)
|
||||
tree = ast.parse((ROOT / 'tools/stamp_version.py').read_text())
|
||||
current_docs = next(ast.literal_eval(n.value) for n in tree.body
|
||||
if isinstance(n, ast.Assign) and any(isinstance(t, ast.Name) and t.id == 'CURRENT_DOCS' for t in n.targets))
|
||||
assert 'RELEASE_STATUS.md' not in current_docs, 'Do not re-stamp immutable verification checkpoints'
|
||||
checked = 0
|
||||
for name in ACTIVE:
|
||||
path = ROOT / name
|
||||
text = re.sub(r'```.*?```', '', path.read_text(), flags=re.S)
|
||||
for target in re.findall(r'!?\[[^\]]*\]\(([^\s)]+)\)', text):
|
||||
url = urlsplit(target)
|
||||
if url.scheme or url.netloc or not url.path:
|
||||
continue
|
||||
destination = (path.parent / unquote(url.path)).resolve()
|
||||
assert destination.is_relative_to(ROOT), (name, 'Link leaves the repository', target)
|
||||
assert destination.exists(), (name, 'Broken local link', target)
|
||||
checked += 1
|
||||
print(f'PASS: development/release separation, archived history, claim scope, and {checked} local documentation links')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
validate()
|
||||
@@ -8,7 +8,7 @@ import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EXE = ROOT / ("claro.exe" if os.name == "nt" else "claro")
|
||||
EXE = Path(os.environ.get("CLARO_EXE", str(ROOT / ("claro.exe" if os.name == "nt" else "claro")))).resolve()
|
||||
INPUTS = {2: "Ada\n", 5: "4\n"}
|
||||
EXPECTED = {
|
||||
1: "Hello!\n",
|
||||
@@ -35,7 +35,7 @@ def main() -> int:
|
||||
script.write_text(block + "\n", encoding="utf-8")
|
||||
run = subprocess.run([str(EXE), str(script)], cwd=tmp,
|
||||
input=INPUTS.get(number, ""), text=True,
|
||||
capture_output=True)
|
||||
capture_output=True, timeout=10)
|
||||
if run.returncode or run.stdout != EXPECTED[number]:
|
||||
print(f"FAIL: First Hour block {number}: exit={run.returncode}")
|
||||
print(f"expected stdout: {EXPECTED[number]!r}")
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the full introductory learning path with invalid-access and leak checks."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def main():
|
||||
if os.name == 'nt':
|
||||
print('SKIP: learning LeakSanitizer validation requires POSIX')
|
||||
return
|
||||
with tempfile.TemporaryDirectory(prefix='claro-learning-ownership-', dir=os.environ.get('TMPDIR')) as tmp:
|
||||
binary = Path(tmp) / 'claro-asan'
|
||||
subprocess.run(['gcc', '-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-O0', '-g', '-fsanitize=address,undefined', str(ROOT / 'src/claro.c'), '-o', str(binary), '-lm'], check=True)
|
||||
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=1:halt_on_error=1', UBSAN_OPTIONS='halt_on_error=1', CLARO_EXE=str(binary))
|
||||
for checker in ['validate_learning_syntax.py', 'validate_first_hour.py']:
|
||||
result = subprocess.run([sys.executable, str(ROOT / 'tools' / checker)], cwd=ROOT, env=env, capture_output=True, text=True, timeout=90)
|
||||
if result.returncode:
|
||||
print(result.stdout + result.stderr)
|
||||
raise SystemExit(result.returncode)
|
||||
print('PASS: leak-checked', checker)
|
||||
print('PASS: introductory lessons, examples, checker paths and REPL under ASan/UBSan/LSan')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keep the introductory learning path consistent and executable."""
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EXE = Path(os.environ.get('CLARO_EXE', str(ROOT / ('claro.exe' if os.name == 'nt' else 'claro')))).resolve()
|
||||
DOCS = ['README.md', 'docs/QUICK_START.md', 'docs/FIRST_HOUR.md', 'docs/SIMPLE_FUNCTIONS.md']
|
||||
LEGACY = re.compile(r'^\s*(?:CALL\b|LEARNED\b|ENDIF\b|DONE\b|TEACH\b[^\n]*\bTAKES\b|SET\s+\w+\s+TO\b|ASK\s+"(?:[^"\\]|\\.)*"\s+AS\b)', re.MULTILINE)
|
||||
LESSONS = {
|
||||
'01_hello.claro': ('', 'Lesson 1: Hello\nClaro can show messages with SAY.\nHello from Claro!\n'),
|
||||
'02_ask_name.claro': ('Ada\n', 'Lesson 2: Ask a question\nWhat is your name?\nHello Ada\n'),
|
||||
'03_variables.claro': ('', 'Lesson 3: Variables\nAlex\n5\n'),
|
||||
'04_math.claro': ('', 'Lesson 4: Math\n5\n'),
|
||||
'05_if_else.claro': ('', 'Lesson 5: Choices\nYou passed!\n'),
|
||||
'06_loops.claro': ('', 'Lesson 6: Loops\nPractice\nPractice\nPractice\n1\n2\n3\n'),
|
||||
'07_lists.claro': ('', 'Lesson 7: Lists\nmilk\nbread\n'),
|
||||
'08_functions.claro': ('', 'Lesson 8: Functions with TEACH\nHello Alex\n'),
|
||||
'09_files.claro': ('', 'Lesson 9: Files\nClaro saved this note.\n'),
|
||||
'10_final_quiz.claro': ('4\n', 'Lesson 10: Tiny Quiz\nWhat is 2 + 2?\nCorrect!\n'),
|
||||
}
|
||||
QUICK = [('', 'Hello!\n'), ('Ada\n', 'What is your name?\nHello Ada\n'),
|
||||
('', '10\n'), ('', 'Good job!\n'), ('', 'Practice\n' * 3),
|
||||
('', 'milk\nbread\n'), ('', 'Hello Alex\n'), ('', 'Hello from Claro\n'),
|
||||
('4\n', 'Tiny Quiz\nWhat is 2 + 2?\nCorrect!\n')]
|
||||
|
||||
|
||||
def blocks(path):
|
||||
return re.findall(r'```claro\s*\n(.*?)\n```', path.read_text(encoding='utf-8'), re.S)
|
||||
|
||||
|
||||
def check_run(work, label, code, input_text, expected):
|
||||
script = work / 'example.claro'
|
||||
script.write_text(code + '\n', encoding='utf-8')
|
||||
for command in ['check', 'typecheck']:
|
||||
checked = subprocess.run([str(EXE), command, str(script)], cwd=work, text=True, capture_output=True, timeout=10)
|
||||
assert checked.returncode == 0, (label, command, checked.stdout, checked.stderr)
|
||||
result = subprocess.run([str(EXE), str(script)], cwd=work, input=input_text,
|
||||
text=True, capture_output=True, timeout=10)
|
||||
assert result.returncode == 0 and result.stdout == expected and not result.stderr, (label, result.returncode, result.stdout, result.stderr)
|
||||
print('PASS:', label)
|
||||
|
||||
|
||||
def main():
|
||||
actual = {p.name for p in (ROOT / 'lessons').glob('*.claro')}
|
||||
assert actual == set(LESSONS), ('Lesson coverage changed; update checker', actual)
|
||||
sources = [(name, '\n'.join(blocks(ROOT / name))) for name in DOCS]
|
||||
sources += [('lessons/' + name, (ROOT / 'lessons' / name).read_text()) for name in LESSONS]
|
||||
examples = json.loads((ROOT / 'tests/learning_examples.json').read_text())
|
||||
assert len(examples) == 10, 'Introductory example coverage changed'
|
||||
sources += [('examples/' + name, (ROOT / 'examples' / name).read_text()) for name in examples]
|
||||
for name, code in sources:
|
||||
found = LEGACY.search(code)
|
||||
assert not found, f'{name}: introductory syntax must use short ASK/SET, TEACH/DO, and END; found {found.group(0)!r}'
|
||||
print('PASS: introductory syntax policy')
|
||||
with tempfile.TemporaryDirectory(prefix='claro-learning-', dir=os.environ.get('TMPDIR')) as tmp:
|
||||
work = Path(tmp)
|
||||
modern_function = blocks(ROOT / 'docs/FIRST_HOUR.md')[7]
|
||||
function_script = work / 'modern-function.claro'
|
||||
function_script.write_text(modern_function + '\n')
|
||||
for command in ['check', 'typecheck']:
|
||||
result = subprocess.run([str(EXE), command, str(function_script)], cwd=work, text=True, capture_output=True, timeout=10)
|
||||
assert result.returncode == 0, ('modern multi-argument function', command, result.stdout, result.stderr)
|
||||
print('PASS: modern function accepted by', command)
|
||||
mistakes = [
|
||||
('SET', 'SET\n', 'Try: SET name "Alex"'),
|
||||
('IF', 'IF YES\n SAY "yes"\n', 'IF block opened here needs END'),
|
||||
('TEACH', 'TEACH greet name\n SAY name\n', 'TEACH block opened here needs END'),
|
||||
('DO', 'DO 2 TIMES\n SAY "practice"\n', 'DO block opened here needs END'),
|
||||
('FOR', 'FOR i FROM 1 TO 3\n SAY i\n', 'FOR block opened here needs END'),
|
||||
('REPEAT', 'REPEAT\n SAY "practice"\n', 'REPEAT block opened here needs UNTIL'),
|
||||
]
|
||||
for label, code, hint in mistakes:
|
||||
script = work / 'mistake.claro'
|
||||
script.write_text(code)
|
||||
result = subprocess.run([str(EXE), 'check', str(script)], cwd=work, text=True, capture_output=True, timeout=10)
|
||||
assert result.returncode != 0 and hint in result.stdout, (label, result.returncode, result.stdout)
|
||||
assert not re.search(r'needs (?:ENDIF|DONE|LEARNED)|SET name TO', result.stdout), result.stdout
|
||||
print('PASS: beginner repair hint for', label)
|
||||
check_run(work, 'quoted multi-argument function', 'TEACH greet first last\n RETURN first + last\nEND\nDO greet "Hello " "Ada"\nSAY RESULT', '', 'Hello Ada\n')
|
||||
check_run(work, 'bare container constructors', 'SET items LIST\nSET pairs MAP\nSAY items\nSAY pairs', '', '[list]\n[map]\n')
|
||||
for name, (input_text, expected) in LESSONS.items():
|
||||
check_run(work, name, (ROOT / 'lessons' / name).read_text(), input_text, expected)
|
||||
check_run(work, 'quiz wrong answer', (ROOT / 'lessons/10_final_quiz.claro').read_text(), '3\n', 'Lesson 10: Tiny Quiz\nWhat is 2 + 2?\nThe answer is 4.\n')
|
||||
quick = blocks(ROOT / 'docs/QUICK_START.md')
|
||||
assert len(quick) == len(QUICK), 'Quick Start block coverage changed'
|
||||
for number, (code, (input_text, expected)) in enumerate(zip(quick, QUICK), 1):
|
||||
check_run(work, f'Quick Start block {number}', code, input_text, expected)
|
||||
functions = blocks(ROOT / 'docs/SIMPLE_FUNCTIONS.md')
|
||||
assert len(functions) == 1, 'Function guide should teach one initial form'
|
||||
check_run(work, 'simple function guide', functions[0], '', 'Hello Jon\n')
|
||||
older = blocks(ROOT / 'docs/COMPATIBILITY.md')
|
||||
assert len(older) == 1, 'Compatibility guide coverage changed'
|
||||
compatibility = older[0]
|
||||
check_run(work, 'legacy syntax remains supported', compatibility, '4\n', '5\nNumber?\n8\n8\n')
|
||||
for name, (input_text, expected) in examples.items():
|
||||
check_run(work, 'examples/' + name, (ROOT / 'examples' / name).read_text(), input_text, expected)
|
||||
result = subprocess.run([str(EXE), 'new', 'Starter'], cwd=work, text=True, capture_output=True, timeout=10)
|
||||
assert result.returncode == 0, result.stderr
|
||||
starter = (work / 'Starter/main.claro').read_text()
|
||||
assert not LEGACY.search(starter), 'Generated starter still teaches older syntax'
|
||||
check_run(work, 'generated starter', starter, '', 'Welcome to Claro!\nHello Learner\nNice to meet you, Friend\n')
|
||||
repl = subprocess.run([str(EXE), 'repl'], cwd=work, input='HELP\nEXIT\n', text=True, capture_output=True, timeout=10)
|
||||
assert repl.returncode == 0 and 'SET name "Jon"' in repl.stdout and 'SET name TO' not in repl.stdout, repl.stdout
|
||||
metadata = json.loads(subprocess.check_output([str(EXE), 'ide'], cwd=work, text=True))
|
||||
snippets = {item['name']: item['body'] for item in metadata['snippets']}
|
||||
for name in ['hello', 'if_else', 'function']:
|
||||
assert not LEGACY.search(snippets[name]), ('IDE snippet still teaches older syntax', name)
|
||||
check_run(work, 'IDE choice snippet', 'SET condition YES\n' + snippets['if_else'], '', 'Yes\n')
|
||||
check_run(work, 'IDE function snippet', snippets['function'] + '\nDO name "Ada"', '', 'Ada\n')
|
||||
print('PASS: REPL help and beginner IDE snippets')
|
||||
print('Learning syntax validation complete')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Prevent VC6 handle/Win98 build-setting regressions without needing the SDK."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def main():
|
||||
source = (ROOT / 'src/claro.c').read_text()
|
||||
start = source.index('static int run_tests(')
|
||||
body = source[start:source.index('static int ', start + 20)]
|
||||
branches = re.search(r'#if defined\(_MSC_VER\) && _MSC_VER < 1300\s+(.*?)#else\s+(.*?)#endif\s+h = _findfirst', body, re.S)
|
||||
assert branches, 'Old MSVC needs its own long _findfirst handle before calling the CRT'
|
||||
legacy = re.sub(r'/\*.*?\*/', '', branches[1], flags=re.S)
|
||||
assert re.search(r'\blong h;', legacy) and 'intptr_t' not in legacy
|
||||
assert re.search(r'\bintptr_t h;', branches[2]), 'Preserve the modern pointer-sized handle'
|
||||
build = (ROOT / 'build98.bat').read_text()
|
||||
response = ROOT / 'build98.rsp'
|
||||
options = build + (response.read_text() if response.exists() else '')
|
||||
for option in ['/TC', '/ML', '/MACHINE:I386', '/SUBSYSTEM:CONSOLE,4.10', '/STACK:16777216']:
|
||||
assert option in options, ('Missing Windows 98 build setting', option)
|
||||
assert max(map(len, build.splitlines())) < 127, 'Keep COMMAND.COM command lines below its legacy length limit'
|
||||
assert '/Dintptr_t' not in options, 'Fix source rather than injecting a global type macro'
|
||||
for name in ['build98.bat', 'build98.rsp']:
|
||||
data = (ROOT / name).read_bytes()
|
||||
assert b'\r\n' in data and b'\n' not in data.replace(b'\r\n', b''), ('Use DOS line endings', name)
|
||||
assert len(Path(name).stem) <= 8
|
||||
assert 'cl @build98.rsp' in build
|
||||
assert not re.search(r'(?i)setlocal|exit /b|%~|\bwhere\b|&&|\|\|', build), 'Use COMMAND.COM-compatible batch syntax'
|
||||
assert 'build98.bat' in (ROOT / 'tools/build_distribution.py').read_text(), 'Include the legacy build script in source distributions'
|
||||
print('PASS: guarded VC6 CRT handle, modern handle, Win98 build settings and source packaging')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deep control blocks must produce diagnostics, not checker/CPU stack overflows."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def main():
|
||||
if os.name == 'nt':
|
||||
print('SKIP: current nesting sanitizer build requires POSIX')
|
||||
return
|
||||
with tempfile.TemporaryDirectory(prefix='claro-nesting-', dir=os.environ.get('TMPDIR')) as tmp:
|
||||
work = Path(tmp)
|
||||
exe = work / 'claro-asan'
|
||||
subprocess.run(['gcc', '-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-O0', '-g', '-fsanitize=address,undefined', str(ROOT / 'src/claro.c'), '-o', str(exe), '-lm'], check=True)
|
||||
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=1:halt_on_error=1', UBSAN_OPTIONS='halt_on_error=1')
|
||||
for depth, expected in [(64, 0), (600, 1)]:
|
||||
script = work / 'nested.claro'
|
||||
script.write_text('IF YES\n' * depth + 'SAY "OK"\n' + 'END\n' * depth)
|
||||
for command in ['check', 'run']:
|
||||
run = subprocess.run([str(exe), command, str(script)], cwd=work, env=env, capture_output=True, text=True, timeout=15)
|
||||
combined = run.stdout + run.stderr
|
||||
assert run.returncode == expected, (depth, command, run.returncode, combined[-2000:])
|
||||
assert not any(s in combined for s in ['AddressSanitizer', 'LeakSanitizer', 'runtime error:']), combined
|
||||
if expected:
|
||||
assert 'block nesting' in combined.lower(), (command, combined)
|
||||
else:
|
||||
assert run.stdout == ('OK\n' if command == 'run' else 'OK\n'), (command, combined)
|
||||
print('PASS:', command, 'block depth', depth)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check OBJECT CLASS/FIELDS release evaluated values under sanitizers."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if os.name == "nt":
|
||||
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||
return 0
|
||||
with tempfile.TemporaryDirectory(prefix="object-expression-cleanup-") as tmp:
|
||||
binary = Path(tmp) / "claro-asan"
|
||||
build = subprocess.run(
|
||||
["gcc", "-std=c89", "-pedantic-errors", "-Werror=implicit-function-declaration", "-O0", "-g", "-fsanitize=address,undefined",
|
||||
"src/claro.c", "-o", str(binary), "-lm"],
|
||||
cwd=ROOT, text=True, capture_output=True,
|
||||
)
|
||||
if build.returncode:
|
||||
print(build.stdout, end="")
|
||||
print(build.stderr, end="", file=sys.stderr)
|
||||
return build.returncode
|
||||
script = Path(tmp) / "object-expressions.claro"
|
||||
script.write_text(
|
||||
"CLASS Person\nHAS name TEXT\nEND\nNEW Person person\n"
|
||||
"DO 2000 TIMES\nOBJECT CLASS person AS kind\nOBJECT FIELDS person AS fields\nEND\n"
|
||||
"SAY kind\nCOUNT fields AS count\nGET fields AT 1 AS first\nSAY count\nSAY first\n", encoding="utf-8"
|
||||
)
|
||||
run = subprocess.run(
|
||||
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||
)
|
||||
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||
print("FAIL: repeated OBJECT CLASS/FIELDS retains expression allocations")
|
||||
print(run.stdout, end="")
|
||||
print(run.stderr, end="", file=sys.stderr)
|
||||
return 1
|
||||
if run.stdout != "Person\n1\nname\n":
|
||||
print(f"FAIL: unexpected output: {run.stdout!r}")
|
||||
return 1
|
||||
print("PASS: repeated OBJECT CLASS/FIELDS is leak-free and preserves results")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EXE = ROOT / ("claro.exe" if os.name == "nt" else "claro")
|
||||
EXPECTED_VERSION = "Claro v2026.10.20.160950"
|
||||
EXPECTED_VERSION = "Claro v2026.10.20.223203"
|
||||
|
||||
|
||||
def fail(message):
|
||||
@@ -57,11 +57,11 @@ def main():
|
||||
fail(out)
|
||||
starter = work / "StarterApp"
|
||||
starter_project = read(starter / "claro.project")
|
||||
for phrase in ["manifest-version: 1", "name: StarterApp", "main: main.claro", "version: v2026.10.20.160950", "packages:"]:
|
||||
for phrase in ["manifest-version: 1", "name: StarterApp", "main: main.claro", "version: v2026.10.20.223203", "packages:"]:
|
||||
if phrase not in starter_project:
|
||||
fail(f"claro new project file missing {phrase!r}:\n{starter_project}")
|
||||
starter_lock = read(starter / "claro.lock")
|
||||
for phrase in ["lock-version: 1", "version: v2026.10.20.160950"]:
|
||||
for phrase in ["lock-version: 1", "version: v2026.10.20.223203"]:
|
||||
if phrase not in starter_lock:
|
||||
fail(f"claro new lockfile missing {phrase!r}:\n{starter_lock}")
|
||||
|
||||
@@ -71,7 +71,7 @@ def main():
|
||||
if rc != 0:
|
||||
fail(out)
|
||||
project = read(work / "claro.project")
|
||||
for phrase in ["manifest-version: 1", "name: ClaroProject", "main: main.claro", "version: v2026.10.20.160950", "packages:"]:
|
||||
for phrase in ["manifest-version: 1", "name: ClaroProject", "main: main.claro", "version: v2026.10.20.223203", "packages:"]:
|
||||
if phrase not in project:
|
||||
fail(f"claro.project missing {phrase!r}:\n{project}")
|
||||
|
||||
@@ -100,7 +100,7 @@ def main():
|
||||
fail(f"Package checksum should be a stable non-placeholder value, got {checksum!r}")
|
||||
|
||||
lock = read(work / "claro.lock")
|
||||
for phrase in ["lock-version: 1", "version: v2026.10.20.160950", "package: math-tools", "checksum:"]:
|
||||
for phrase in ["lock-version: 1", "version: v2026.10.20.223203", "package: math-tools", "checksum:"]:
|
||||
if phrase not in lock:
|
||||
fail(f"claro.lock missing {phrase!r}:\n{lock}")
|
||||
|
||||
@@ -127,12 +127,12 @@ def main():
|
||||
fail(f"Lock format-version diagnostic was unclear:\n{out}")
|
||||
if read(work / "claro.lock") != invalid_lock:
|
||||
fail("package doctor must not rewrite an invalid lock format version")
|
||||
invalid_release_lock = lock.replace("version: v2026.10.20.160950", "version: v1.18.25", 1)
|
||||
invalid_release_lock = lock.replace("version: v2026.10.20.223203", "version: v1.18.25", 1)
|
||||
(work / "claro.lock").write_text(invalid_release_lock, encoding="utf-8")
|
||||
rc, out = run([str(EXE), "package", "doctor"], work)
|
||||
if rc == 0:
|
||||
fail("package doctor must reject a lockfile for a different Claro release")
|
||||
if "BAD lock release version: expected exactly one version: v2026.10.20.160950 line" not in out:
|
||||
if "BAD lock release version: expected exactly one version: v2026.10.20.223203 line" not in out:
|
||||
fail(f"Lock release-version diagnostic was unclear:\n{out}")
|
||||
if read(work / "claro.lock") != invalid_release_lock:
|
||||
fail("package doctor must not rewrite an invalid lock release version")
|
||||
@@ -343,7 +343,7 @@ def main():
|
||||
fail("Long package name should not create a package folder")
|
||||
|
||||
(work / "claro.project").write_text(
|
||||
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.160950\npackages:\npackage: ../bad\n",
|
||||
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.223203\npackages:\npackage: ../bad\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
rc, out = run([str(EXE), "package", "doctor"], work)
|
||||
@@ -382,7 +382,7 @@ def main():
|
||||
fail("package add should not create a package folder while unsafe names remain in claro.project")
|
||||
|
||||
(work / "claro.project").write_text(
|
||||
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.160950\npackages:\npackage: math-tools\npackage: ../bad\n",
|
||||
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.20.223203\npackages:\npackage: math-tools\npackage: ../bad\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
rc, out = run([str(EXE), "package", "remove", "math-tools"], work)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Bounded malformed-expression regressions; sanitizer runs include leak checks."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DRIVER = r'''#define main claro_cli_main
|
||||
#include "claro.c"
|
||||
#undef main
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
Runtime rt;
|
||||
Value result;
|
||||
int status;
|
||||
if(argc==3 && strcmp(argv[1],"check")==0) return check_file(argv[2]);
|
||||
if(argc!=2) return 2;
|
||||
rt_init(&rt);
|
||||
result=eval_expr(&rt,argv[1]);
|
||||
value_free(result);
|
||||
status=rt.error?1:0;
|
||||
if(rt.error) fprintf(stderr,"%s\n",rt.err_msg);
|
||||
runtime_free(&rt);
|
||||
return status;
|
||||
}
|
||||
'''
|
||||
CASES = [
|
||||
('!', 'NOT'),
|
||||
('(1 + 2', 'closing'),
|
||||
('1 +', 'value'),
|
||||
('"unfinished', 'quote'),
|
||||
('1 2', 'Unexpected'),
|
||||
('(' * 600 + '1' + ')' * 600, 'nesting'),
|
||||
('NOT ' * 600 + 'YES', 'nesting'),
|
||||
]
|
||||
|
||||
|
||||
def limited():
|
||||
import resource
|
||||
resource.setrlimit(resource.RLIMIT_AS, (128 * 1024 * 1024, 128 * 1024 * 1024))
|
||||
|
||||
|
||||
def main():
|
||||
if os.name == 'nt':
|
||||
print('SKIP: current parser sanitizer driver requires POSIX')
|
||||
return
|
||||
with tempfile.TemporaryDirectory(prefix='claro-parse-', dir=os.environ.get('TMPDIR')) as tmp:
|
||||
work = Path(tmp)
|
||||
(work / 'claro.c').write_bytes((ROOT / 'src/claro.c').read_bytes())
|
||||
driver = work / 'parse.c'
|
||||
driver.write_text(DRIVER)
|
||||
flags = ['gcc', '-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-O0', '-g']
|
||||
plain, sanitized = work / 'parse', work / 'parse-asan'
|
||||
subprocess.run(flags + [str(driver), '-o', str(plain), '-lm'], check=True)
|
||||
for expression, expected in CASES:
|
||||
run = subprocess.run([str(plain), expression], cwd=work, capture_output=True, text=True, timeout=5, preexec_fn=limited)
|
||||
assert run.returncode == 1 and expected in run.stderr, (expression[:50], run.returncode, run.stderr[-1000:])
|
||||
subprocess.run(flags + ['-fsanitize=address,undefined', str(driver), '-o', str(sanitized), '-lm'], check=True)
|
||||
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=1:halt_on_error=1', UBSAN_OPTIONS='halt_on_error=1')
|
||||
for expression, expected in CASES:
|
||||
run = subprocess.run([str(sanitized), expression], cwd=work, env=env, capture_output=True, text=True, timeout=10)
|
||||
assert run.returncode == 1 and expected in run.stderr, (expression[:50], run.returncode, run.stderr[-1000:])
|
||||
assert not any(marker in run.stderr for marker in ['AddressSanitizer', 'LeakSanitizer', 'runtime error:']), run.stderr
|
||||
for expression, expected in CASES[:1] + CASES[3:4]:
|
||||
script = work / 'bad.claro'
|
||||
script.write_text('SAY ' + expression + '\n')
|
||||
run = subprocess.run([str(sanitized), 'check', str(script)], cwd=work, env=env, capture_output=True, text=True, timeout=10)
|
||||
assert run.returncode == 1 and expected in run.stdout, ('check', expression, run.returncode, run.stdout, run.stderr[-1000:])
|
||||
assert not run.stderr, run.stderr
|
||||
for expression in ['(1 + 2) * 3', '(' * 64 + '1' + ')' * 64, 'NOT ' * 64 + 'YES', '"a" + "b"', '1 != 2']:
|
||||
run = subprocess.run([str(sanitized), expression], cwd=work, env=env, capture_output=True, text=True, timeout=10)
|
||||
assert run.returncode == 0 and not run.stderr, (expression[:50], run.returncode, run.stderr[-1000:])
|
||||
print('PASS: seven malformed expressions and five valid expressions terminate cleanly under ASan/UBSan/LSan')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the maintained ownership, malformed-input, nesting and safety regressions."""
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EXTRA = ['validate_learning_ownership.py', 'validate_value_ownership.py', 'validate_call_depth_guard.py',
|
||||
'validate_nesting_guards.py', 'validate_http_hardening.py',
|
||||
'validate_trusted_command_docs.py']
|
||||
|
||||
|
||||
def main():
|
||||
scripts = sorted((ROOT / 'tools').glob('validate_*cleanup.py'))
|
||||
scripts += [ROOT / 'tools' / name for name in EXTRA]
|
||||
assert len(scripts) == len(set(scripts)), 'Duplicate reliability checks'
|
||||
assert all(path.is_file() and path.stat().st_size for path in scripts), 'Missing or empty reliability check'
|
||||
logs = Path(tempfile.mkdtemp(prefix='claro-reliability-', dir=os.environ.get('TMPDIR')))
|
||||
|
||||
def run(path):
|
||||
try:
|
||||
result = subprocess.run([sys.executable, str(path)], cwd=ROOT, text=True, capture_output=True, timeout=180)
|
||||
output = result.stdout + result.stderr
|
||||
status = result.returncode
|
||||
except subprocess.TimeoutExpired as error:
|
||||
output = f'TIMEOUT: {error}'
|
||||
status = 124
|
||||
log = logs / (path.stem + '.log')
|
||||
log.write_text(output, encoding='utf-8')
|
||||
return {'test': path.name, 'exit': status, 'log': str(log), 'skipped': output.startswith('SKIP:')}
|
||||
|
||||
with ThreadPoolExecutor(max_workers=4) as pool:
|
||||
results = list(pool.map(run, scripts))
|
||||
(logs / 'results.json').write_text(json.dumps(results, indent=2), encoding='utf-8')
|
||||
for result in results:
|
||||
status = 'FAIL' if result['exit'] else ('SKIP' if result['skipped'] else 'PASS')
|
||||
print(status + ': ' + result['test'])
|
||||
if result['exit']:
|
||||
print(Path(result['log']).read_text()[-6000:])
|
||||
failed = sum(result['exit'] != 0 for result in results)
|
||||
skipped = sum(result['skipped'] for result in results)
|
||||
print(f'Reliability checks: {len(results)} total, {failed} failed, {skipped} skipped')
|
||||
print('Results:', logs / 'results.json')
|
||||
return 1 if failed else 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
@@ -43,4 +43,4 @@ with tempfile.TemporaryDirectory() as td:
|
||||
run([str(local), "package", "remove", "text"], cwd=proj)
|
||||
run([str(local), "run"], cwd=proj)
|
||||
|
||||
print("Validation passed. Claro v2026.10.20.160950 networking reliability is ready for use.")
|
||||
print("Validation passed. Claro v2026.10.20.223203 networking reliability is ready for use.")
|
||||
|
||||
@@ -38,26 +38,24 @@ def build():
|
||||
def main():
|
||||
build()
|
||||
run([sys.executable, "tools/validate_c89.py"])
|
||||
run([sys.executable, "tools/validate_msvc6_source.py"])
|
||||
out = run([str(EXE), "--version"])
|
||||
if "Claro v2026.10.20.160950" not in out:
|
||||
if "Claro v2026.10.20.223203" not in out:
|
||||
raise SystemExit("Wrong version string")
|
||||
run([str(EXE), "test"])
|
||||
run([str(EXE), "validate"])
|
||||
run([sys.executable, "tools/validate_first_hour.py"])
|
||||
run([sys.executable, "tools/validate_learning_syntax.py"])
|
||||
run([sys.executable, "tools/validate_ide_metadata.py"])
|
||||
run([sys.executable, "tools/validate_lsp_helper.py"])
|
||||
run([sys.executable, "tools/validate_concurrency.py"])
|
||||
run([sys.executable, "tools/validate_package_security.py"])
|
||||
run([sys.executable, "tools/validate_typecheck_diagnostics.py"])
|
||||
run([sys.executable, "tools/validate_runtime_error_cleanup.py"])
|
||||
run([sys.executable, "tools/validate_get_argument_expression_cleanup.py"])
|
||||
run([sys.executable, "tools/validate_get_env_expression_cleanup.py"])
|
||||
run([sys.executable, "tools/validate_get_at_expression_cleanup.py"])
|
||||
run([sys.executable, "tools/validate_read_file_expression_cleanup.py"])
|
||||
run([sys.executable, "tools/validate_write_file_expression_cleanup.py"])
|
||||
run([sys.executable, "tools/validate_reliability.py"])
|
||||
run([sys.executable, "tools/validate_version_convention.py"])
|
||||
run([sys.executable, "tools/validate_documentation_status.py"])
|
||||
run([str(EXE), "examples/networking.claro"])
|
||||
print("v2026.10.20.160950 validation complete")
|
||||
print("v2026.10.20.223203 validation complete")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Isolate borrowed metadata and owned object temporaries with ASan/UBSan/LSan."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DRIVER = r'''#define main claro_cli_main
|
||||
#include "claro.c"
|
||||
#undef main
|
||||
#include <assert.h>
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
Runtime rt;
|
||||
Value obj,text;
|
||||
ClassDef *cls;
|
||||
int i,which;
|
||||
const char *name;
|
||||
char typebuf[320];
|
||||
assert(argc==2);
|
||||
which=atoi(argv[1]);
|
||||
rt_init(&rt);
|
||||
if(which==1){
|
||||
for(i=0;i<2000;i++) assert(rt_set_checked(&rt,"probe",1,"score","NUMBER",v_num(i)));
|
||||
} else if(which==2){
|
||||
text=v_str("NUMBER"); rt_set(&rt,"__type_score",text); value_free(text);
|
||||
for(i=0;i<2000;i++){
|
||||
name=runtime_type_for(&rt,"score",typebuf,sizeof(typebuf));
|
||||
assert(name && strcmp(name,"NUMBER")==0);
|
||||
}
|
||||
} else if(which==3){
|
||||
obj=v_map(); text=v_str("Person"); map_put(obj.map,"class",text); value_free(text);
|
||||
for(i=0;i<2000;i++){
|
||||
name=object_class_name(obj); assert(name && strcmp(name,"Person")==0);
|
||||
}
|
||||
value_free(obj);
|
||||
} else if(which==4){
|
||||
obj=v_map(); rt_set(&rt,"person",obj); value_free(obj);
|
||||
for(i=0;i<2000;i++) assert(rt_set_checked(&rt,"probe",1,"person.score","NUMBER",v_num(i)));
|
||||
} else if(which==5){
|
||||
cls=add_class(&rt,"Person"); class_add_field(cls,"name","TEXT");
|
||||
for(i=0;i<2000;i++) create_object_value(&rt,"probe",1,"Person","person");
|
||||
} else return 2;
|
||||
assert(!rt.error);
|
||||
runtime_free(&rt);
|
||||
return 0;
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def main():
|
||||
if os.name == 'nt':
|
||||
print('SKIP: LeakSanitizer ownership checks require POSIX')
|
||||
return
|
||||
failures = []
|
||||
with tempfile.TemporaryDirectory(prefix='claro-ownership-', dir=os.environ.get('TMPDIR')) as tmp:
|
||||
work = Path(tmp)
|
||||
(work / 'claro.c').write_bytes((ROOT / 'src/claro.c').read_bytes())
|
||||
driver = work / 'ownership.c'
|
||||
driver.write_text(DRIVER)
|
||||
binary = work / 'ownership'
|
||||
subprocess.run(['gcc', '-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-g', '-O0', '-fsanitize=address,undefined', str(driver), '-o', str(binary), '-lm'], check=True)
|
||||
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=1:halt_on_error=1', UBSAN_OPTIONS='halt_on_error=1')
|
||||
for number, name in enumerate(['checked type storage', 'runtime type metadata', 'object class metadata', 'object field copies', 'object construction'], 1):
|
||||
run = subprocess.run([str(binary), str(number)], cwd=work, env=env, capture_output=True, text=True, timeout=20)
|
||||
if run.returncode or run.stderr:
|
||||
failures.append(name)
|
||||
print('FAIL:', name, run.stderr[-3500:])
|
||||
else:
|
||||
print('PASS:', name)
|
||||
if failures:
|
||||
raise SystemExit('Ownership failures: ' + ', '.join(failures))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
EXE = ROOT / "claro.exe"
|
||||
EXPECTED = "Claro v2026.10.20.160950"
|
||||
EXPECTED = "Claro v2026.10.20.223203"
|
||||
|
||||
|
||||
def fail(message):
|
||||
@@ -31,12 +31,12 @@ def main():
|
||||
if ide.returncode != 0:
|
||||
fail(ide.stdout)
|
||||
metadata = json.loads(ide.stdout)
|
||||
if metadata.get("version") != "v2026.10.20.160950":
|
||||
fail(f"Expected IDE metadata version 'v2026.10.20.160950', got {metadata.get('version')!r}")
|
||||
if metadata.get("version") != "v2026.10.20.223203":
|
||||
fail(f"Expected IDE metadata version 'v2026.10.20.223203', got {metadata.get('version')!r}")
|
||||
validator = ROOT / "tools" / "validate_v1_17.py"
|
||||
text = validator.read_text(encoding="utf-8")
|
||||
if "Claro v2026.10.20.160950" not in text:
|
||||
fail("tools/validate_v1_17.py must validate Claro v2026.10.20.160950")
|
||||
if "Claro v2026.10.20.223203" not in text:
|
||||
fail("tools/validate_v1_17.py must validate Claro v2026.10.20.223203")
|
||||
print("Version convention validation OK")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user