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
|
run: python3 tools/validate_c89.py
|
||||||
- name: Validate executable learning path
|
- name: Validate executable learning path
|
||||||
run: python3 tools/validate_learning_syntax.py
|
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
|
- name: Validate memory and malformed-input reliability
|
||||||
run: python3 tools/validate_reliability.py
|
run: python3 tools/validate_reliability.py
|
||||||
- name: Validate CI workflow coverage
|
- name: Validate CI workflow coverage
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
# Preserve DOS line endings for the Windows 98 COMMAND.COM build inputs.
|
# Preserve DOS line endings for the Windows 98 COMMAND.COM build inputs.
|
||||||
build98.bat -text whitespace=cr-at-eol
|
build98.bat -text whitespace=cr-at-eol
|
||||||
build98.rsp -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).
|
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
|
## Beginner syntax consistency
|
||||||
|
|
||||||
Follow [the syntax guide](docs/SYNTAX.md) in introductory teaching material.
|
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
|
and small programs. Accessibility for people with learning disabilities is a
|
||||||
core design goal; it still needs testing with learners.
|
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
|
This is a development snapshot, not a newly published release. See
|
||||||
[current status](docs/CURRENT_STATUS.md) and the dated
|
[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.
|
- [Documentation](docs/README.md) — learning guides and reference.
|
||||||
- [Current roadmap](docs/ROADMAP.md) — the small set of active priorities.
|
- [Current roadmap](docs/ROADMAP.md) — the small set of active priorities.
|
||||||
- [Learning-first rules](docs/LEARNING_FIRST.md) — how Claro is designed.
|
- [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.
|
- [Contributing](CONTRIBUTING.md) — source layout, builds, and validation.
|
||||||
|
|
||||||
Existing features and compatibility syntax remain supported; they are not
|
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.
|
**not yet validated** by intended learners. Passing tests does not establish it.
|
||||||
Use [current status](CURRENT_STATUS.md) for scope, not older feature roadmaps.
|
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
|
## Before a session
|
||||||
|
|
||||||
- Use a build whose platform was actually tested; check `claro --version`.
|
- Use a build whose platform was actually tested; check `claro --version`.
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
# Claro Command Line
|
# Claro Command Line
|
||||||
|
|
||||||
Claro v2026.10.20.223203 supports these main commands:
|
Claro v2026.10.25.014405 supports these main commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
claro file.claro
|
claro file.claro
|
||||||
@@ -58,7 +58,7 @@ claro package doctor
|
|||||||
claro package lock
|
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
|
## IDE metadata
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Claro Complete Platform Roadmap
|
# 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.
|
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
|
# 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;
|
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.
|
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.
|
Earlier failure logs are history, not current unresolved findings.
|
||||||
|
|
||||||
Claro runs trusted scripts with the user's permissions. It is **not a sandbox**.
|
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
|
Loops can run indefinitely by default. Opt-in step and READ FILE budgets are
|
||||||
provided. See [actual runtime guards](RUNTIME_LIMITS.md).
|
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
|
## 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
|
## What is next
|
||||||
|
|
||||||
1. Observe actual beginners and improve instructions/error recovery.
|
1. Use the [learner pilot kit](LEARNER_PILOT.md) with a willing person; code checks
|
||||||
2. Verify the Windows builds on their real target operating systems.
|
are not human feedback. Source-located expression diagnostics now have regressions.
|
||||||
3. Review the accumulated local changes and publish only when authorized.
|
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
|
Follow the [small active roadmap](ROADMAP.md). The
|
||||||
[previous detailed status](history/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md) and
|
[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
|
```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
|
```text
|
||||||
SET needs TO.
|
operand.claro:2: This expression needs a value after the operator.
|
||||||
Try:
|
|
||||||
SET name TO "Alex"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
One repair:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
SET score 1
|
||||||
|
SAY score + 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Command
|
||||||
|
|
||||||
|
Save as `command.claro`. Run `claro check command.claro`.
|
||||||
|
|
||||||
|
```claro
|
||||||
|
PRNT "Hello"
|
||||||
|
```
|
||||||
|
|
||||||
|
Message:
|
||||||
|
|
||||||
```text
|
```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
|
```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
|
||||||
./claro check my_program.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
|
# 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.
|
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
|
# 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.
|
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.
|
- [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.
|
- [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
|
## Look something up
|
||||||
|
|
||||||
- [Beginner syntax](SYNTAX.md) and [older compatibility forms](COMPATIBILITY.md)
|
- [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
|
[current status](CURRENT_STATUS.md) for the learning scope and
|
||||||
[the release checklist](FINAL_RELEASE_CHECKLIST.md) before publishing.
|
[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
|
## Immutable artifact checkpoints
|
||||||
|
|
||||||
These are verified build artifacts previously delivered for testing, **not a
|
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.
|
metadata, field storage and object construction so one leak cannot hide another.
|
||||||
Malformed-expression and nesting tests check failure diagnostics and valid inputs.
|
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
|
## Scope
|
||||||
|
|
||||||
A passing run establishes the exercised boundaries, not universal leak freedom,
|
A passing run establishes the exercised boundaries, not universal leak freedom,
|
||||||
|
|||||||
+50
-24
@@ -1,32 +1,58 @@
|
|||||||
# Current runtime guards
|
# Current runtime guards
|
||||||
|
|
||||||
These are implemented guardrails, not a sandbox or a promise that arbitrary
|
Claro runs trusted scripts with the user's permissions. These guards are **not
|
||||||
scripts have bounded resource use.
|
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
|
## Always-on nesting/response guards
|
||||||
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.
|
|
||||||
|
|
||||||
Malformed expressions such as a lone `!`, an unclosed quote/parenthesis,
|
- Function/method calls: at most 256 active calls.
|
||||||
a missing operand, or trailing expression tokens are rejected by the runtime.
|
- Expressions and additional nested control blocks: at most 128 levels.
|
||||||
Use `NOT` for logical negation; `!=` remains a supported comparison.
|
- 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
|
## Not provided
|
||||||
|
|
||||||
The former beta document described `CLARO_STEP_LIMIT`, `CLARO_MAX_FILE_BYTES`,
|
There is no general CPU/time budget, process isolation, total-allocation limit,
|
||||||
and `CLARO_MAX_JSON_BYTES`. Those environment controls are not implemented in
|
global file/output limit or JSON byte-size control. The old beta names
|
||||||
the current `src/claro.c`; do not depend on them. The original document is
|
`CLARO_MAX_FILE_BYTES` and `CLARO_MAX_JSON_BYTES` are still not implemented.
|
||||||
preserved under [history](history/RUNTIME_LIMITS_BETA25.md).
|
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
|
Without the optional step budget ordinary intentional loops can run indefinitely.
|
||||||
not establish a general JSON recursion limit, global file/output-size limit,
|
Run only trusted scripts. See [reliability](RELIABILITY.md),
|
||||||
CPU budget, or untrusted-script protection. Scripts and `RUN COMMAND` execute
|
[errors and repairs](ERRORS.md), and the [historical limits](history/RUNTIME_LIMITS_BETA25.md).
|
||||||
with the user's permissions.
|
|
||||||
|
|
||||||
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**.
|
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
|
```text
|
||||||
Unknown command: SDL
|
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
|
# Claro Language Specification
|
||||||
|
|
||||||
Current package: **Claro v2026.10.20.223203**
|
Current package: **Claro v2026.10.25.014405**
|
||||||
Status: **Living v1 specification**
|
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.
|
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)
|
only after running the target tests there. [Release status](RELEASE_STATUS.md)
|
||||||
records the dated scope; [the checklist](FINAL_RELEASE_CHECKLIST.md) governs publication.
|
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
|
## Historical tools
|
||||||
|
|
||||||
Older `validate_rc*.py` and versioned validators remain for history and focused
|
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
|
## Create a project
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ README.md
|
|||||||
manifest-version: 1
|
manifest-version: 1
|
||||||
name: MyProject
|
name: MyProject
|
||||||
main: main.claro
|
main: main.claro
|
||||||
version: v2026.10.20.223203
|
version: v2026.10.25.014405
|
||||||
packages:
|
packages:
|
||||||
package: text
|
package: text
|
||||||
package: net_tools
|
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
|
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
|
```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.
|
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
|
# 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.
|
Claro currently has beginner HTTP client commands and offline `claro://` test URLs. A web server API is not complete yet.
|
||||||
|
|
||||||
|
|||||||
+244
-96
@@ -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
|
A compact, buildable Claro interpreter focused on the beginner-friendly
|
||||||
teaching core: readable commands, simple expressions, functions, collections,
|
teaching core: readable commands, simple expressions, functions, collections,
|
||||||
@@ -45,7 +45,7 @@ int pclose(FILE *stream);
|
|||||||
#define F_OK 0
|
#define F_OK 0
|
||||||
#endif
|
#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_CALL_DEPTH 256
|
||||||
#define CLARO_MAX_EXPR_DEPTH 128
|
#define CLARO_MAX_EXPR_DEPTH 128
|
||||||
#define CLARO_MAX_BLOCK_DEPTH 128
|
#define CLARO_MAX_BLOCK_DEPTH 128
|
||||||
@@ -574,6 +574,9 @@ typedef struct Runtime {
|
|||||||
char *err_msg;
|
char *err_msg;
|
||||||
char *err_file;
|
char *err_file;
|
||||||
int err_line;
|
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;
|
int returning;
|
||||||
Value ret;
|
Value ret;
|
||||||
int call_depth;
|
int call_depth;
|
||||||
@@ -585,10 +588,42 @@ typedef struct Runtime {
|
|||||||
int script_argc;
|
int script_argc;
|
||||||
char **script_argv;
|
char **script_argv;
|
||||||
} Runtime;
|
} 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) {
|
static void rt_init(Runtime *rt) {
|
||||||
memset(rt, 0, sizeof(*rt));
|
memset(rt, 0, sizeof(*rt));
|
||||||
str_init(&rt->captured);
|
str_init(&rt->captured);
|
||||||
rt->ret = v_none();
|
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);
|
srand(1);
|
||||||
}
|
}
|
||||||
static Var *env_find(Var *v, const char *name) {
|
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);
|
va_start(ap, fmt);
|
||||||
claro_vsnprintf(buf, sizeof(buf), fmt, ap);
|
claro_vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
if (!file || !*file)
|
||||||
|
file = rt->active_file;
|
||||||
|
if (line <= 0)
|
||||||
|
line = rt->active_line;
|
||||||
nfile = normalize_path_copy(file ? file : "");
|
nfile = normalize_path_copy(file ? file : "");
|
||||||
rt->error = 1;
|
rt->error = 1;
|
||||||
rt->err_msg = xstrdup(buf);
|
rt->err_msg = xstrdup(buf);
|
||||||
@@ -1877,134 +1916,195 @@ static int find_done(Program *p, int start) {
|
|||||||
/* ---------- JSON ---------- */
|
/* ---------- JSON ---------- */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *s;
|
const char *s;
|
||||||
|
int depth;
|
||||||
|
const char *error;
|
||||||
} JsonP;
|
} JsonP;
|
||||||
|
#define CLARO_MAX_JSON_DEPTH 128
|
||||||
static void jp_ws(JsonP *j) {
|
static void jp_ws(JsonP *j) {
|
||||||
while (*j->s && isspace((unsigned char)*j->s))
|
while (*j->s && isspace((unsigned char)*j->s))
|
||||||
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) {
|
static char *jp_string(JsonP *j) {
|
||||||
Str b;
|
Str b;
|
||||||
|
char escaped;
|
||||||
str_init(&b);
|
str_init(&b);
|
||||||
jp_ws(j);
|
jp_ws(j);
|
||||||
if (*j->s != '"')
|
if (*j->s != '"') { j->error = "JSON object keys need double quotes."; return xstrdup(""); }
|
||||||
return xstrdup("");
|
|
||||||
j->s++;
|
j->s++;
|
||||||
while (*j->s && *j->s != '"') {
|
while (*j->s && *j->s != '"' && !j->error) {
|
||||||
if (*j->s == '\\') {
|
if (*j->s == '\\') {
|
||||||
j->s++;
|
j->s++;
|
||||||
if (*j->s == 'n')
|
if (!*j->s) { j->error = "JSON text is missing a closing quote."; break; }
|
||||||
str_ch(&b, '\n');
|
escaped = *j->s++;
|
||||||
else if (*j->s == 't')
|
if (escaped == 'n') str_ch(&b, '\n');
|
||||||
str_ch(&b, '\t');
|
else if (escaped == 't') str_ch(&b, '\t');
|
||||||
else if (*j->s == 'r')
|
else if (escaped == 'r') str_ch(&b, '\r');
|
||||||
str_ch(&b, '\r');
|
else if (escaped == 'b') str_ch(&b, '\b');
|
||||||
else if (*j->s)
|
else if (escaped == 'f') str_ch(&b, '\f');
|
||||||
str_ch(&b, *j->s);
|
else if (escaped == 'u') {
|
||||||
if (*j->s)
|
unsigned long code = jp_hex4(j);
|
||||||
j->s++;
|
if (!j->error) jp_utf8(j, &b, code);
|
||||||
} else
|
} else if (escaped == '"' || escaped == '\\' || escaped == '/') str_ch(&b, escaped);
|
||||||
str_ch(&b, *j->s++);
|
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 == '"')
|
if (*j->s == '"') j->s++;
|
||||||
j->s++;
|
else if (!j->error) j->error = "JSON text is missing a closing quote.";
|
||||||
return str_take(&b);
|
return str_take(&b);
|
||||||
}
|
}
|
||||||
static Value jp_value(JsonP *j);
|
static Value jp_value(JsonP *j);
|
||||||
static Value jp_array(JsonP *j) {
|
static Value jp_array(JsonP *j) {
|
||||||
Value arr = v_list();
|
Value arr = v_list();
|
||||||
jp_ws(j);
|
|
||||||
if (*j->s == '[')
|
|
||||||
j->s++;
|
j->s++;
|
||||||
jp_ws(j);
|
jp_ws(j);
|
||||||
if (*j->s == ']') {
|
if (*j->s == ']') { j->s++; return arr; }
|
||||||
j->s++;
|
while (!j->error) {
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
while (*j->s) {
|
|
||||||
Value v = jp_value(j);
|
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);
|
jp_ws(j);
|
||||||
if (*j->s == ',') {
|
if (*j->s == ']') { j->s++; break; }
|
||||||
|
if (*j->s != ',') { j->error = "JSON list needs a comma or closing bracket."; break; }
|
||||||
j->s++;
|
j->s++;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*j->s == ']') {
|
|
||||||
j->s++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
static Value jp_object(JsonP *j) {
|
static Value jp_object(JsonP *j) {
|
||||||
Value obj = v_map();
|
Value obj = v_map();
|
||||||
jp_ws(j);
|
|
||||||
if (*j->s == '{')
|
|
||||||
j->s++;
|
j->s++;
|
||||||
jp_ws(j);
|
jp_ws(j);
|
||||||
if (*j->s == '}') {
|
if (*j->s == '}') { j->s++; return obj; }
|
||||||
j->s++;
|
while (!j->error) {
|
||||||
return obj;
|
char *key = jp_string(j);
|
||||||
}
|
Value v = v_none();
|
||||||
while (*j->s) {
|
|
||||||
char *k = jp_string(j);
|
|
||||||
Value v;
|
|
||||||
jp_ws(j);
|
jp_ws(j);
|
||||||
if (*j->s == ':')
|
if (!j->error && *j->s != ':') j->error = "JSON object needs a colon after its key.";
|
||||||
j->s++;
|
if (!j->error) { j->s++; v = jp_value(j); }
|
||||||
v = jp_value(j);
|
if (!j->error) map_put(obj.map, key, v);
|
||||||
map_put(obj.map, k, v);
|
value_free(v);
|
||||||
free(k);
|
free(key);
|
||||||
|
if (j->error) break;
|
||||||
jp_ws(j);
|
jp_ws(j);
|
||||||
if (*j->s == ',') {
|
if (*j->s == '}') { j->s++; break; }
|
||||||
|
if (*j->s != ',') { j->error = "JSON object needs a comma or closing brace."; break; }
|
||||||
j->s++;
|
j->s++;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (*j->s == '}') {
|
|
||||||
j->s++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
static Value jp_value(JsonP *j) {
|
static Value jp_value(JsonP *j) {
|
||||||
|
const char *scan;
|
||||||
char *end;
|
char *end;
|
||||||
|
double number;
|
||||||
|
Value result;
|
||||||
jp_ws(j);
|
jp_ws(j);
|
||||||
if (*j->s == '"') {
|
if (*j->s == '"') {
|
||||||
char *s = jp_string(j);
|
char *text = jp_string(j);
|
||||||
Value v = v_str(s);
|
result = v_str(text);
|
||||||
free(s);
|
free(text);
|
||||||
return v;
|
return result;
|
||||||
}
|
}
|
||||||
if (*j->s == '{')
|
if (*j->s == '[' || *j->s == '{') {
|
||||||
return jp_object(j);
|
if (j->depth >= CLARO_MAX_JSON_DEPTH) {
|
||||||
if (*j->s == '[')
|
j->error = "JSON nesting exceeds the safe limit of 128. Simplify the data.";
|
||||||
return jp_array(j);
|
|
||||||
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();
|
return v_none();
|
||||||
}
|
}
|
||||||
end = NULL;
|
j->depth++;
|
||||||
{
|
result = *j->s == '[' ? jp_array(j) : jp_object(j);
|
||||||
double d = strtod(j->s, &end);
|
j->depth--;
|
||||||
if (end && end != j->s) {
|
return result;
|
||||||
j->s = end;
|
|
||||||
return v_num(d);
|
|
||||||
}
|
}
|
||||||
|
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 (*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();
|
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;
|
JsonP j;
|
||||||
j.s = s;
|
Value result;
|
||||||
return jp_value(&j);
|
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) {
|
static void json_escape(Str *b, const char *s) {
|
||||||
str_ch(b, '"');
|
str_ch(b, '"');
|
||||||
@@ -2015,6 +2115,12 @@ static void json_escape(Str *b, const char *s) {
|
|||||||
str_add(b, "\\\\");
|
str_add(b, "\\\\");
|
||||||
} else if (*s == '\n') {
|
} else if (*s == '\n') {
|
||||||
str_add(b, "\\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
|
} else
|
||||||
str_ch(b, *s);
|
str_ch(b, *s);
|
||||||
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);
|
exec_range(rt, p, 0, p->count);
|
||||||
}
|
}
|
||||||
rt->import_depth--;
|
rt->import_depth--;
|
||||||
|
free(rt->import_stack[rt->import_depth]);
|
||||||
|
rt->import_stack[rt->import_depth] = NULL;
|
||||||
|
split_args_free(exports, ec);
|
||||||
free(prefix);
|
free(prefix);
|
||||||
}
|
}
|
||||||
static void create_object_value(Runtime *rt, const char *file, int line, const char *cls,
|
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 buf[4096], up[64], w[128];
|
||||||
char *t;
|
char *t;
|
||||||
int pc = *pcp;
|
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) {
|
if (strcmp(up, "REPEAT") == 0) {
|
||||||
int end = match_block(p, pc, "REPEAT", "UNTIL", NULL, NULL);
|
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 {
|
do {
|
||||||
exec_range(rt, p, pc + 1, end);
|
exec_range(rt, p, pc + 1, end);
|
||||||
if (rt->error || rt->returning)
|
if (rt->error || rt->returning)
|
||||||
break;
|
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;
|
*pcp = end < 0 ? pc : end;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3717,12 +3839,21 @@ static void exec_line(Runtime *rt, Program *p, int *pcp, const char *raw) {
|
|||||||
Str b;
|
Str b;
|
||||||
int c;
|
int c;
|
||||||
Value contents;
|
Value contents;
|
||||||
|
unsigned long bytes = 0;
|
||||||
str_init(&b);
|
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);
|
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);
|
fclose(f);
|
||||||
contents = v_str(b.s ? b.s : "");
|
contents = v_str(b.s ? b.s : "");
|
||||||
rt_set(rt, var, contents);
|
if (!rt->error) rt_set(rt, var, contents);
|
||||||
value_free(contents);
|
value_free(contents);
|
||||||
free(b.s);
|
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));
|
char *var = xstrdup(trim_inplace((char *)as + 2));
|
||||||
Value s = eval_expr(rt, ex);
|
Value s = eval_expr(rt, ex);
|
||||||
char *txt = v_to_string(s);
|
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(txt);
|
||||||
free(ex);
|
free(ex);
|
||||||
free(var);
|
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));
|
char *var = xstrdup(trim_inplace((char *)as + 2));
|
||||||
Value v = eval_expr(rt, ex);
|
Value v = eval_expr(rt, ex);
|
||||||
char *js = make_json(v, pretty);
|
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(js);
|
||||||
free(ex);
|
free(ex);
|
||||||
free(var);
|
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);
|
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) {
|
static void exec_range(Runtime *rt, Program *p, int start, int end) {
|
||||||
int pc;
|
int pc;
|
||||||
/* Function calls have their own guard; count additional control-block frames. */
|
/* 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.");
|
"Block nesting exceeds the safe limit of 128. Simplify the nested blocks.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!rt_tick(rt)) return;
|
||||||
rt->range_depth++;
|
rt->range_depth++;
|
||||||
if (end < 0 || end > p->count)
|
if (end < 0 || end > p->count)
|
||||||
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(path, sizeof(path), "%s/claro.project", name);
|
||||||
claro_snprintf(
|
claro_snprintf(
|
||||||
text, sizeof(text),
|
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);
|
name);
|
||||||
write_text_file_simple(path, text);
|
write_text_file_simple(path, text);
|
||||||
claro_snprintf(path, sizeof(path), "%s/claro.lock", name);
|
claro_snprintf(path, sizeof(path), "%s/claro.lock", name);
|
||||||
write_text_file_simple(path,
|
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);
|
claro_snprintf(path, sizeof(path), "%s/README.md", name);
|
||||||
write_text_file_simple(
|
write_text_file_simple(
|
||||||
path, "# My Claro Project\n\nBuild Claro, then run this project with:\n\n```bash\nclaro "
|
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"))
|
if (!file_exists_simple("claro.project"))
|
||||||
write_text_file_simple("claro.project",
|
write_text_file_simple("claro.project",
|
||||||
"manifest-version: 1\nname: ClaroProject\nmain: "
|
"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) {
|
static char *project_value(const char *key) {
|
||||||
char *txt = read_file_text("claro.project");
|
char *txt = read_file_text("claro.project");
|
||||||
@@ -6858,7 +7006,7 @@ static int write_package_lock(void) {
|
|||||||
free(txt);
|
free(txt);
|
||||||
return 0;
|
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;
|
p = txt;
|
||||||
while (*p) {
|
while (*p) {
|
||||||
char *line = p;
|
char *line = p;
|
||||||
@@ -7341,7 +7489,7 @@ static int package_doctor(void) {
|
|||||||
}
|
}
|
||||||
if (txt && !package_lock_release_version_matches(txt)) {
|
if (txt && !package_lock_release_version_matches(txt)) {
|
||||||
printf(
|
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;
|
ok = 0;
|
||||||
}
|
}
|
||||||
free(txt);
|
free(txt);
|
||||||
@@ -7505,7 +7653,7 @@ static int run_package_cmd(int argc, char **argv, int arg) {
|
|||||||
static int print_ide_info(void) {
|
static int print_ide_info(void) {
|
||||||
printf("{\n");
|
printf("{\n");
|
||||||
printf(" \"language\": \"Claro\",\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(" \"file_extensions\": [\".claro\"],\n");
|
||||||
printf(" \"keywords\": "
|
printf(" \"keywords\": "
|
||||||
"[\"SAY\",\"ASK\",\"SET\",\"IF\",\"ELSE\",\"ENDIF\",\"END\",\"REPEAT\",\"UNTIL\","
|
"[\"SAY\",\"ASK\",\"SET\",\"IF\",\"ELSE\",\"ENDIF\",\"END\",\"REPEAT\",\"UNTIL\","
|
||||||
@@ -7855,7 +8003,7 @@ static int run_validate(void) {
|
|||||||
printf(
|
printf(
|
||||||
"%s\n",
|
"%s\n",
|
||||||
fails ? "Validation found problems."
|
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;
|
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]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
DIRECTORIES = ['.forgejo', 'src', 'docs', 'lessons', 'examples', 'lib', 'tests', 'tools', 'assets', 'support']
|
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']
|
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_c89.py",
|
||||||
"python3 tools/validate_msvc6_source.py",
|
"python3 tools/validate_msvc6_source.py",
|
||||||
"python3 tools/validate_documentation_status.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",
|
"python3 tools/validate_ci_workflow.py",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ ACTIVE = (
|
|||||||
'docs/RELEASE_STATUS.md', 'docs/FINAL_RELEASE_CHECKLIST.md',
|
'docs/RELEASE_STATUS.md', 'docs/FINAL_RELEASE_CHECKLIST.md',
|
||||||
'docs/CLASSROOM_READINESS.md', 'docs/TESTING.md', 'docs/HARDENING.md',
|
'docs/CLASSROOM_READINESS.md', 'docs/TESTING.md', 'docs/HARDENING.md',
|
||||||
'docs/PORTABILITY.md', 'docs/RELIABILITY.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/CURRENT_STATUS_BEFORE_STATUS_CLEANUP.md',
|
||||||
'docs/history/HARDENING_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'
|
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)
|
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))
|
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)
|
result = subprocess.run([sys.executable, str(ROOT / 'tools' / checker)], cwd=ROOT, env=env, capture_output=True, text=True, timeout=90)
|
||||||
if result.returncode:
|
if result.returncode:
|
||||||
print(result.stdout + result.stderr)
|
print(result.stdout + result.stderr)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
EXE = ROOT / ("claro.exe" if os.name == "nt" else "claro")
|
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):
|
def fail(message):
|
||||||
@@ -57,11 +57,11 @@ def main():
|
|||||||
fail(out)
|
fail(out)
|
||||||
starter = work / "StarterApp"
|
starter = work / "StarterApp"
|
||||||
starter_project = read(starter / "claro.project")
|
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:
|
if phrase not in starter_project:
|
||||||
fail(f"claro new project file missing {phrase!r}:\n{starter_project}")
|
fail(f"claro new project file missing {phrase!r}:\n{starter_project}")
|
||||||
starter_lock = read(starter / "claro.lock")
|
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:
|
if phrase not in starter_lock:
|
||||||
fail(f"claro new lockfile missing {phrase!r}:\n{starter_lock}")
|
fail(f"claro new lockfile missing {phrase!r}:\n{starter_lock}")
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ def main():
|
|||||||
if rc != 0:
|
if rc != 0:
|
||||||
fail(out)
|
fail(out)
|
||||||
project = read(work / "claro.project")
|
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:
|
if phrase not in project:
|
||||||
fail(f"claro.project missing {phrase!r}:\n{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}")
|
fail(f"Package checksum should be a stable non-placeholder value, got {checksum!r}")
|
||||||
|
|
||||||
lock = read(work / "claro.lock")
|
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:
|
if phrase not in lock:
|
||||||
fail(f"claro.lock missing {phrase!r}:\n{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}")
|
fail(f"Lock format-version diagnostic was unclear:\n{out}")
|
||||||
if read(work / "claro.lock") != invalid_lock:
|
if read(work / "claro.lock") != invalid_lock:
|
||||||
fail("package doctor must not rewrite an invalid lock format version")
|
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")
|
(work / "claro.lock").write_text(invalid_release_lock, encoding="utf-8")
|
||||||
rc, out = run([str(EXE), "package", "doctor"], work)
|
rc, out = run([str(EXE), "package", "doctor"], work)
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
fail("package doctor must reject a lockfile for a different Claro release")
|
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}")
|
fail(f"Lock release-version diagnostic was unclear:\n{out}")
|
||||||
if read(work / "claro.lock") != invalid_release_lock:
|
if read(work / "claro.lock") != invalid_release_lock:
|
||||||
fail("package doctor must not rewrite an invalid lock release version")
|
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")
|
fail("Long package name should not create a package folder")
|
||||||
|
|
||||||
(work / "claro.project").write_text(
|
(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",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
rc, out = run([str(EXE), "package", "doctor"], work)
|
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")
|
fail("package add should not create a package folder while unsafe names remain in claro.project")
|
||||||
|
|
||||||
(work / "claro.project").write_text(
|
(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",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
rc, out = run([str(EXE), "package", "remove", "math-tools"], work)
|
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), "package", "remove", "text"], cwd=proj)
|
||||||
run([str(local), "run"], 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_c89.py"])
|
||||||
run([sys.executable, "tools/validate_msvc6_source.py"])
|
run([sys.executable, "tools/validate_msvc6_source.py"])
|
||||||
out = run([str(EXE), "--version"])
|
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")
|
raise SystemExit("Wrong version string")
|
||||||
run([str(EXE), "test"])
|
run([str(EXE), "test"])
|
||||||
run([str(EXE), "validate"])
|
run([str(EXE), "validate"])
|
||||||
run([sys.executable, "tools/validate_first_hour.py"])
|
run([sys.executable, "tools/validate_first_hour.py"])
|
||||||
run([sys.executable, "tools/validate_learning_syntax.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_ide_metadata.py"])
|
||||||
run([sys.executable, "tools/validate_lsp_helper.py"])
|
run([sys.executable, "tools/validate_lsp_helper.py"])
|
||||||
run([sys.executable, "tools/validate_concurrency.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_version_convention.py"])
|
||||||
run([sys.executable, "tools/validate_documentation_status.py"])
|
run([sys.executable, "tools/validate_documentation_status.py"])
|
||||||
run([str(EXE), "examples/networking.claro"])
|
run([str(EXE), "examples/networking.claro"])
|
||||||
print("v2026.10.20.223203 validation complete")
|
print("v2026.10.25.014405 validation complete")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
EXE = ROOT / "claro.exe"
|
EXE = ROOT / "claro.exe"
|
||||||
EXPECTED = "Claro v2026.10.20.223203"
|
EXPECTED = "Claro v2026.10.25.014405"
|
||||||
|
|
||||||
|
|
||||||
def fail(message):
|
def fail(message):
|
||||||
@@ -31,12 +31,12 @@ def main():
|
|||||||
if ide.returncode != 0:
|
if ide.returncode != 0:
|
||||||
fail(ide.stdout)
|
fail(ide.stdout)
|
||||||
metadata = json.loads(ide.stdout)
|
metadata = json.loads(ide.stdout)
|
||||||
if metadata.get("version") != "v2026.10.20.223203":
|
if metadata.get("version") != "v2026.10.25.014405":
|
||||||
fail(f"Expected IDE metadata version 'v2026.10.20.223203', got {metadata.get('version')!r}")
|
fail(f"Expected IDE metadata version 'v2026.10.25.014405', got {metadata.get('version')!r}")
|
||||||
validator = ROOT / "tools" / "validate_v1_17.py"
|
validator = ROOT / "tools" / "validate_v1_17.py"
|
||||||
text = validator.read_text(encoding="utf-8")
|
text = validator.read_text(encoding="utf-8")
|
||||||
if "Claro v2026.10.20.223203" not in text:
|
if "Claro v2026.10.25.014405" not in text:
|
||||||
fail("tools/validate_v1_17.py must validate Claro v2026.10.20.223203")
|
fail("tools/validate_v1_17.py must validate Claro v2026.10.25.014405")
|
||||||
print("Version convention validation OK")
|
print("Version convention validation OK")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user