fix: harden Claro diagnostics and runtime guards
Preserve source locations, validate malformed JSON, and add opt-in resource limits. Include executable error-guide coverage, learner pilot materials, native Windows acceptance helpers, and updated CI/documentation.
This commit is contained in:
@@ -35,6 +35,12 @@ jobs:
|
||||
run: python3 tools/validate_c89.py
|
||||
- name: Validate executable learning path
|
||||
run: python3 tools/validate_learning_syntax.py
|
||||
- name: Validate diagnostics, error guide and learner pilot code
|
||||
run: |
|
||||
python3 tools/validate_error_locations.py
|
||||
python3 tools/validate_error_guide.py
|
||||
python3 tools/validate_learner_pilot.py
|
||||
python3 tools/validate_resource_guards.py
|
||||
- name: Validate memory and malformed-input reliability
|
||||
run: python3 tools/validate_reliability.py
|
||||
- name: Validate CI workflow coverage
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 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
|
||||
test98.bat -text whitespace=cr-at-eol
|
||||
testwin.bat -text whitespace=cr-at-eol
|
||||
|
||||
@@ -59,6 +59,18 @@ 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).
|
||||
|
||||
|
||||
## Learning-first charter
|
||||
|
||||
Claro is a learning tool first and a general-purpose language second. Keep the
|
||||
core small, predictable, and beginner-focused; prioritize executable lessons,
|
||||
useful diagnostics, and reliability over feature breadth. Teach one recommended
|
||||
spelling per concept while preserving supported compatibility forms. Do not
|
||||
claim accessibility validation without feedback from intended learners. The
|
||||
[learning-first charter](docs/LEARNING_FIRST.md) is the current product guidance.
|
||||
Older ambitious plans are retained as historical context, not active priorities;
|
||||
they do not imply that existing features are removed. See the current
|
||||
[learning-first roadmap](docs/ROADMAP.md).
|
||||
|
||||
## Beginner syntax consistency
|
||||
|
||||
Follow [the syntax guide](docs/SYNTAX.md) in introductory teaching material.
|
||||
|
||||
@@ -8,7 +8,7 @@ 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.
|
||||
|
||||
Development version: **v2026.10.20.223203**
|
||||
Development version: **v2026.10.25.014405**
|
||||
|
||||
This is a development snapshot, not a newly published release. See
|
||||
[current status](docs/CURRENT_STATUS.md) and the dated
|
||||
@@ -51,6 +51,7 @@ concept. Older forms remain supported in a separate compatibility reference.
|
||||
- [Documentation](docs/README.md) — learning guides and reference.
|
||||
- [Current roadmap](docs/ROADMAP.md) — the small set of active priorities.
|
||||
- [Learning-first rules](docs/LEARNING_FIRST.md) — how Claro is designed.
|
||||
- [Active learning-first roadmap](docs/ROADMAP.md) — current priorities; older platform plans are historical context.
|
||||
- [Contributing](CONTRIBUTING.md) — source layout, builds, and validation.
|
||||
|
||||
Existing features and compatibility syntax remain supported; they are not
|
||||
|
||||
@@ -5,6 +5,9 @@ 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.
|
||||
|
||||
The [ready-to-run pilot kit](LEARNER_PILOT.md) includes short tasks and a
|
||||
[blank feedback form](LEARNER_FEEDBACK_TEMPLATE.md). No real session is implied.
|
||||
|
||||
## Before a session
|
||||
|
||||
- Use a build whose platform was actually tested; check `claro --version`.
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Claro Command Line
|
||||
|
||||
Claro v2026.10.20.223203 supports these main commands:
|
||||
Claro v2026.10.25.014405 supports these main commands:
|
||||
|
||||
```bash
|
||||
claro file.claro
|
||||
@@ -58,7 +58,7 @@ claro package doctor
|
||||
claro package lock
|
||||
```
|
||||
|
||||
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.
|
||||
The v2026.10.25.014405 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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Claro Complete Platform Roadmap
|
||||
|
||||
Current release: **Claro v2026.10.20.223203**
|
||||
Current release: **Claro v2026.10.25.014405**
|
||||
|
||||
This roadmap tracks the larger goal: Claro as a complete beginner-friendly programming platform, not only a toy scripting interpreter.
|
||||
|
||||
|
||||
+10
-6
@@ -1,6 +1,6 @@
|
||||
# Claro current status
|
||||
|
||||
Development version: **v2026.10.20.223203**
|
||||
Development version: **v2026.10.25.014405**
|
||||
|
||||
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.
|
||||
@@ -28,8 +28,9 @@ call depth and the learning path under leak-enabled sanitizers. See
|
||||
Earlier failure logs are history, not current unresolved findings.
|
||||
|
||||
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).
|
||||
Loops can run indefinitely by default. Opt-in step and READ FILE budgets are
|
||||
available; parsed JSON nesting is capped. Global file/output/memory and CPU-time
|
||||
budgets are not provided. See [actual runtime guards](RUNTIME_LIMITS.md).
|
||||
|
||||
## Platforms — do not confuse compilation with OS acceptance
|
||||
|
||||
@@ -55,9 +56,12 @@ They are **not active expansion priorities** without a separate request.
|
||||
|
||||
## What is next
|
||||
|
||||
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.
|
||||
1. Use the [learner pilot kit](LEARNER_PILOT.md) with a willing person; code checks
|
||||
are not human feedback. Source-located expression diagnostics now have regressions.
|
||||
2. Use the [native Windows kit](NATIVE_WINDOWS_TESTING.md) on an authorized target.
|
||||
Wine is explicitly rejected by its OS evidence helper.
|
||||
3. Hosted CI/release delivery needs authenticated Gitea access and enabled Actions.
|
||||
See the [six-track status](SIX_TRACK_STATUS.md) for current blockers.
|
||||
|
||||
Follow the [small active roadmap](ROADMAP.md). The
|
||||
[previous detailed status](history/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md) and
|
||||
|
||||
+127
-12
@@ -1,29 +1,144 @@
|
||||
# Claro Errors
|
||||
# Errors and small repairs
|
||||
|
||||
Claro tries to report mistakes in beginner-friendly language.
|
||||
An error starts with `filename:line:`. Open that file and find that line.
|
||||
The line belongs to the function or imported file where the error happened,
|
||||
not automatically to the caller. Caught errors expose the same origin through
|
||||
`LASTERRORFILE` and `LASTERRORLINE`.
|
||||
|
||||
Examples:
|
||||
Use `claro check my_program.claro` for command/block mistakes; running the
|
||||
program also detects runtime expression/value errors. A checker is not a sandbox.
|
||||
The examples below are executed by `tools/validate_error_guide.py`.
|
||||
|
||||
## Quote
|
||||
|
||||
Save as `quote.claro`. Run `claro quote.claro`.
|
||||
|
||||
```claro
|
||||
SAY "Start"
|
||||
SAY "unfinished
|
||||
```
|
||||
|
||||
Message:
|
||||
|
||||
```text
|
||||
I do not know the command "PRNT". Did you mean "SAY"?
|
||||
quote.claro:2: Text is missing a closing quote. Add a double quote at the end.
|
||||
```
|
||||
|
||||
One repair:
|
||||
|
||||
```claro
|
||||
SAY "Start"
|
||||
SAY "finished"
|
||||
```
|
||||
|
||||
## Operand
|
||||
|
||||
Save as `operand.claro`. Run `claro operand.claro`.
|
||||
|
||||
```claro
|
||||
SET score 1
|
||||
SAY score +
|
||||
```
|
||||
|
||||
Message:
|
||||
|
||||
```text
|
||||
SET needs TO.
|
||||
Try:
|
||||
SET name TO "Alex"
|
||||
operand.claro:2: This expression needs a value after the operator.
|
||||
```
|
||||
|
||||
One repair:
|
||||
|
||||
```claro
|
||||
SET score 1
|
||||
SAY score + 1
|
||||
```
|
||||
|
||||
## Command
|
||||
|
||||
Save as `command.claro`. Run `claro check command.claro`.
|
||||
|
||||
```claro
|
||||
PRNT "Hello"
|
||||
```
|
||||
|
||||
Message:
|
||||
|
||||
```text
|
||||
IF block opened here is missing ENDIF
|
||||
command.claro:1: I do not know the command "PRNT". Did you mean "SAY"?
|
||||
```
|
||||
|
||||
One repair:
|
||||
|
||||
```claro
|
||||
SAY "Hello"
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
Save as `assignment.claro`. Run `claro check assignment.claro`.
|
||||
|
||||
```claro
|
||||
SET name
|
||||
```
|
||||
|
||||
Message:
|
||||
|
||||
```text
|
||||
I do not know the variable "socre" yet. Did you mean "score"?
|
||||
assignment.claro:1: SET needs a name and a value. Try: SET name "Alex"
|
||||
```
|
||||
|
||||
For safer learning, run this before running a new program:
|
||||
One repair:
|
||||
|
||||
```bash
|
||||
./claro check my_program.claro
|
||||
```claro
|
||||
SET name "Alex"
|
||||
SAY name
|
||||
```
|
||||
|
||||
## Choice
|
||||
|
||||
Save as `choice.claro`. Run `claro check choice.claro`.
|
||||
|
||||
```claro
|
||||
IF YES
|
||||
SAY "Yes"
|
||||
```
|
||||
|
||||
Message:
|
||||
|
||||
```text
|
||||
choice.claro:1: IF block opened here needs END
|
||||
```
|
||||
|
||||
One repair:
|
||||
|
||||
```claro
|
||||
IF YES
|
||||
SAY "Yes"
|
||||
END
|
||||
```
|
||||
|
||||
## Variable
|
||||
|
||||
Save as `variable.claro`. Run `claro variable.claro`.
|
||||
|
||||
```claro
|
||||
SET score 10
|
||||
SAY socre
|
||||
```
|
||||
|
||||
Message:
|
||||
|
||||
```text
|
||||
variable.claro:2: I do not know the variable "socre" yet.
|
||||
```
|
||||
|
||||
One repair:
|
||||
|
||||
```claro
|
||||
SET score 10
|
||||
SAY score
|
||||
```
|
||||
|
||||
These teach short `SET` and `END`. Older forms remain supported in
|
||||
[compatibility syntax](COMPATIBILITY.md), not required as repairs.
|
||||
For a caught error, read its metadata before another error changes it.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Claro Graphics Status
|
||||
|
||||
Current release: **Claro v2026.10.20.223203**
|
||||
Current release: **Claro v2026.10.25.014405**
|
||||
|
||||
Graphics support is intentionally separated from the stable beginner console workflow.
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Learner feedback — blank observation form
|
||||
|
||||
Status: NOT RUN. Leave observations blank until a consenting person participates.
|
||||
Do not fill this form with predictions, agent simulations or invented participants.
|
||||
|
||||
- Anonymous session ID:
|
||||
- Consent to retain anonymous notes: yes / no
|
||||
- Build version and artifact checksum:
|
||||
- OS/device and assistive tools (only if voluntarily relevant):
|
||||
- Tasks attempted / skipped:
|
||||
- Breaks, pacing and requested adjustments:
|
||||
|
||||
For each attempted task record:
|
||||
|
||||
- What the learner tried or said (anonymous, no health information):
|
||||
- What worked independently:
|
||||
- What help or clarification was requested:
|
||||
- Exact confusing instruction/error and its filename/line:
|
||||
- Whether the suggested repair helped:
|
||||
- Learner's suggested change:
|
||||
|
||||
Use plain observations, not ability rankings. Do not infer a medical diagnosis.
|
||||
|
||||
Debrief:
|
||||
|
||||
- Clearest part:
|
||||
- Most confusing part:
|
||||
- One small change to try:
|
||||
- Follow-up session agreed, if any:
|
||||
- Notes retention/deletion agreement:
|
||||
|
||||
Automated example execution verifies code/output only, not these observations.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Beginner pilot — observer kit
|
||||
|
||||
**Status: prepared, not learner-validated.** No participant feedback has been
|
||||
collected by these automated checks. Do not infer accessibility from test passes.
|
||||
|
||||
Ask for willing participation and permission to keep anonymous notes. Explain
|
||||
that we are testing the instructions, not the person. Collect no diagnosis,
|
||||
medical history, real names or sensitive information. Let the learner decline,
|
||||
ask for help, take breaks or stop. Use any assistive technology they prefer.
|
||||
|
||||
Use a platform-tested build, a fresh writable practice folder and reviewed scripts.
|
||||
Show how to stop/close the interpreter before starting. Do not run downloaded
|
||||
untrusted scripts. Explain that optional limits are not a sandbox.
|
||||
|
||||
Try the first three tasks at the learner's pace. Offer the rest only if wanted.
|
||||
Let the learner predict, run and change the program. Observe before giving a
|
||||
hint; never leave someone distressed just to measure independence. Record help
|
||||
without judging it. Answers/output for observers are in `tests/learner_pilot.json`.
|
||||
|
||||
## Say hello
|
||||
|
||||
Run this once. Change the message to something you choose and run it again.
|
||||
|
||||
```claro
|
||||
SAY "Hello!"
|
||||
```
|
||||
|
||||
## Ask a name
|
||||
|
||||
Run it and type a pretend name. Explain where that name is remembered.
|
||||
|
||||
```claro
|
||||
ASK "Name? " name
|
||||
SAY "Hello " + name
|
||||
```
|
||||
|
||||
## Change a number
|
||||
|
||||
Predict the result, run it, then change 2 to another small number.
|
||||
|
||||
```claro
|
||||
SET score 2
|
||||
SAY score + 1
|
||||
```
|
||||
|
||||
## Make a choice
|
||||
|
||||
Optional: change score to 0. Predict which message appears.
|
||||
|
||||
```claro
|
||||
SET score 2
|
||||
IF score > 1
|
||||
SAY "More than one"
|
||||
ELSE
|
||||
SAY "One or less"
|
||||
END
|
||||
```
|
||||
|
||||
## Repeat
|
||||
|
||||
Optional: change how many times the message appears. Ask how to stop a stuck run.
|
||||
|
||||
```claro
|
||||
DO 3 TIMES
|
||||
SAY "Again"
|
||||
END
|
||||
```
|
||||
|
||||
## Save a file
|
||||
|
||||
Optional: use a new writable practice folder. Find the file after running it.
|
||||
|
||||
```claro
|
||||
WRITE FILE "practice.txt" WITH "My first file"
|
||||
READ FILE "practice.txt" AS text
|
||||
SAY text
|
||||
```
|
||||
|
||||
## Gentle mistake and recovery
|
||||
|
||||
In the hello task, replace SAY with PRNT, run `claro check practice.claro`, and
|
||||
ask what the message suggests. Restore SAY and rerun. This mistake is optional;
|
||||
skip it if it adds stress. Do not teach alternative compatibility spellings here.
|
||||
|
||||
## Debrief and next change
|
||||
|
||||
Ask: What was clear? What was confusing? What would you change? Observe whether
|
||||
the person can start/run/save, predict a result and use a repair hint. Do not
|
||||
calculate a disability score or call one session proof of universal accessibility.
|
||||
Use [the blank feedback form](LEARNER_FEEDBACK_TEMPLATE.md); keep only consented,
|
||||
anonymous notes, record the exact build/platform, and choose one small improvement.
|
||||
The [classroom checklist](CLASSROOM_READINESS.md) is a pilot guide, not certification.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Native Windows acceptance kit
|
||||
|
||||
**Prepared, not executed on a native target.** Wine results do not satisfy this
|
||||
checklist. A Windows login/authorized VM was not supplied for this run.
|
||||
|
||||
Use a **fresh extracted test folder**, not personal documents. Scripts write
|
||||
`oscheck.txt`, `nver.txt`, `ntests.txt`, `ncheck.txt` and replace their own `native.ok`
|
||||
marker. Preserve failed logs. A marker alone is not proof: review the OS evidence,
|
||||
version, all discovered fixtures and every test result together.
|
||||
|
||||
## OS evidence helper
|
||||
|
||||
`tools/native_platform_probe.c` builds a separate `osprobe.exe`; it is not a
|
||||
second interpreter source file. With VC6 configured:
|
||||
|
||||
```bat
|
||||
cl /nologo /TC /ML /O1 tools\native_platform_probe.c /Feosprobe.exe /link /SUBSYSTEM:CONSOLE,4.10
|
||||
```
|
||||
|
||||
The probe records numeric OS API fields, detects Wine's exported implementation
|
||||
entry point, and refuses Wine with exit 2. `--require-win98se` additionally needs
|
||||
Win32-Windows platform ID and version 4.10 build 2222. A real Windows VM is an
|
||||
acceptable target; a compatibility layer is not. Old API version reporting on
|
||||
modern Windows can be affected by application compatibility, so also record the
|
||||
actual OS/version from its own system information screen. The probe is evidence,
|
||||
not cryptographic attestation or a substitute for inspecting a real target.
|
||||
|
||||
## Automated checks
|
||||
|
||||
Put the compiled probe beside the matching interpreter and the complete package.
|
||||
For Windows 98 SE run [test98.bat](../test98.bat). For modern x64 Windows run
|
||||
[testwin.bat](../testwin.bat) with the MinGW `claro.exe` build.
|
||||
These batch scripts avoid modern CMD-only extensions and reject a wrong target
|
||||
before writing a PASS marker. Review `ntests.txt`: every packaged golden fixture
|
||||
must appear, with zero failures. `ncheck.txt` must show built-in validation passed.
|
||||
|
||||
## Manual target observations — leave blank until actually tested
|
||||
|
||||
- Artifact version/SHA-256 and OS/VM identity:
|
||||
- `oscheck.txt` reviewed, Wine false, target version independently confirmed:
|
||||
- All packaged fixtures discovered/passed:
|
||||
- Keyboard input, editing and non-ASCII display observations:
|
||||
- Save/read a text file in a writable folder:
|
||||
- File paths containing spaces:
|
||||
- Start/stop a loop and recover to the console:
|
||||
- First-hour examples and quiz interaction:
|
||||
- Missing input or failed command recovery:
|
||||
- Tester/date, consent to retain anonymous notes, unresolved failures:
|
||||
|
||||
Transfer only consented technical logs, not passwords, personal files or medical
|
||||
information. Do not change an unknown remote machine or install services just to
|
||||
make a test pass. The native target remains **unverified** until these observations
|
||||
and actual target logs exist. See [Windows 98 compilation](WINDOWS98.md).
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Claro Performance and Optimization Notes
|
||||
|
||||
Current release: **Claro v2026.10.20.223203**
|
||||
Current release: **Claro v2026.10.25.014405**
|
||||
|
||||
Performance work should come after language behavior is stable and measured. Do not optimize blindly.
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
- [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.
|
||||
|
||||
- [Learner pilot kit](LEARNER_PILOT.md) and [blank feedback](LEARNER_FEEDBACK_TEMPLATE.md)
|
||||
- [Native Windows acceptance kit](NATIVE_WINDOWS_TESTING.md)
|
||||
- [Six-track progress and blockers](SIX_TRACK_STATUS.md)
|
||||
|
||||
## Look something up
|
||||
|
||||
- [Beginner syntax](SYNTAX.md) and [older compatibility forms](COMPATIBILITY.md)
|
||||
|
||||
@@ -19,6 +19,15 @@ 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.
|
||||
|
||||
## Candidate continuation — 2026-10-21
|
||||
|
||||
The last pushed source checkpoint is `d55507140660186e594303306647e0f0e9facf76`.
|
||||
The continuation candidate is a separate development run; see
|
||||
[six-track status](SIX_TRACK_STATUS.md) for scope and blocked acceptance.
|
||||
Gitea currently reports Actions disabled, and authenticated runs/release access
|
||||
was not available for this continuation. No hosted CI success or hosted release
|
||||
is claimed. Earlier artifact records below retain their original hashes/versions.
|
||||
|
||||
## Immutable artifact checkpoints
|
||||
|
||||
These are verified build artifacts previously delivered for testing, **not a
|
||||
|
||||
@@ -41,6 +41,23 @@ 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.
|
||||
|
||||
## Diagnostics, JSON and opt-in budgets
|
||||
|
||||
Expression errors keep source origins across calls, imports and TRY/CATCH.
|
||||
The error guide is executable-verified. Malformed JSON now fails instead of
|
||||
hanging, nesting is bounded, control-character round trips are checked, and
|
||||
Unicode escapes/surrogate pairs are covered. JSON temporaries, serialization
|
||||
copies and popped import-stack entries have focused leak-enabled regressions.
|
||||
The optional step budget counts empty loop entries; READ FILE can enforce a
|
||||
per-read byte cap. These are not process isolation or wall-clock bounds.
|
||||
|
||||
`validate_error_locations.py`, `validate_error_guide.py`, `validate_learner_pilot.py`
|
||||
and `validate_resource_guards.py` run in the full gate and inside the existing
|
||||
learning-ownership sanitizer check. The reliability runner still counts 35
|
||||
maintained top-level checks; subcase coverage has expanded, not been relabeled
|
||||
as 35 new independent platform tests. Human feedback and native Windows tests
|
||||
remain separate, blocked acceptance tracks.
|
||||
|
||||
## Scope
|
||||
|
||||
A passing run establishes the exercised boundaries, not universal leak freedom,
|
||||
|
||||
+50
-24
@@ -1,32 +1,58 @@
|
||||
# Current runtime guards
|
||||
|
||||
These are implemented guardrails, not a sandbox or a promise that arbitrary
|
||||
scripts have bounded resource use.
|
||||
Claro runs trusted scripts with the user's permissions. These guards are **not
|
||||
a sandbox**, a wall-clock timeout, or proof that arbitrary scripts use bounded resources.
|
||||
|
||||
- **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.
|
||||
## Always-on nesting/response guards
|
||||
|
||||
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.
|
||||
- Function/method calls: at most 256 active calls.
|
||||
- Expressions and additional nested control blocks: at most 128 levels.
|
||||
- Parsed JSON: at most 128 nested arrays/objects. A scalar inside 128 containers
|
||||
is allowed; a 129th container is rejected. Malformed syntax fails with a Claro
|
||||
diagnostic rather than looping without consuming input. JSON Unicode escapes
|
||||
and surrogate pairs are decoded; embedded zero characters are rejected because
|
||||
Claro text uses zero-terminated strings. This is not a general encoding validator.
|
||||
- HTTP response bodies: the existing 1,048,576-byte cap remains.
|
||||
|
||||
Errors raised during expressions keep the executing source file and line,
|
||||
including imported functions and caught `LASTERRORFILE`/`LASTERRORLINE` metadata.
|
||||
REPEAT without UNTIL is rejected before accessing a missing terminator.
|
||||
|
||||
## Optional guards — disabled by default
|
||||
|
||||
`CLARO_STEP_LIMIT` sets a non-negative interpreter-work budget. Zero/unset means
|
||||
unlimited. It counts source-line dispatches and block/range entries, including
|
||||
empty loop iterations; it is **not** an exact statement count or elapsed time.
|
||||
Once exhausted it reports a source-located error suggesting the loop be checked.
|
||||
The consumed budget is not reset by TRY/CATCH or by calling a function.
|
||||
|
||||
`CLARO_READ_FILE_LIMIT` sets the maximum bytes read by each `READ FILE` command.
|
||||
Zero/unset means unlimited. The exact boundary is accepted; larger input errors
|
||||
without storing a partial result. It does not cap source loading, package metadata,
|
||||
all file operations, aggregate memory or output. Invalid/overflowing limit values
|
||||
fail configuration; supported numeric range depends on the compiler's unsigned long.
|
||||
|
||||
For example, in a Windows command prompt before running a reviewed script:
|
||||
|
||||
```bat
|
||||
set CLARO_STEP_LIMIT=10000
|
||||
set CLARO_READ_FILE_LIMIT=1048576
|
||||
claro98.exe practice.claro
|
||||
```
|
||||
|
||||
On Linux/macOS shells the equivalent is an environment assignment before `./claro`.
|
||||
These optional budgets do not interrupt a blocked ASK, a shell command, curl,
|
||||
or an individual long-running builtin. They are not untrusted-code protection.
|
||||
|
||||
## Not provided
|
||||
|
||||
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).
|
||||
There is no general CPU/time budget, process isolation, total-allocation limit,
|
||||
global file/output limit or JSON byte-size control. The old beta names
|
||||
`CLARO_MAX_FILE_BYTES` and `CLARO_MAX_JSON_BYTES` are still not implemented.
|
||||
The newer READ FILE guard is deliberately narrower. The former unimplemented
|
||||
step-limit claim is preserved as history; the current step guard is the explicit
|
||||
implementation described above, not validation of those older promises.
|
||||
|
||||
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.
|
||||
|
||||
See [reliability verification](RELIABILITY.md) for the tested scope.
|
||||
Without the optional step budget ordinary intentional loops can run indefinitely.
|
||||
Run only trusted scripts. See [reliability](RELIABILITY.md),
|
||||
[errors and repairs](ERRORS.md), and the [historical limits](history/RUNTIME_LIMITS_BETA25.md).
|
||||
|
||||
+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.223203, 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.25.014405, running them with the included `claro.exe` will fail with:
|
||||
|
||||
```text
|
||||
Unknown command: SDL
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Six-track progress — development candidate v2026.10.21.010934
|
||||
|
||||
This is a work/acceptance map, not a stable release or a claim all six external
|
||||
milestones are complete. Real learner sessions and native OS access were not
|
||||
supplied in the clarification request. No simulated observations are recorded.
|
||||
|
||||
## 1. Error reporting — implemented, regression-tested
|
||||
|
||||
Expressions retain file/line origins through functions, imports, UNTIL and
|
||||
TRY/CATCH metadata. The first failing location is not overwritten by its caller.
|
||||
Focused tests cover direct, nested and caught paths, including leak-enabled runs.
|
||||
|
||||
## 2. Error guide — aligned and executable-verified
|
||||
|
||||
The [guide](ERRORS.md) now shows current short SET/END forms and six actual
|
||||
failure/repair pairs. `tests/error_examples.json` is checked against every guide
|
||||
code block and against the interpreter's diagnostic and repaired output.
|
||||
|
||||
## 3. Beginner testing — kit ready; actual learner acceptance BLOCKED
|
||||
|
||||
The [pilot kit](LEARNER_PILOT.md) contains six small starter tasks (three primary,
|
||||
three optional), observer instructions and a [blank feedback form](LEARNER_FEEDBACK_TEMPLATE.md).
|
||||
Automated code/output checks are not a person testing the language. A willing,
|
||||
consenting learner and anonymous real observations are still required.
|
||||
|
||||
## 4. Native Windows — test kit prepared; target acceptance BLOCKED
|
||||
|
||||
[Native checks](NATIVE_WINDOWS_TESTING.md) require an authorized Windows login or
|
||||
real Windows 98 SE VM. No such access was supplied. The OS helper explicitly
|
||||
rejects Wine and can require Win98 SE 4.10.2222 before a PASS marker is written.
|
||||
Compiler/Wine checks are separate evidence and never counted as target acceptance.
|
||||
|
||||
## 5. Robustness — focused fixes and guards, not a sandbox
|
||||
|
||||
Malformed JSON such as `[?]` previously hung; it now produces a located diagnostic.
|
||||
Containers are bounded at 128, separators/numbers/escapes are checked, Unicode
|
||||
escapes/surrogate pairs and control-character serialization are covered. JSON
|
||||
parse/serialize temporaries and popped import entries have cleanup regressions.
|
||||
Missing UNTIL is rejected. Opt-in step and per-READ FILE byte budgets are described
|
||||
in [runtime guards](RUNTIME_LIMITS.md). Empty loops consume work budget; TRY/CATCH
|
||||
does not reset it. These do not bound all memory, time, output or external commands.
|
||||
|
||||
## 6. Release delivery — candidate validation; hosted gate BLOCKED
|
||||
|
||||
Local source/Linux/Windows candidate archives must pass extracted-payload checks
|
||||
before delivery. Actual checksums/reports belong to those frozen artifacts, not
|
||||
just the source checkout. The VC6 ZIP uses a separate manifest/report path.
|
||||
|
||||
On this run the Gitea repository metadata reported `has_actions: false`; the
|
||||
Actions runs API returned HTTP 401, the Actions web page was unavailable, and
|
||||
there was no authenticated browser/API credential. Secure vault entry was not
|
||||
available in this session. Hosted CI cannot be verified or enabled from Git SSH
|
||||
access alone. No hosted release or CI success is claimed and no release assets
|
||||
have been uploaded. Authenticated administration and enabled/configured Actions
|
||||
are needed before completing the requested hosted CI/release gate.
|
||||
|
||||
The repository API also reported `private: false`. No visibility change was made;
|
||||
do not put credentials or sensitive learner/native-host information into this repo.
|
||||
|
||||
See [release status](RELEASE_STATUS.md) for dated earlier checkpoints. Candidate
|
||||
build evidence does not retroactively validate native Windows or accessibility.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Claro Language Specification
|
||||
|
||||
Current package: **Claro v2026.10.20.223203**
|
||||
Current package: **Claro v2026.10.25.014405**
|
||||
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.
|
||||
|
||||
@@ -59,6 +59,22 @@ 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.
|
||||
|
||||
## New focused checks
|
||||
|
||||
```bash
|
||||
python3 tools/validate_error_locations.py
|
||||
python3 tools/validate_error_guide.py
|
||||
python3 tools/validate_learner_pilot.py
|
||||
python3 tools/validate_resource_guards.py
|
||||
```
|
||||
|
||||
They are also part of the full gate. `validate_learning_ownership.py` runs them
|
||||
with leak detection enabled, including negative paths. Pilot code checks are
|
||||
not learner observations. [Native acceptance](NATIVE_WINDOWS_TESTING.md) requires
|
||||
real target logs; the Wine-rejecting probe prevents silently substituting a
|
||||
compatibility-layer result. Current [six-track status](SIX_TRACK_STATUS.md)
|
||||
separates implemented work from blocked human/hosted acceptance.
|
||||
|
||||
## Historical tools
|
||||
|
||||
Older `validate_rc*.py` and versioned validators remain for history and focused
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Claro v2026.10.20.223203 - Packages and Projects
|
||||
# Claro v2026.10.25.014405 - Packages and Projects
|
||||
|
||||
Claro v2026.10.20.223203 makes projects and packages safer and more useful while keeping the beginner syntax simple.
|
||||
Claro v2026.10.25.014405 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.223203
|
||||
version: v2026.10.25.014405
|
||||
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.223203`. A missing, stale, blank, or duplicate release line fails with:
|
||||
The lockfile must also contain exactly one current release line, `version: v2026.10.25.014405`. A missing, stale, blank, or duplicate release line fails with:
|
||||
|
||||
```text
|
||||
BAD lock release version: expected exactly one version: v2026.10.20.223203 line
|
||||
BAD lock release version: expected exactly one version: v2026.10.25.014405 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.223203**
|
||||
Current release: **Claro v2026.10.25.014405**
|
||||
|
||||
Claro currently has beginner HTTP client commands and offline `claro://` test URLs. A web server API is not complete yet.
|
||||
|
||||
|
||||
+249
-101
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Claro v2026.10.20.223203 - Networking Reliability Release
|
||||
Claro v2026.10.25.014405 - Networking Reliability Release
|
||||
|
||||
A compact, buildable Claro interpreter focused on the beginner-friendly
|
||||
teaching core: readable commands, simple expressions, functions, collections,
|
||||
@@ -45,7 +45,7 @@ int pclose(FILE *stream);
|
||||
#define F_OK 0
|
||||
#endif
|
||||
|
||||
#define CLARO_VERSION "Claro v2026.10.20.223203"
|
||||
#define CLARO_VERSION "Claro v2026.10.25.014405"
|
||||
#define CLARO_MAX_CALL_DEPTH 256
|
||||
#define CLARO_MAX_EXPR_DEPTH 128
|
||||
#define CLARO_MAX_BLOCK_DEPTH 128
|
||||
@@ -574,6 +574,9 @@ typedef struct Runtime {
|
||||
char *err_msg;
|
||||
char *err_file;
|
||||
int err_line;
|
||||
const char *active_file; /* borrowed while a source line executes */
|
||||
int active_line;
|
||||
unsigned long steps, step_limit, read_file_limit;
|
||||
int returning;
|
||||
Value ret;
|
||||
int call_depth;
|
||||
@@ -585,10 +588,42 @@ typedef struct Runtime {
|
||||
int script_argc;
|
||||
char **script_argv;
|
||||
} Runtime;
|
||||
static void rt_error(Runtime *rt, const char *file, int line, const char *fmt, ...);
|
||||
static unsigned long rt_env_limit(Runtime *rt, const char *name) {
|
||||
const char *text = getenv(name);
|
||||
unsigned long value = 0;
|
||||
if (!text || !*text) return 0;
|
||||
while (*text) {
|
||||
unsigned long digit;
|
||||
if (*text < '0' || *text > '9') {
|
||||
rt_error(rt, "<configuration>", 1, "%s needs a non-negative whole number.", name);
|
||||
return 0;
|
||||
}
|
||||
digit = (unsigned long)(*text++ - '0');
|
||||
if (value > (ULONG_MAX - digit) / 10) {
|
||||
rt_error(rt, "<configuration>", 1, "%s is too large for this build.", name);
|
||||
return 0;
|
||||
}
|
||||
value = value * 10 + digit;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
static int rt_tick(Runtime *rt) {
|
||||
if (rt->error) return 0;
|
||||
if (!rt->step_limit) return 1;
|
||||
if (rt->steps >= rt->step_limit) {
|
||||
rt_error(rt, "", 0, "Execution reached its step limit. Check the loop or raise CLARO_STEP_LIMIT.");
|
||||
return 0;
|
||||
}
|
||||
rt->steps++;
|
||||
return 1;
|
||||
}
|
||||
static void rt_init(Runtime *rt) {
|
||||
memset(rt, 0, sizeof(*rt));
|
||||
str_init(&rt->captured);
|
||||
rt->ret = v_none();
|
||||
rt->step_limit = rt_env_limit(rt, "CLARO_STEP_LIMIT");
|
||||
rt->read_file_limit = rt_env_limit(rt, "CLARO_READ_FILE_LIMIT");
|
||||
srand(1);
|
||||
}
|
||||
static Var *env_find(Var *v, const char *name) {
|
||||
@@ -825,6 +860,10 @@ static void rt_error(Runtime *rt, const char *file, int line, const char *fmt, .
|
||||
va_start(ap, fmt);
|
||||
claro_vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
if (!file || !*file)
|
||||
file = rt->active_file;
|
||||
if (line <= 0)
|
||||
line = rt->active_line;
|
||||
nfile = normalize_path_copy(file ? file : "");
|
||||
rt->error = 1;
|
||||
rt->err_msg = xstrdup(buf);
|
||||
@@ -1877,134 +1916,195 @@ static int find_done(Program *p, int start) {
|
||||
/* ---------- JSON ---------- */
|
||||
typedef struct {
|
||||
const char *s;
|
||||
int depth;
|
||||
const char *error;
|
||||
} JsonP;
|
||||
#define CLARO_MAX_JSON_DEPTH 128
|
||||
static void jp_ws(JsonP *j) {
|
||||
while (*j->s && isspace((unsigned char)*j->s))
|
||||
j->s++;
|
||||
}
|
||||
static unsigned long jp_hex4(JsonP *j) {
|
||||
unsigned long value = 0;
|
||||
int i, digit;
|
||||
for (i = 0; i < 4; i++) {
|
||||
char c = *j->s;
|
||||
if (c >= '0' && c <= '9') digit = c - '0';
|
||||
else if (c >= 'a' && c <= 'f') digit = c - 'a' + 10;
|
||||
else if (c >= 'A' && c <= 'F') digit = c - 'A' + 10;
|
||||
else { j->error = "JSON Unicode escapes need four hexadecimal digits."; return 0; }
|
||||
value = value * 16 + (unsigned long)digit;
|
||||
j->s++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
static void jp_utf8(JsonP *j, Str *b, unsigned long code) {
|
||||
unsigned long low;
|
||||
if (code >= 0xd800UL && code <= 0xdbffUL) {
|
||||
if (j->s[0] != '\\' || j->s[1] != 'u') {
|
||||
j->error = "JSON Unicode needs a matching low surrogate.";
|
||||
return;
|
||||
}
|
||||
j->s += 2;
|
||||
low = jp_hex4(j);
|
||||
if (j->error) return;
|
||||
if (low < 0xdc00UL || low > 0xdfffUL) {
|
||||
j->error = "JSON Unicode needs a matching low surrogate.";
|
||||
return;
|
||||
}
|
||||
code = 0x10000UL + (code - 0xd800UL) * 1024 + low - 0xdc00UL;
|
||||
} else if (code >= 0xdc00UL && code <= 0xdfffUL) {
|
||||
j->error = "JSON Unicode has an unmatched low surrogate.";
|
||||
return;
|
||||
}
|
||||
if (!code) { j->error = "JSON text cannot contain a zero character."; return; }
|
||||
if (code < 0x80UL) str_ch(b, (char)code);
|
||||
else if (code < 0x800UL) {
|
||||
str_ch(b, (char)(0xc0UL | (code >> 6)));
|
||||
str_ch(b, (char)(0x80UL | (code & 63)));
|
||||
} else if (code < 0x10000UL) {
|
||||
str_ch(b, (char)(0xe0UL | (code >> 12)));
|
||||
str_ch(b, (char)(0x80UL | ((code >> 6) & 63)));
|
||||
str_ch(b, (char)(0x80UL | (code & 63)));
|
||||
} else {
|
||||
str_ch(b, (char)(0xf0UL | (code >> 18)));
|
||||
str_ch(b, (char)(0x80UL | ((code >> 12) & 63)));
|
||||
str_ch(b, (char)(0x80UL | ((code >> 6) & 63)));
|
||||
str_ch(b, (char)(0x80UL | (code & 63)));
|
||||
}
|
||||
}
|
||||
static char *jp_string(JsonP *j) {
|
||||
Str b;
|
||||
char escaped;
|
||||
str_init(&b);
|
||||
jp_ws(j);
|
||||
if (*j->s != '"')
|
||||
return xstrdup("");
|
||||
if (*j->s != '"') { j->error = "JSON object keys need double quotes."; return xstrdup(""); }
|
||||
j->s++;
|
||||
while (*j->s && *j->s != '"') {
|
||||
while (*j->s && *j->s != '"' && !j->error) {
|
||||
if (*j->s == '\\') {
|
||||
j->s++;
|
||||
if (*j->s == 'n')
|
||||
str_ch(&b, '\n');
|
||||
else if (*j->s == 't')
|
||||
str_ch(&b, '\t');
|
||||
else if (*j->s == 'r')
|
||||
str_ch(&b, '\r');
|
||||
else if (*j->s)
|
||||
str_ch(&b, *j->s);
|
||||
if (*j->s)
|
||||
j->s++;
|
||||
} else
|
||||
str_ch(&b, *j->s++);
|
||||
if (!*j->s) { j->error = "JSON text is missing a closing quote."; break; }
|
||||
escaped = *j->s++;
|
||||
if (escaped == 'n') str_ch(&b, '\n');
|
||||
else if (escaped == 't') str_ch(&b, '\t');
|
||||
else if (escaped == 'r') str_ch(&b, '\r');
|
||||
else if (escaped == 'b') str_ch(&b, '\b');
|
||||
else if (escaped == 'f') str_ch(&b, '\f');
|
||||
else if (escaped == 'u') {
|
||||
unsigned long code = jp_hex4(j);
|
||||
if (!j->error) jp_utf8(j, &b, code);
|
||||
} else if (escaped == '"' || escaped == '\\' || escaped == '/') str_ch(&b, escaped);
|
||||
else j->error = "JSON has an invalid text escape.";
|
||||
} else if ((unsigned char)*j->s < 32) j->error = "JSON text contains an unescaped control character.";
|
||||
else str_ch(&b, *j->s++);
|
||||
}
|
||||
if (*j->s == '"')
|
||||
j->s++;
|
||||
if (*j->s == '"') j->s++;
|
||||
else if (!j->error) j->error = "JSON text is missing a closing quote.";
|
||||
return str_take(&b);
|
||||
}
|
||||
static Value jp_value(JsonP *j);
|
||||
static Value jp_array(JsonP *j) {
|
||||
Value arr = v_list();
|
||||
j->s++;
|
||||
jp_ws(j);
|
||||
if (*j->s == '[')
|
||||
j->s++;
|
||||
jp_ws(j);
|
||||
if (*j->s == ']') {
|
||||
j->s++;
|
||||
return arr;
|
||||
}
|
||||
while (*j->s) {
|
||||
if (*j->s == ']') { j->s++; return arr; }
|
||||
while (!j->error) {
|
||||
Value v = jp_value(j);
|
||||
list_add(arr.list, v);
|
||||
if (!j->error) list_add(arr.list, v);
|
||||
value_free(v);
|
||||
if (j->error) break;
|
||||
jp_ws(j);
|
||||
if (*j->s == ',') {
|
||||
j->s++;
|
||||
continue;
|
||||
}
|
||||
if (*j->s == ']') {
|
||||
j->s++;
|
||||
break;
|
||||
}
|
||||
if (*j->s == ']') { j->s++; break; }
|
||||
if (*j->s != ',') { j->error = "JSON list needs a comma or closing bracket."; break; }
|
||||
j->s++;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
static Value jp_object(JsonP *j) {
|
||||
Value obj = v_map();
|
||||
j->s++;
|
||||
jp_ws(j);
|
||||
if (*j->s == '{')
|
||||
j->s++;
|
||||
jp_ws(j);
|
||||
if (*j->s == '}') {
|
||||
j->s++;
|
||||
return obj;
|
||||
}
|
||||
while (*j->s) {
|
||||
char *k = jp_string(j);
|
||||
Value v;
|
||||
if (*j->s == '}') { j->s++; return obj; }
|
||||
while (!j->error) {
|
||||
char *key = jp_string(j);
|
||||
Value v = v_none();
|
||||
jp_ws(j);
|
||||
if (*j->s == ':')
|
||||
j->s++;
|
||||
v = jp_value(j);
|
||||
map_put(obj.map, k, v);
|
||||
free(k);
|
||||
if (!j->error && *j->s != ':') j->error = "JSON object needs a colon after its key.";
|
||||
if (!j->error) { j->s++; v = jp_value(j); }
|
||||
if (!j->error) map_put(obj.map, key, v);
|
||||
value_free(v);
|
||||
free(key);
|
||||
if (j->error) break;
|
||||
jp_ws(j);
|
||||
if (*j->s == ',') {
|
||||
j->s++;
|
||||
continue;
|
||||
}
|
||||
if (*j->s == '}') {
|
||||
j->s++;
|
||||
break;
|
||||
}
|
||||
if (*j->s == '}') { j->s++; break; }
|
||||
if (*j->s != ',') { j->error = "JSON object needs a comma or closing brace."; break; }
|
||||
j->s++;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
static Value jp_value(JsonP *j) {
|
||||
const char *scan;
|
||||
char *end;
|
||||
double number;
|
||||
Value result;
|
||||
jp_ws(j);
|
||||
if (*j->s == '"') {
|
||||
char *s = jp_string(j);
|
||||
Value v = v_str(s);
|
||||
free(s);
|
||||
return v;
|
||||
char *text = jp_string(j);
|
||||
result = v_str(text);
|
||||
free(text);
|
||||
return result;
|
||||
}
|
||||
if (*j->s == '{')
|
||||
return jp_object(j);
|
||||
if (*j->s == '[')
|
||||
return jp_array(j);
|
||||
if (!strncmp(j->s, "true", 4)) {
|
||||
j->s += 4;
|
||||
return v_bool(1);
|
||||
if (*j->s == '[' || *j->s == '{') {
|
||||
if (j->depth >= CLARO_MAX_JSON_DEPTH) {
|
||||
j->error = "JSON nesting exceeds the safe limit of 128. Simplify the data.";
|
||||
return v_none();
|
||||
}
|
||||
j->depth++;
|
||||
result = *j->s == '[' ? jp_array(j) : jp_object(j);
|
||||
j->depth--;
|
||||
return result;
|
||||
}
|
||||
if (!strncmp(j->s, "false", 5)) {
|
||||
j->s += 5;
|
||||
return v_bool(0);
|
||||
if (!strncmp(j->s, "true", 4)) { j->s += 4; return v_bool(1); }
|
||||
if (!strncmp(j->s, "false", 5)) { j->s += 5; return v_bool(0); }
|
||||
if (!strncmp(j->s, "null", 4)) { j->s += 4; return v_none(); }
|
||||
scan = j->s;
|
||||
if (*scan == '-') scan++;
|
||||
if (*scan == '0') scan++;
|
||||
else if (*scan >= '1' && *scan <= '9') while (isdigit((unsigned char)*scan)) scan++;
|
||||
else { j->error = "JSON needs a value here."; return v_none(); }
|
||||
if (*scan == '.') {
|
||||
scan++;
|
||||
if (!isdigit((unsigned char)*scan)) { j->error = "JSON number needs digits after its decimal point."; return v_none(); }
|
||||
while (isdigit((unsigned char)*scan)) scan++;
|
||||
}
|
||||
if (!strncmp(j->s, "null", 4)) {
|
||||
j->s += 4;
|
||||
if (*scan == 'e' || *scan == 'E') {
|
||||
scan++;
|
||||
if (*scan == '+' || *scan == '-') scan++;
|
||||
if (!isdigit((unsigned char)*scan)) { j->error = "JSON number needs exponent digits."; return v_none(); }
|
||||
while (isdigit((unsigned char)*scan)) scan++;
|
||||
}
|
||||
number = strtod(j->s, &end);
|
||||
if (end != scan || number > DBL_MAX || number < -DBL_MAX) {
|
||||
j->error = "JSON number is outside the supported range.";
|
||||
return v_none();
|
||||
}
|
||||
end = NULL;
|
||||
{
|
||||
double d = strtod(j->s, &end);
|
||||
if (end && end != j->s) {
|
||||
j->s = end;
|
||||
return v_num(d);
|
||||
}
|
||||
}
|
||||
return v_none();
|
||||
j->s = scan;
|
||||
return v_num(number);
|
||||
}
|
||||
static Value parse_json_text(const char *s) {
|
||||
static Value parse_json_text(Runtime *rt, const char *text) {
|
||||
JsonP j;
|
||||
j.s = s;
|
||||
return jp_value(&j);
|
||||
Value result;
|
||||
memset(&j, 0, sizeof(j));
|
||||
j.s = text;
|
||||
result = jp_value(&j);
|
||||
jp_ws(&j);
|
||||
if (!j.error && *j.s) j.error = "JSON has unexpected text after its value.";
|
||||
if (j.error) {
|
||||
value_free(result);
|
||||
rt_error(rt, "", 0, "%s", j.error);
|
||||
return v_none();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static void json_escape(Str *b, const char *s) {
|
||||
str_ch(b, '"');
|
||||
@@ -2015,6 +2115,12 @@ static void json_escape(Str *b, const char *s) {
|
||||
str_add(b, "\\\\");
|
||||
} else if (*s == '\n') {
|
||||
str_add(b, "\\n");
|
||||
} else if ((unsigned char)*s < 32) {
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
unsigned char c = (unsigned char)*s;
|
||||
str_add(b, "\\u00");
|
||||
str_ch(b, hex[c >> 4]);
|
||||
str_ch(b, hex[c & 15]);
|
||||
} else
|
||||
str_ch(b, *s);
|
||||
s++;
|
||||
@@ -2861,6 +2967,9 @@ static void do_import(Runtime *rt, const char *path, const char *ns, const char
|
||||
exec_range(rt, p, 0, p->count);
|
||||
}
|
||||
rt->import_depth--;
|
||||
free(rt->import_stack[rt->import_depth]);
|
||||
rt->import_stack[rt->import_depth] = NULL;
|
||||
split_args_free(exports, ec);
|
||||
free(prefix);
|
||||
}
|
||||
static void create_object_value(Runtime *rt, const char *file, int line, const char *cls,
|
||||
@@ -2889,7 +2998,7 @@ static void create_object_value(Runtime *rt, const char *file, int line, const c
|
||||
}
|
||||
}
|
||||
}
|
||||
static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
static void exec_line_body(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
char buf[4096], up[64], w[128];
|
||||
char *t;
|
||||
int pc = *pcp;
|
||||
@@ -3461,12 +3570,25 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
}
|
||||
if (strcmp(up, "REPEAT") == 0) {
|
||||
int end = match_block(p, pc, "REPEAT", "UNTIL", NULL, NULL);
|
||||
char *cond = p->lines[end] + 5;
|
||||
char *cond;
|
||||
int again = 0;
|
||||
if (end < 0) {
|
||||
rt_error(rt, p->path, pc + 1, "REPEAT needs UNTIL and a stopping condition.");
|
||||
return;
|
||||
}
|
||||
cond = p->lines[end] + 5;
|
||||
do {
|
||||
exec_range(rt, p, pc + 1, end);
|
||||
if (rt->error || rt->returning)
|
||||
break;
|
||||
} while (!v_truth(eval_expr(rt, cond)));
|
||||
rt->active_file = p->path;
|
||||
rt->active_line = end + 1;
|
||||
{
|
||||
Value condition = eval_expr(rt, cond);
|
||||
again = !v_truth(condition);
|
||||
value_free(condition);
|
||||
}
|
||||
} while (again && !rt->error);
|
||||
*pcp = end < 0 ? pc : end;
|
||||
return;
|
||||
}
|
||||
@@ -3717,12 +3839,21 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
Str b;
|
||||
int c;
|
||||
Value contents;
|
||||
unsigned long bytes = 0;
|
||||
str_init(&b);
|
||||
while ((c = fgetc(f)) != EOF)
|
||||
while ((c = fgetc(f)) != EOF) {
|
||||
if (rt->read_file_limit && bytes >= rt->read_file_limit) {
|
||||
rt_error(rt, p->path, pc + 1,
|
||||
"READ FILE exceeded the configured file limit. Raise CLARO_READ_FILE_LIMIT if needed.");
|
||||
break;
|
||||
}
|
||||
str_ch(&b, (char)c);
|
||||
if (rt->read_file_limit) bytes++;
|
||||
}
|
||||
if (ferror(f)) rt_error(rt, p->path, pc + 1, "Could not finish reading the file.");
|
||||
fclose(f);
|
||||
contents = v_str(b.s ? b.s : "");
|
||||
rt_set(rt, var, contents);
|
||||
if (!rt->error) rt_set(rt, var, contents);
|
||||
value_free(contents);
|
||||
free(b.s);
|
||||
}
|
||||
@@ -3844,7 +3975,10 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
char *var = xstrdup(trim_inplace((char *)as + 2));
|
||||
Value s = eval_expr(rt, ex);
|
||||
char *txt = v_to_string(s);
|
||||
rt_set(rt, var, parse_json_text(txt));
|
||||
Value parsed = parse_json_text(rt, txt);
|
||||
if (!rt->error) rt_set(rt, var, parsed);
|
||||
value_free(parsed);
|
||||
value_free(s);
|
||||
free(txt);
|
||||
free(ex);
|
||||
free(var);
|
||||
@@ -3860,7 +3994,10 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
char *var = xstrdup(trim_inplace((char *)as + 2));
|
||||
Value v = eval_expr(rt, ex);
|
||||
char *js = make_json(v, pretty);
|
||||
rt_set(rt, var, v_str(js));
|
||||
Value encoded = v_str(js);
|
||||
rt_set(rt, var, encoded);
|
||||
value_free(encoded);
|
||||
value_free(v);
|
||||
free(js);
|
||||
free(ex);
|
||||
free(var);
|
||||
@@ -4136,6 +4273,16 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
}
|
||||
rt_error(rt, p->path, pc + 1, "Unknown command: %s", w);
|
||||
}
|
||||
static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
||||
const char *saved_file = rt->active_file;
|
||||
int saved_line = rt->active_line;
|
||||
rt->active_file = p->path;
|
||||
rt->active_line = *pcp + 1;
|
||||
if (rt_tick(rt))
|
||||
exec_line_body(rt, p, pcp, raw);
|
||||
rt->active_file = saved_file;
|
||||
rt->active_line = saved_line;
|
||||
}
|
||||
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. */
|
||||
@@ -4144,6 +4291,7 @@ static void exec_range(Runtime *rt, Program *p, int start, int end) {
|
||||
"Block nesting exceeds the safe limit of 128. Simplify the nested blocks.");
|
||||
return;
|
||||
}
|
||||
if (!rt_tick(rt)) return;
|
||||
rt->range_depth++;
|
||||
if (end < 0 || end > p->count)
|
||||
end = p->count;
|
||||
@@ -6630,12 +6778,12 @@ static int create_new_project(const char *name) {
|
||||
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.223203\npackages:\n",
|
||||
"manifest-version: 1\nname: %s\nmain: main.claro\nversion: v2026.10.25.014405\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.223203\n");
|
||||
"# Claro package lock\nlock-version: 1\nversion: v2026.10.25.014405\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 "
|
||||
@@ -6732,7 +6880,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.223203\npackages:\n");
|
||||
"main.claro\nversion: v2026.10.25.014405\npackages:\n");
|
||||
}
|
||||
static char *project_value(const char *key) {
|
||||
char *txt = read_file_text("claro.project");
|
||||
@@ -6858,7 +7006,7 @@ static int write_package_lock(void) {
|
||||
free(txt);
|
||||
return 0;
|
||||
}
|
||||
fputs("# Claro package lock\nlock-version: 1\nversion: v2026.10.20.223203\n", f);
|
||||
fputs("# Claro package lock\nlock-version: 1\nversion: v2026.10.25.014405\n", f);
|
||||
p = txt;
|
||||
while (*p) {
|
||||
char *line = p;
|
||||
@@ -7341,7 +7489,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.223203 line\n");
|
||||
" BAD lock release version: expected exactly one version: v2026.10.25.014405 line\n");
|
||||
ok = 0;
|
||||
}
|
||||
free(txt);
|
||||
@@ -7505,7 +7653,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.223203\",\n");
|
||||
printf(" \"version\": \"v2026.10.25.014405\",\n");
|
||||
printf(" \"file_extensions\": [\".claro\"],\n");
|
||||
printf(" \"keywords\": "
|
||||
"[\"SAY\",\"ASK\",\"SET\",\"IF\",\"ELSE\",\"ENDIF\",\"END\",\"REPEAT\",\"UNTIL\","
|
||||
@@ -7855,7 +8003,7 @@ static int run_validate(void) {
|
||||
printf(
|
||||
"%s\n",
|
||||
fails ? "Validation found problems."
|
||||
: "Validation passed. Claro v2026.10.20.223203 foundation checks are ready for use.");
|
||||
: "Validation passed. Claro v2026.10.25.014405 foundation checks are ready for use.");
|
||||
return fails ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
rem Run only in a fresh extracted Claro test folder.
|
||||
if exist native.ok del native.ok
|
||||
if not exist osprobe.exe goto wrongos
|
||||
osprobe.exe --require-win98se >oscheck.txt
|
||||
if errorlevel 1 goto wrongos
|
||||
claro98.exe --version >nver.txt
|
||||
if errorlevel 1 goto failed
|
||||
claro98.exe test >ntests.txt
|
||||
if errorlevel 1 goto failed
|
||||
claro98.exe validate >ncheck.txt
|
||||
if errorlevel 1 goto failed
|
||||
echo Automated native checks passed. Manual console and file checks are still required.
|
||||
echo PASS>native.ok
|
||||
goto end
|
||||
:wrongos
|
||||
echo Native target not verified. Check oscheck.txt and the probe message.
|
||||
goto end
|
||||
:failed
|
||||
echo Claro checks failed. Keep the logs; do not report acceptance.
|
||||
:end
|
||||
@@ -0,0 +1,56 @@
|
||||
[
|
||||
{
|
||||
"name": "quote",
|
||||
"filename": "quote.claro",
|
||||
"mode": "run",
|
||||
"code": "SAY \"Start\"\nSAY \"unfinished\n",
|
||||
"diagnostic": "quote.claro:2: Text is missing a closing quote. Add a double quote at the end.",
|
||||
"repair": "SAY \"Start\"\nSAY \"finished\"\n",
|
||||
"output": "Start\nfinished\n"
|
||||
},
|
||||
{
|
||||
"name": "operand",
|
||||
"filename": "operand.claro",
|
||||
"mode": "run",
|
||||
"code": "SET score 1\nSAY score +\n",
|
||||
"diagnostic": "operand.claro:2: This expression needs a value after the operator.",
|
||||
"repair": "SET score 1\nSAY score + 1\n",
|
||||
"output": "2\n"
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"filename": "command.claro",
|
||||
"mode": "check",
|
||||
"code": "PRNT \"Hello\"\n",
|
||||
"diagnostic": "command.claro:1: I do not know the command \"PRNT\". Did you mean \"SAY\"?",
|
||||
"repair": "SAY \"Hello\"\n",
|
||||
"output": "Hello\n"
|
||||
},
|
||||
{
|
||||
"name": "assignment",
|
||||
"filename": "assignment.claro",
|
||||
"mode": "check",
|
||||
"code": "SET name\n",
|
||||
"diagnostic": "assignment.claro:1: SET needs a name and a value. Try: SET name \"Alex\"",
|
||||
"repair": "SET name \"Alex\"\nSAY name\n",
|
||||
"output": "Alex\n"
|
||||
},
|
||||
{
|
||||
"name": "choice",
|
||||
"filename": "choice.claro",
|
||||
"mode": "check",
|
||||
"code": "IF YES\n SAY \"Yes\"\n",
|
||||
"diagnostic": "choice.claro:1: IF block opened here needs END",
|
||||
"repair": "IF YES\n SAY \"Yes\"\nEND\n",
|
||||
"output": "Yes\n"
|
||||
},
|
||||
{
|
||||
"name": "variable",
|
||||
"filename": "variable.claro",
|
||||
"mode": "run",
|
||||
"code": "SET score 10\nSAY socre\n",
|
||||
"diagnostic": "variable.claro:2: I do not know the variable \"socre\" yet.",
|
||||
"repair": "SET score 10\nSAY score\n",
|
||||
"output": "10\n"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,44 @@
|
||||
[
|
||||
{
|
||||
"name": "Say hello",
|
||||
"code": "SAY \"Hello!\"\n",
|
||||
"input": "",
|
||||
"output": "Hello!\n",
|
||||
"task": "Run this once. Change the message to something you choose and run it again."
|
||||
},
|
||||
{
|
||||
"name": "Ask a name",
|
||||
"code": "ASK \"Name? \" name\nSAY \"Hello \" + name\n",
|
||||
"input": "Sam\n",
|
||||
"output": "Name? \nHello Sam\n",
|
||||
"task": "Run it and type a pretend name. Explain where that name is remembered."
|
||||
},
|
||||
{
|
||||
"name": "Change a number",
|
||||
"code": "SET score 2\nSAY score + 1\n",
|
||||
"input": "",
|
||||
"output": "3\n",
|
||||
"task": "Predict the result, run it, then change 2 to another small number."
|
||||
},
|
||||
{
|
||||
"name": "Make a choice",
|
||||
"code": "SET score 2\nIF score > 1\n SAY \"More than one\"\nELSE\n SAY \"One or less\"\nEND\n",
|
||||
"input": "",
|
||||
"output": "More than one\n",
|
||||
"task": "Optional: change score to 0. Predict which message appears."
|
||||
},
|
||||
{
|
||||
"name": "Repeat",
|
||||
"code": "DO 3 TIMES\n SAY \"Again\"\nEND\n",
|
||||
"input": "",
|
||||
"output": "Again\nAgain\nAgain\n",
|
||||
"task": "Optional: change how many times the message appears. Ask how to stop a stuck run."
|
||||
},
|
||||
{
|
||||
"name": "Save a file",
|
||||
"code": "WRITE FILE \"practice.txt\" WITH \"My first file\"\nREAD FILE \"practice.txt\" AS text\nSAY text\n",
|
||||
"input": "",
|
||||
"output": "My first file\n",
|
||||
"task": "Optional: use a new writable practice folder. Find the file after running it."
|
||||
}
|
||||
]
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
rem Run only in a fresh extracted Claro test folder.
|
||||
if exist native.ok del native.ok
|
||||
if not exist osprobe.exe goto wrongos
|
||||
osprobe.exe --require-native >oscheck.txt
|
||||
if errorlevel 1 goto wrongos
|
||||
claro.exe --version >nver.txt
|
||||
if errorlevel 1 goto failed
|
||||
claro.exe test >ntests.txt
|
||||
if errorlevel 1 goto failed
|
||||
claro.exe validate >ncheck.txt
|
||||
if errorlevel 1 goto failed
|
||||
echo Automated native checks passed. Manual console and file checks are still required.
|
||||
echo PASS>native.ok
|
||||
goto end
|
||||
:wrongos
|
||||
echo Native target not verified. Check oscheck.txt and the probe message.
|
||||
goto end
|
||||
:failed
|
||||
echo Claro checks failed. Keep the logs; do not report acceptance.
|
||||
:end
|
||||
@@ -14,7 +14,7 @@ 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']
|
||||
FILES = ['README.md', 'LICENSE', 'CHANGELOG.md', 'CONTRIBUTING.md', 'Makefile', 'build.bat', 'build.ps1', 'build98.bat', 'build98.rsp', 'test98.bat', 'testwin.bat', 'run_tests.bat']
|
||||
FLAGS = ['-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-O2', '-static']
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/* Native-OS evidence helper, not part of the Claro interpreter. */
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
int main(int argc, char **argv) {
|
||||
OSVERSIONINFOA info;
|
||||
HMODULE module;
|
||||
int wine, want98, match98;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.dwOSVersionInfoSize = sizeof(info);
|
||||
if (!GetVersionExA(&info)) {
|
||||
fprintf(stderr, "Could not identify the reported Windows API version.\n");
|
||||
return 3;
|
||||
}
|
||||
module = GetModuleHandleA("ntdll.dll");
|
||||
wine = module && GetProcAddress(module, "wine_get_version") != NULL;
|
||||
want98 = argc > 1 && strcmp(argv[1], "--require-win98se") == 0;
|
||||
match98 = info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS &&
|
||||
info.dwMajorVersion == 4 && info.dwMinorVersion == 10 &&
|
||||
(info.dwBuildNumber & 0xffffUL) == 2222;
|
||||
printf("{\"wine\":%s,\"reported_api_major\":%lu,\"reported_api_minor\":%lu,\"reported_build\":%lu,\"platform_id\":%lu,\"matches_win98se\":%s}\n",
|
||||
wine ? "true" : "false", (unsigned long)info.dwMajorVersion,
|
||||
(unsigned long)info.dwMinorVersion,
|
||||
(unsigned long)(info.dwBuildNumber & 0xffffUL),
|
||||
(unsigned long)info.dwPlatformId, match98 ? "true" : "false");
|
||||
if (wine) {
|
||||
fprintf(stderr, "Wine detected: this is NOT native Windows acceptance.\n");
|
||||
return 2;
|
||||
}
|
||||
if (want98 && !match98) {
|
||||
fprintf(stderr, "This is not the required Windows 98 SE 4.10.2222 target.\n");
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -17,6 +17,10 @@ REQUIRED_COMMANDS = [
|
||||
"python3 tools/validate_c89.py",
|
||||
"python3 tools/validate_msvc6_source.py",
|
||||
"python3 tools/validate_documentation_status.py",
|
||||
"python3 tools/validate_error_locations.py",
|
||||
"python3 tools/validate_error_guide.py",
|
||||
"python3 tools/validate_learner_pilot.py",
|
||||
"python3 tools/validate_resource_guards.py",
|
||||
"python3 tools/validate_ci_workflow.py",
|
||||
]
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ ACTIVE = (
|
||||
'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/ERRORS.md', 'docs/RUNTIME_LIMITS.md', 'docs/LEARNER_PILOT.md',
|
||||
'docs/LEARNER_FEEDBACK_TEMPLATE.md', 'docs/NATIVE_WINDOWS_TESTING.md',
|
||||
'docs/SIX_TRACK_STATUS.md',
|
||||
'docs/history/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md',
|
||||
'docs/history/HARDENING_BEFORE_STATUS_CLEANUP.md',
|
||||
)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Execute every error-guide failure and repair against the actual interpreter."""
|
||||
from pathlib import Path
|
||||
import json, os, re, shlex, subprocess, tempfile
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
def main():
|
||||
cases = json.loads((ROOT/'tests/error_examples.json').read_text())
|
||||
guide = (ROOT/'docs/ERRORS.md').read_text()
|
||||
blocks = re.findall(r'```claro\n(.*?)```',guide,re.S)
|
||||
expected = [c[k] for c in cases for k in ['code','repair']]
|
||||
assert blocks == expected, 'Every error and repair block must match executable coverage'
|
||||
with tempfile.TemporaryDirectory(prefix='claro-error-guide-',dir=os.environ.get('TMPDIR')) as tmp:
|
||||
work=Path(tmp); override=os.environ.get('CLARO_EXE'); exe=Path(override).resolve() if override else work/'claro'
|
||||
if not override:
|
||||
subprocess.run(shlex.split(os.environ.get('CLARO_CC','gcc'))+['-std=c89','-pedantic-errors',str(ROOT/'src/claro.c'),'-o',str(exe),'-lm'],check=True)
|
||||
for case in cases:
|
||||
path=work/case['filename'];path.write_text(case['code'])
|
||||
cmd=[str(exe)]+(['check'] if case['mode']=='check' else [])+[path.name]
|
||||
r=subprocess.run(cmd,cwd=work,capture_output=True,text=True,timeout=10)
|
||||
assert r.returncode==1 and case['diagnostic'] in r.stdout+r.stderr,(case['name'],r.stdout,r.stderr)
|
||||
assert case['diagnostic'] in guide
|
||||
path.write_text(case['repair']);r=subprocess.run([str(exe),path.name],cwd=work,capture_output=True,text=True,timeout=10)
|
||||
assert r.returncode==0 and r.stdout==case['output'],(case['name'],r.stdout,r.stderr)
|
||||
print('PASS error guide:',case['name'])
|
||||
if __name__=='__main__':main()
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify real runtime diagnostic origins, including functions/imports/TRY."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def main():
|
||||
with tempfile.TemporaryDirectory(prefix='claro-error-locations-', dir=os.environ.get('TMPDIR')) as temp:
|
||||
work = Path(temp)
|
||||
override = os.environ.get('CLARO_EXE')
|
||||
exe = Path(override).resolve() if override else work / 'claro'
|
||||
if not override:
|
||||
compiler = shlex.split(os.environ.get('CLARO_CC', 'gcc'))
|
||||
subprocess.run(compiler + ['-std=c89', '-pedantic-errors', str(ROOT / 'src/claro.c'), '-o', str(exe), '-lm'], check=True)
|
||||
cases = [
|
||||
('quote.claro', 'SAY "Start"\nSAY "unfinished\n', 2, 'Text is missing a closing quote.'),
|
||||
('operand.claro', 'SET score 1\nSAY score +\n', 2, 'needs a value after the operator'),
|
||||
('parenthesis.claro', 'SAY "Start"\nSAY (1 + 2\n', 2, 'needs a closing parenthesis'),
|
||||
('function.claro', 'TEACH broken\n SAY 1 +\nEND\nDO broken\n', 2, 'needs a value after the operator'),
|
||||
('repeat.claro', 'REPEAT\n SAY "Once"\nUNTIL (\n', 3, 'needs a value after the operator'),
|
||||
]
|
||||
for name, text, line, message in cases:
|
||||
(work / name).write_text(text)
|
||||
result = subprocess.run([str(exe), name], cwd=work, capture_output=True, text=True, timeout=10)
|
||||
output = result.stdout + result.stderr
|
||||
assert not any(t in output for t in ["AddressSanitizer", "LeakSanitizer", "runtime error:"]), output
|
||||
assert result.returncode == 1, (name, result.returncode, output)
|
||||
assert f'{name}:{line}:' in output and ':0:' not in output and message in output, (name, output)
|
||||
print('PASS origin:', name, line)
|
||||
(work / 'broken-module.claro').write_text('TEACH broken\n SAY 1 +\nEND\n')
|
||||
(work / 'caller.claro').write_text('IMPORT "broken-module.claro" AS problem\nDO problem.broken\n')
|
||||
result = subprocess.run([str(exe), 'caller.claro'], cwd=work, capture_output=True, text=True, timeout=10)
|
||||
assert result.returncode == 1 and 'broken-module.claro:2:' in result.stderr, result.stderr
|
||||
print('PASS origin: imported function')
|
||||
(work / 'caught.claro').write_text('TRY\n DO problem.broken\nCATCH\n SAY LASTERRORFILE\n SAY LASTERRORLINE\nENDTRY\n')
|
||||
(work / 'caller.claro').write_text('IMPORT "broken-module.claro" AS problem\nTRY\n DO problem.broken\nCATCH\n SAY LASTERRORFILE\n SAY LASTERRORLINE\nENDTRY\n')
|
||||
result = subprocess.run([str(exe), 'caller.claro'], cwd=work, capture_output=True, text=True, timeout=10)
|
||||
assert result.returncode == 0 and result.stdout.replace('\\', '/').endswith('broken-module.claro\n2\n'), (result.stdout, result.stderr)
|
||||
print('PASS origin: caught error metadata')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Execute the pilot's six starter tasks, not simulated human observations."""
|
||||
from pathlib import Path
|
||||
import json,os,re,shlex,subprocess,tempfile
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
def main():
|
||||
tasks=json.loads((ROOT/'tests/learner_pilot.json').read_text())
|
||||
blocks=re.findall(r'```claro\n(.*?)```',(ROOT/'docs/LEARNER_PILOT.md').read_text(),re.S)
|
||||
assert blocks==[t['code'] for t in tasks]
|
||||
with tempfile.TemporaryDirectory(prefix='claro-pilot-',dir=os.environ.get('TMPDIR')) as tmp:
|
||||
work=Path(tmp);override=os.environ.get('CLARO_EXE');exe=Path(override).resolve() if override else work/'claro'
|
||||
if not override:subprocess.run(shlex.split(os.environ.get('CLARO_CC','gcc'))+['-std=c89','-pedantic-errors',str(ROOT/'src/claro.c'),'-o',str(exe),'-lm'],check=True)
|
||||
for i,t in enumerate(tasks):
|
||||
p=work/f'task{i}.claro';p.write_text(t['code']);r=subprocess.run([str(exe),p.name],cwd=work,input=t['input'],capture_output=True,text=True,timeout=10)
|
||||
assert r.returncode==0 and r.stdout==t['output'],(t['name'],r.stdout,r.stderr)
|
||||
print('PASS pilot code:',t['name'])
|
||||
assert (work/'practice.txt').read_text()=='My first file'
|
||||
print('Human usability/accessibility: NOT TESTED')
|
||||
if __name__=='__main__':main()
|
||||
@@ -17,7 +17,7 @@ def main():
|
||||
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']:
|
||||
for checker in ['validate_learning_syntax.py', 'validate_first_hour.py', 'validate_error_locations.py', 'validate_error_guide.py', 'validate_learner_pilot.py', 'validate_resource_guards.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)
|
||||
|
||||
@@ -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.223203"
|
||||
EXPECTED_VERSION = "Claro v2026.10.25.014405"
|
||||
|
||||
|
||||
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.223203", "packages:"]:
|
||||
for phrase in ["manifest-version: 1", "name: StarterApp", "main: main.claro", "version: v2026.10.25.014405", "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.223203"]:
|
||||
for phrase in ["lock-version: 1", "version: v2026.10.25.014405"]:
|
||||
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.223203", "packages:"]:
|
||||
for phrase in ["manifest-version: 1", "name: ClaroProject", "main: main.claro", "version: v2026.10.25.014405", "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.223203", "package: math-tools", "checksum:"]:
|
||||
for phrase in ["lock-version: 1", "version: v2026.10.25.014405", "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.223203", "version: v1.18.25", 1)
|
||||
invalid_release_lock = lock.replace("version: v2026.10.25.014405", "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.223203 line" not in out:
|
||||
if "BAD lock release version: expected exactly one version: v2026.10.25.014405 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.223203\npackages:\npackage: ../bad\n",
|
||||
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.25.014405\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.223203\npackages:\npackage: math-tools\npackage: ../bad\n",
|
||||
"manifest-version: 1\nname: ClaroProject\nmain: main.claro\nversion: v2026.10.25.014405\npackages:\npackage: math-tools\npackage: ../bad\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
rc, out = run([str(EXE), "package", "remove", "math-tools"], work)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise bounded malformed JSON, opt-in resource guards and empty loops."""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def main():
|
||||
with tempfile.TemporaryDirectory(prefix='claro-resource-guards-', dir=os.environ.get('TMPDIR')) as tmp:
|
||||
work = Path(tmp)
|
||||
override = os.environ.get('CLARO_EXE')
|
||||
exe = Path(override).resolve() if override else work / 'claro'
|
||||
if not override:
|
||||
subprocess.run(shlex.split(os.environ.get('CLARO_CC', 'gcc')) + ['-std=c89', '-pedantic-errors', str(ROOT/'src/claro.c'), '-o', str(exe), '-lm'], check=True)
|
||||
def run(name, text, error=None, limits=None):
|
||||
(work/name).write_text(text)
|
||||
env = dict(os.environ)
|
||||
for key in ['CLARO_STEP_LIMIT', 'CLARO_READ_FILE_LIMIT']:
|
||||
env.pop(key, None)
|
||||
env.update(limits or {})
|
||||
try:
|
||||
r = subprocess.run([str(exe), name], cwd=work, env=env, capture_output=True, text=True, timeout=8)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise AssertionError(f'{name}: interpreter failed to terminate') from None
|
||||
output = r.stdout+r.stderr
|
||||
assert not any(t in output for t in ["AddressSanitizer", "LeakSanitizer", "runtime error:"]), output
|
||||
assert r.returncode == (1 if error else 0), (name, r.returncode, output)
|
||||
if error:
|
||||
assert error in output and ':0:' not in output, (name, output)
|
||||
print('PASS:', name)
|
||||
return r
|
||||
(work/'invalid.json').write_text('[?]')
|
||||
run('bad-json.claro', 'READ FILE "invalid.json" AS text\nPARSE JSON text AS data\n', 'JSON')
|
||||
for number, text in enumerate(['[1,]', '{"x" 1}', '[1 2]', '"unterminated', '1 extra', '01', '[', '{', '{"x":}', '1e+', '"\\uD800"', '"\\uDC00"', '"\\u12zz"', '"\\u0000"', '"\\q"']):
|
||||
(work/'invalid.json').write_text(text)
|
||||
run(f'malformed-{number}.claro', 'READ FILE "invalid.json" AS text\nPARSE JSON text AS data\n', 'JSON')
|
||||
(work/'nested.json').write_text('['*129+'0'+']'*129)
|
||||
run('deep-json.claro','READ FILE "nested.json" AS text\nPARSE JSON text AS data\n','nesting')
|
||||
(work/'valid.json').write_text('['*128+'0'+']'*128)
|
||||
run('valid-depth.claro','READ FILE "valid.json" AS text\nPARSE JSON text AS data\n')
|
||||
(work/'valid.json').write_text('{"text":"\\u0041\\uD83D\\uDE00", "n": -1.25e2, "ok": true}')
|
||||
r=run('valid-json.claro','READ FILE "valid.json" AS text\nPARSE JSON text AS data\nGET data KEY "text" AS result\nSAY result\n')
|
||||
assert r.stdout=='A😀\n',r.stdout
|
||||
original = {"text": "tab\tnewline\ncontrol\x01quote\"", "values": [1, True, None, "A😀"]}
|
||||
(work/'valid.json').write_text(json.dumps(original))
|
||||
run('roundtrip.claro','READ FILE "valid.json" AS text\nPARSE JSON text AS data\nMAKE JSON data AS encoded\nWRITE FILE "roundtrip.json" WITH encoded\n')
|
||||
assert json.loads((work/'roundtrip.json').read_text()) == original
|
||||
for name, text in [('empty-repeat.claro','REPEAT\nUNTIL NO\n'),('empty-count.claro','DO 1000000000 TIMES\nEND\n'),('empty-for.claro','FOR i FROM 1 TO 1000000000\nEND\n')]:
|
||||
run(name,text,'step limit',{'CLARO_STEP_LIMIT':'20'})
|
||||
run('finite.claro','DO 3 TIMES\nSAY "Yes"\nEND\n',limits={'CLARO_STEP_LIMIT':'20'})
|
||||
run('missing-until.claro','REPEAT\nSAY "Oops"\n','UNTIL')
|
||||
(work/'large.txt').write_bytes(b'x'*1048577)
|
||||
run('read-limit.claro','READ FILE "large.txt" AS data\n','file limit',{'CLARO_READ_FILE_LIMIT':'1048576'})
|
||||
(work/'boundary.txt').write_bytes(b'x'*32)
|
||||
r=run('read-boundary.claro','READ FILE "boundary.txt" AS data\nTEXT LENGTH data AS length\nSAY length\n',limits={'CLARO_READ_FILE_LIMIT':'32'})
|
||||
assert r.stdout=='32\n',r.stdout
|
||||
run('unlimited-read.claro','READ FILE "large.txt" AS data\n')
|
||||
run('caught-budget.claro','TRY\nREPEAT\nUNTIL NO\nCATCH\nSAY "must not escape"\nENDTRY\n','step limit',{'CLARO_STEP_LIMIT':'20'})
|
||||
r=run('long-finite.claro','SET i 0\nREPEAT\nSET i i + 1\nUNTIL i = 20000\nSAY i\n',limits={'CLARO_STEP_LIMIT':'200000'})
|
||||
assert r.stdout=='20000\n',r.stdout
|
||||
run('overflow-limit.claro','SAY "must not execute"\n','CLARO_STEP_LIMIT',{'CLARO_STEP_LIMIT':'999999999999999999999999999999'})
|
||||
run('invalid-limit.claro','SAY "must not execute"\n','CLARO_STEP_LIMIT',{'CLARO_STEP_LIMIT':'-1'})
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
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.223203 networking reliability is ready for use.")
|
||||
print("Validation passed. Claro v2026.10.25.014405 networking reliability is ready for use.")
|
||||
|
||||
@@ -40,12 +40,16 @@ def main():
|
||||
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.223203" not in out:
|
||||
if "Claro v2026.10.25.014405" 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_error_locations.py"])
|
||||
run([sys.executable, "tools/validate_error_guide.py"])
|
||||
run([sys.executable, "tools/validate_learner_pilot.py"])
|
||||
run([sys.executable, "tools/validate_resource_guards.py"])
|
||||
run([sys.executable, "tools/validate_ide_metadata.py"])
|
||||
run([sys.executable, "tools/validate_lsp_helper.py"])
|
||||
run([sys.executable, "tools/validate_concurrency.py"])
|
||||
@@ -55,7 +59,7 @@ def main():
|
||||
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.223203 validation complete")
|
||||
print("v2026.10.25.014405 validation complete")
|
||||
|
||||
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.223203"
|
||||
EXPECTED = "Claro v2026.10.25.014405"
|
||||
|
||||
|
||||
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.223203":
|
||||
fail(f"Expected IDE metadata version 'v2026.10.20.223203', got {metadata.get('version')!r}")
|
||||
if metadata.get("version") != "v2026.10.25.014405":
|
||||
fail(f"Expected IDE metadata version 'v2026.10.25.014405', got {metadata.get('version')!r}")
|
||||
validator = ROOT / "tools" / "validate_v1_17.py"
|
||||
text = validator.read_text(encoding="utf-8")
|
||||
if "Claro v2026.10.20.223203" not in text:
|
||||
fail("tools/validate_v1_17.py must validate Claro v2026.10.20.223203")
|
||||
if "Claro v2026.10.25.014405" not in text:
|
||||
fail("tools/validate_v1_17.py must validate Claro v2026.10.25.014405")
|
||||
print("Version convention validation OK")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user