Compare commits
58
Commits
9beb0e167e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
114d92063c | ||
|
|
2d0e2a7430 | ||
|
|
c0f72a8d0a | ||
|
|
047c8acda9 | ||
|
|
0b22ebc58b | ||
|
|
8aa1e49658 | ||
|
|
970a446f4e | ||
|
|
e4ded3acc7 | ||
|
|
d668594478 | ||
|
|
a57b51d365 | ||
|
|
437fd30313 | ||
|
|
8183d0a9fa | ||
|
|
eca642d8d8 | ||
|
|
4f0219dd11 | ||
|
|
60978f0fcf | ||
|
|
569b4a3665 | ||
|
|
00f1453ec8 | ||
|
|
0b440109dd | ||
|
|
3fdc5a90f0 | ||
|
|
56b72d8ba5 | ||
|
|
d05465b299 | ||
|
|
51b69bc291 | ||
|
|
3ef86e6479 | ||
|
|
974e2db019 | ||
|
|
3bfe881fba | ||
|
|
d60672f575 | ||
|
|
728292c5f6 | ||
|
|
c4d894315f | ||
|
|
d9a226bd2d | ||
|
|
52a7ca5ff4 | ||
|
|
b77dd4dacc | ||
|
|
d76a2cd6e4 | ||
|
|
8edab412b3 | ||
|
|
f590b01bfe | ||
|
|
b1eba07cf1 | ||
|
|
a4f748b142 | ||
|
|
21cff73185 | ||
|
|
44937b074f | ||
|
|
72a5589a3e | ||
|
|
aae8eb7608 | ||
|
|
878afc8160 | ||
|
|
3eebd6a2b8 | ||
|
|
9622f99fb1 | ||
|
|
6be5088000 | ||
|
|
e652ad0ae3 | ||
|
|
283c3dfcb1 | ||
|
|
b2614a3901 | ||
|
|
e3f1a53130 | ||
|
|
b30cf44b04 | ||
|
|
1c1e3e4607 | ||
|
|
40145e5c74 | ||
|
|
bdc56d4aa1 | ||
|
|
1a2de275d1 | ||
|
|
bcaadf0423 | ||
|
|
5cdb88b30a | ||
|
|
7d081f346c | ||
|
|
cdb5b75c62 | ||
|
|
ba50b3c789 |
@@ -25,6 +25,8 @@ jobs:
|
|||||||
run: python3 tools/validate_package_security.py
|
run: python3 tools/validate_package_security.py
|
||||||
- name: Validate compiler warnings
|
- name: Validate compiler warnings
|
||||||
run: python3 tools/validate_compiler_warnings.py
|
run: python3 tools/validate_compiler_warnings.py
|
||||||
|
- name: Validate trusted command documentation
|
||||||
|
run: python3 tools/validate_trusted_command_docs.py
|
||||||
- name: Validate CI workflow coverage
|
- name: Validate CI workflow coverage
|
||||||
run: python3 tools/validate_ci_workflow.py
|
run: python3 tools/validate_ci_workflow.py
|
||||||
- name: Check lessons
|
- name: Check lessons
|
||||||
|
|||||||
@@ -1,5 +1,46 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### Diagnose missing values in untyped object-field assignments
|
||||||
|
|
||||||
|
- `claro typecheck` now explains how to repair `SET player.score` by adding a value expression after the field name.
|
||||||
|
- Added focused negative coverage to the complete typecheck validation matrix.
|
||||||
|
|
||||||
|
### Validate missing short method-field values
|
||||||
|
|
||||||
|
- Added focused coverage for `SET score NUMBER` inside a method, preserving the learner-facing repair hint that one value expression is required after the type.
|
||||||
|
- Wired the fixture into both typecheck validation gates.
|
||||||
|
- Added the matching compatibility `TAKES` / `LEARNED` fixture so older method syntax receives the same coverage.
|
||||||
|
|
||||||
|
### Diagnose missing `TO` in separated field annotations
|
||||||
|
|
||||||
|
- `claro typecheck` now explains how to repair `SET player.score AS NUMBER` by adding `TO` and a value.
|
||||||
|
- Added focused negative coverage to the complete typecheck validation matrix.
|
||||||
|
|
||||||
|
### Diagnose missing values after short field annotations
|
||||||
|
|
||||||
|
- `claro typecheck` now explains how to repair a short explicitly typed object-field assignment that stops after the type, such as `SET player.score NUMBER`.
|
||||||
|
- Added focused negative coverage to the complete typecheck validation matrix.
|
||||||
|
|
||||||
|
### Diagnose missing values after `AS ... TO` field annotations
|
||||||
|
|
||||||
|
- `claro typecheck` now explains how to repair an explicitly typed object-field assignment that ends after `TO`, such as `SET player.score AS NUMBER TO`.
|
||||||
|
- Added focused negative coverage to the complete typecheck validation matrix.
|
||||||
|
|
||||||
|
### Diagnose extra words after short typed field assignments
|
||||||
|
|
||||||
|
- `claro typecheck` now rejects trailing words after the value in short explicit object-field assignments such as `SET player.score NUMBER 10 extra`.
|
||||||
|
- Added focused negative coverage to the complete typecheck validation matrix and documented the repair hint.
|
||||||
|
|
||||||
|
### Validate explicitly typed YESNO object-field assignments
|
||||||
|
|
||||||
|
- Added positive typecheck coverage for `SET player.ready YESNO YES` when the class declares `HAS ready YESNO`.
|
||||||
|
- Added the fixture to the complete typecheck validation matrix and documented the explicit annotation form.
|
||||||
|
|
||||||
|
### Validate explicitly typed TEXT object-field assignments
|
||||||
|
|
||||||
|
- Added positive typecheck coverage for `SET player.name TEXT "Ada"` when the class declares `HAS name TEXT`.
|
||||||
|
- Added the fixture to the complete typecheck validation matrix and documented the explicit annotation form.
|
||||||
|
|
||||||
### Diagnose missing class field names
|
### Diagnose missing class field names
|
||||||
|
|
||||||
- `claro typecheck` now explains how to repair a bare `HAS` declaration instead of reporting a confusing missing type for an unnamed field.
|
- `claro typecheck` now explains how to repair a bare `HAS` declaration instead of reporting a confusing missing type for an unnamed field.
|
||||||
|
|||||||
@@ -1,59 +1,100 @@
|
|||||||
# Claro v1.18.26
|
# Claro
|
||||||
|
|
||||||
<img src="assets/Claro_Logo.jpg" alt="Claro logo" width="160">
|
<img src="assets/Claro_Logo.jpg" alt="Claro logo" width="160">
|
||||||
|
|
||||||
Claro is a small, readable scripting language designed to help beginners — especially learners with learning disabilities — learn programming without being overwhelmed by punctuation-heavy syntax.
|
**Claro** is a small, readable scripting language for beginners. It is designed to make programming approachable without requiring punctuation-heavy syntax, while still providing a path toward functions, types, objects, files, packages, and networking.
|
||||||
|
|
||||||
Claro stays plain-text first: simple enough to start with `SET name "Jon"`, but able to grow into stronger typed scripts, objects, packages, networking, and tooling over the v1 line. Graphics/SDL work is experimental and not enabled in the stable executable.
|
Current release: **v1.18.26**
|
||||||
|
|
||||||
## Status
|
## Quick start
|
||||||
|
|
||||||
**Current release:** Claro v1.18.26
|
Build Claro with a C99 compiler and `make`:
|
||||||
|
|
||||||
For the current feature map and beginner-safe limits, start with [`docs/CURRENT_STATUS.md`](docs/CURRENT_STATUS.md). The sequenced development priorities are in [`docs/ROADMAP.md`](docs/ROADMAP.md); older release-candidate notes are historical.
|
|
||||||
|
|
||||||
Validated in this package:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./claro --version
|
make
|
||||||
# Claro v1.18.26
|
|
||||||
|
|
||||||
./claro test
|
|
||||||
# PASS: 0 failure(s)
|
|
||||||
|
|
||||||
./claro validate
|
|
||||||
# Validation passed. Claro v1.18.26 networking reliability is ready for use.
|
|
||||||
|
|
||||||
python3 tools/validate_typecheck_diagnostics.py
|
|
||||||
python3 tools/validate_ide_metadata.py
|
|
||||||
python3 tools/validate_version_convention.py
|
|
||||||
python3 tools/validate_package_security.py
|
|
||||||
python3 tools/validate_compiler_warnings.py
|
|
||||||
python3 tools/validate_ci_workflow.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The CI workflow validator also requires its own command to remain in an executable `run` step, so the release-gate check cannot silently disappear from CI. `make` builds both `claro` and `claro.exe`, allowing the version validator to exercise the same source build on every platform.
|
Run a script:
|
||||||
|
|
||||||
## Beginner-first syntax
|
```bash
|
||||||
|
./claro lessons/01_hello.claro
|
||||||
|
./claro examples/quiz.claro
|
||||||
|
```
|
||||||
|
|
||||||
Claro accepts both the sentence-like style and the shorter simple style:
|
Check a script without running it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./claro check examples/quiz.claro
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the built-in test suite and validation checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./claro test
|
||||||
|
./claro validate
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful commands:
|
||||||
|
|
||||||
|
```text
|
||||||
|
claro help
|
||||||
|
claro --version
|
||||||
|
claro doctor
|
||||||
|
claro examples
|
||||||
|
claro repl
|
||||||
|
claro new MyProject
|
||||||
|
claro run
|
||||||
|
claro fmt file.claro
|
||||||
|
claro typecheck file.claro
|
||||||
|
```
|
||||||
|
|
||||||
|
## First Claro program
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
SET name TO "Jon"
|
SAY "Hello from Claro!"
|
||||||
SET name "Jon"
|
|
||||||
SET city to "Edmonton"
|
|
||||||
|
|
||||||
ASK "What is your name?" name
|
ASK "What is your name?" name
|
||||||
SAY "Hello " + name
|
SAY "Hello " + name
|
||||||
|
|
||||||
IF name = "Jon"
|
|
||||||
SAY "Nice name!"
|
|
||||||
END
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Older forms such as `ASK "Name?" AS name`, `ENDIF`, `DONE`, and `LEARNED` still work so older Claro examples do not break.
|
Claro accepts both the short beginner-friendly form and older compatibility forms. For example, these are both valid:
|
||||||
|
|
||||||
## Simple functions
|
```claro
|
||||||
|
SET name "Jon"
|
||||||
|
SET name TO "Jon"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core language features
|
||||||
|
|
||||||
|
### Decisions and loops
|
||||||
|
|
||||||
|
```claro
|
||||||
|
SET score 10
|
||||||
|
|
||||||
|
IF score >= 5
|
||||||
|
SAY "Good job!"
|
||||||
|
ELSE
|
||||||
|
SAY "Try again."
|
||||||
|
END
|
||||||
|
|
||||||
|
DO 3 TIMES
|
||||||
|
SAY "Practice"
|
||||||
|
DONE
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lists and maps
|
||||||
|
|
||||||
|
```claro
|
||||||
|
SET names AS LIST OF TEXT TO LIST
|
||||||
|
ADD "Ada" TO names
|
||||||
|
ADD "Grace" TO names
|
||||||
|
|
||||||
|
FOR EACH name IN names
|
||||||
|
SAY name
|
||||||
|
DONE
|
||||||
|
```
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
TEACH greet name
|
TEACH greet name
|
||||||
@@ -63,30 +104,24 @@ END
|
|||||||
DO greet "Jon"
|
DO greet "Jon"
|
||||||
```
|
```
|
||||||
|
|
||||||
Older function syntax still works:
|
The older compatibility syntax remains supported:
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
TEACH greet TAKES name
|
TEACH greet TAKES name
|
||||||
SAY "Hello " + name
|
RETURN "Hello " + name
|
||||||
LEARNED
|
LEARNED
|
||||||
|
|
||||||
CALL greet WITH "Jon"
|
CALL greet WITH "Jon"
|
||||||
|
SAY RESULT
|
||||||
```
|
```
|
||||||
|
|
||||||
## Objects and classes
|
### Objects and classes
|
||||||
|
|
||||||
Classes can have typed fields and simple methods.
|
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
CLASS Player
|
CLASS Player
|
||||||
HAS name TEXT
|
HAS name TEXT
|
||||||
HAS score NUMBER
|
HAS score NUMBER
|
||||||
|
|
||||||
TEACH show
|
|
||||||
SAY name
|
|
||||||
SAY score
|
|
||||||
END
|
|
||||||
|
|
||||||
TEACH add points
|
TEACH add points
|
||||||
SET score score + points
|
SET score score + points
|
||||||
END
|
END
|
||||||
@@ -95,371 +130,52 @@ END
|
|||||||
NEW Player player
|
NEW Player player
|
||||||
SET player.name "Jon"
|
SET player.name "Jon"
|
||||||
SET player.score 10
|
SET player.score 10
|
||||||
SET player.score player.score + 1
|
|
||||||
|
|
||||||
DO player.show
|
|
||||||
DO player.add 5
|
DO player.add 5
|
||||||
SAY player.score
|
SAY player.score
|
||||||
```
|
```
|
||||||
|
|
||||||
Object helper commands:
|
### Type checking
|
||||||
|
|
||||||
```claro
|
Type annotations are optional for beginners:
|
||||||
OBJECT CLASS player AS kind
|
|
||||||
OBJECT FIELDS player AS fields
|
|
||||||
```
|
|
||||||
|
|
||||||
When a script already declares classes, `claro typecheck` also catches a typo in a `NEW` class name and explains how to repair it:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Class Plaeyr is not known yet. Check the class name or declare CLASS Plaeyr before creating player.
|
|
||||||
```
|
|
||||||
|
|
||||||
Inside a method, an inline field type must agree with the class declaration. For example, `HAS score NUMBER` must not be assigned with `SET score TEXT 10`; `claro typecheck` explains the conflict and suggests `NUMBER`. The same check applies to older `TEACH ... TAKES ...` / `LEARNED` methods, so compatibility lessons get the same feedback.
|
|
||||||
|
|
||||||
If an inline method-field annotation is not a Claro type, `claro typecheck` names the field and method and suggests the class declaration. For example, `SET score AS BANANA TO 10` reports that `BANANA` is unknown and recommends `NUMBER`.
|
|
||||||
The same diagnostic is covered for older `TEACH ... TAKES ...` / `LEARNED` methods, so compatibility lessons do not silently accept misspelled field types.
|
|
||||||
|
|
||||||
## Static type safety
|
|
||||||
|
|
||||||
Beginners can still write the simplest form:
|
|
||||||
|
|
||||||
```claro
|
|
||||||
SET name "Jon"
|
|
||||||
```
|
|
||||||
|
|
||||||
When learners are ready, Claro can protect variables with plain-text types:
|
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
SET score NUMBER 10
|
SET score NUMBER 10
|
||||||
SET name TEXT "Jon"
|
SET name TEXT "Jon"
|
||||||
SET ready YESNO YES
|
SET ready YESNO YES
|
||||||
|
|
||||||
TYPE OF score AS kind
|
|
||||||
SAY kind
|
|
||||||
|
|
||||||
CHECK TYPE score IS NUMBER
|
CHECK TYPE score IS NUMBER
|
||||||
```
|
```
|
||||||
|
|
||||||
`CHECK TYPE` gives a direct repair hint when `IS` is missing:
|
Run static checks with:
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
CHECK TYPE needs IS. Try: CHECK TYPE score IS NUMBER.
|
./claro typecheck examples/type_hardening.claro
|
||||||
```
|
```
|
||||||
|
|
||||||
If extra words follow the result name, `claro typecheck` explains that `TYPE OF` accepts one expression, `AS`, and one result name:
|
Claro provides learner-facing diagnostics for many incorrect variable, function, method, and object-field types. For an explicitly typed object field, the separated form needs both `AS TYPE TO value`, so `SET player.score AS NUMBER` explains how to add `TO` and a value. Type checking is still a focused foundation rather than a complete static type system.
|
||||||
|
|
||||||
```text
|
## Files, JSON, and standard helpers
|
||||||
TYPE OF score AS kind extra
|
|
||||||
TYPE OF score has extra text after result name kind. Keep only the expression, AS, and one result name.
|
|
||||||
```
|
|
||||||
If the learner leaves out the expression as well, the checker explains all three required parts:
|
|
||||||
|
|
||||||
```text
|
Claro includes commands and libraries for practical scripts:
|
||||||
CHECK TYPE needs an expression, IS, and a type. Try: CHECK TYPE score IS NUMBER.
|
|
||||||
```
|
|
||||||
|
|
||||||
If `IS` is present but the expression is missing, Claro points out that the expression belongs before `IS`:
|
|
||||||
|
|
||||||
```text
|
|
||||||
CHECK TYPE needs an expression before IS. Try: CHECK TYPE score IS NUMBER.
|
|
||||||
```
|
|
||||||
|
|
||||||
`TYPE OF` also explains when the result variable is missing its `AS` keyword:
|
|
||||||
|
|
||||||
```text
|
|
||||||
TYPE OF needs AS. Try: TYPE OF score AS kind.
|
|
||||||
```
|
|
||||||
|
|
||||||
If the learner writes `IS` but leaves out the expected type, Claro names the missing piece and shows a complete example:
|
|
||||||
|
|
||||||
```text
|
|
||||||
CHECK TYPE score needs a type after IS. Try: CHECK TYPE score IS NUMBER.
|
|
||||||
```
|
|
||||||
|
|
||||||
If extra words follow the expected type, `claro typecheck` explains that only one type belongs there:
|
|
||||||
|
|
||||||
```text
|
|
||||||
CHECK TYPE score IS NUMBER TEXT
|
|
||||||
CHECK TYPE score has extra text after type NUMBER. Keep only the expression, IS, and one type.
|
|
||||||
```
|
|
||||||
|
|
||||||
If `RETURNS` is present without a type, `claro typecheck` explains what is missing and shows a beginner-friendly repair:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Function greet needs a return type after RETURNS. Add a type such as NUMBER.
|
|
||||||
```
|
|
||||||
|
|
||||||
The same missing-type check covers the older `TEACH ... TAKES ... RETURNS` / `LEARNED` spelling, so compatibility lessons receive the same repair instead of silently accepting an incomplete return declaration.
|
|
||||||
|
|
||||||
The same repair applies to object methods and older compatibility methods, and names the class so the learner can find the declaration:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Method Player.score needs a return type after RETURNS. Add a type such as NUMBER.
|
|
||||||
```
|
|
||||||
|
|
||||||
An empty typed `RETURN` is also rejected in both modern and compatibility functions and methods. Add an expression of the declared type, such as `RETURN amount` for a `NUMBER` return.
|
|
||||||
|
|
||||||
`CHECK TYPE` names a real Claro type. If a type name is misspelled, the checker explains the allowed beginner types instead of reporting a confusing value mismatch:
|
|
||||||
|
|
||||||
```text
|
|
||||||
CHECK TYPE score IS BANANA
|
|
||||||
CHECK TYPE needs a known type such as NUMBER, TEXT, YESNO, LIST, or MAP, but BANANA is not a Claro type.
|
|
||||||
```
|
|
||||||
|
|
||||||
Advanced container checks are available through `claro typecheck`:
|
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
SET names AS LIST OF TEXT TO LIST
|
WRITE FILE "note.txt" WITH "Hello from Claro"
|
||||||
ADD "Ada" TO names
|
READ FILE "note.txt" AS note
|
||||||
|
SAY note
|
||||||
SET scores AS MAP OF NUMBER TO MAP
|
|
||||||
PUT scores KEY "math" VALUE 98
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`claro typecheck` also has an early function-argument diagnostic foundation. A function can state a parameter expectation with `CHECK TYPE`, and calls with the wrong value type or a missing checked argument get a friendly error:
|
Standard-library modules include path and collection helpers:
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
TEACH square amount
|
IMPORT "lib/path.claro" AS path
|
||||||
CHECK TYPE amount IS NUMBER
|
CALL path.join WITH "notes", "today.txt"
|
||||||
SAY amount
|
SAY RESULT
|
||||||
END
|
|
||||||
|
|
||||||
DO square "oops"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
See `docs/STDLIB.md` for the current helper API.
|
||||||
Type mismatch for function square: parameter amount needs NUMBER, but this argument looks like TEXT.
|
|
||||||
Function square needs argument amount as NUMBER, but this call does not provide it.
|
|
||||||
Function greet needs 1 argument, but this call gives 0. Add the missing argument.
|
|
||||||
Function square only accepts 1 argument, but this call gives 2. Remove the extra argument.
|
|
||||||
Function squre is not known yet. Check the function name or add TEACH squre before calling it.
|
|
||||||
```
|
|
||||||
|
|
||||||
If a function repeats a parameter name, `claro typecheck` reports the declaration mistake before the function is called. This applies to both modern `END` functions and compatibility `TAKES` / `LEARNED` functions:
|
## Projects and packages
|
||||||
|
|
||||||
```text
|
Create a project:
|
||||||
Function greet declares parameter name more than once. Give each parameter a different name.
|
|
||||||
```
|
|
||||||
|
|
||||||
Top-level functions must also have unique names. If a script declares `TEACH greet` twice, `claro typecheck` reports `Function greet is declared more than once. Give each function a different name.` Rename one function before calling it. A function declaration must include a name; writing only `TEACH` reports `TEACH needs a function name. Add a name after TEACH, such as TEACH greet.` Inside `CLASS Player`, the same mistake reports `Method Player needs a method name. Add a name after TEACH, such as TEACH show.`
|
|
||||||
|
|
||||||
The same declaration check applies to object methods in both syntax styles. For example, `TEACH greet name, name` inside `CLASS Player` reports `Method Player.greet declares parameter name more than once. Give each parameter a different name.` Duplicate method names are also rejected in both modern `END` methods and compatibility `TAKES` / `LEARNED` methods, with a rename hint before a call can become ambiguous.
|
|
||||||
|
|
||||||
Object methods must also have unique names within their class. If `CLASS Player` declares `TEACH show` twice, `claro typecheck` reports `Method Player.show is declared more than once. Give each method a different name.` Rename one method before calling it.
|
|
||||||
|
|
||||||
Class fields must also have unique names and known types. If `CLASS Player` declares `HAS score NUMBER` twice, or declares the same field with another type, `claro typecheck` reports `Class Player declares field score more than once. Give each field a different name.` A bare `HAS` reports `Class Player needs a field name. Add a name and type after HAS, such as HAS score NUMBER.` If a field omits its type, it reports `Class Player field score is missing a type. Add a type such as NUMBER, TEXT, YESNO, LIST, or MAP after the field name.` If a field uses an unknown type such as `BANANA`, it reports `Class Player field score uses an unknown type BANANA. Use a Claro type such as NUMBER, TEXT, YESNO, LIST, or MAP.` If extra text follows a valid type, it reports `Class Player field score has extra text after type NUMBER. Keep only the field name and one type.` Keep one `HAS` line for each field and use one supported type.
|
|
||||||
|
|
||||||
Class declarations must include a name. A bare `CLASS` reports `CLASS needs a class name. Add a name after CLASS, such as CLASS Player.` Class names must also be unique and use one name only. If a file declares `CLASS Player` twice, `claro typecheck` reports `Class Player is declared more than once. Give each class a different name.` If extra words follow the name, it reports `Class Player has extra text after its name. Keep only the class name after CLASS.` Rename or simplify the declaration before creating its objects. Object names must also be unique within a script: creating `NEW Player player` twice reports `Object player is created more than once. Give each object a different name.` Use a different object name for the second instance. A `NEW` declaration must include both a class name and an object name; bare `NEW` reports `NEW needs a class name. Add a class and object name, such as NEW Player player.`, while `NEW Player` reports `NEW Player needs an object name. Add a name after the class, such as NEW Player player.`
|
|
||||||
Extra words after an object name are also rejected, for example `NEW Player player extra` reports `NEW Player has extra text after object name player. Keep only the class and object names.` Keep each `NEW` declaration to one class name and one object name.
|
|
||||||
|
|
||||||
Simple functions and object methods can also declare a return type. `claro typecheck` checks each `RETURN` expression against it and reports a missing return when a declaration never returns a value:
|
|
||||||
|
|
||||||
```claro
|
|
||||||
TEACH square amount RETURNS NUMBER
|
|
||||||
CHECK TYPE amount IS NUMBER
|
|
||||||
RETURN amount
|
|
||||||
END
|
|
||||||
```
|
|
||||||
|
|
||||||
The same focused check applies to methods such as `Player.score RETURNS NUMBER`; a wrong return reports the full method name, for example: `Type mismatch for return from Player.score: expected NUMBER, but this value looks like TEXT.`
|
|
||||||
|
|
||||||
The older compatibility spelling keeps the same return check for functions and methods. For example, `TEACH square TAKES amount RETURNS NUMBER` with `LEARNED` and `CALL square WITH 4` is checked before it runs, so older lessons get the same type-safety feedback. A compatibility method such as `TEACH score TAKES amount RETURNS NUMBER` inside a class is checked the same way; a correct `CALL player.score WITH 4` path is covered by release validation too.
|
|
||||||
|
|
||||||
If a function or method declares `RETURNS TYPE` but uses `RETURN` without a value, the checker points out the missing expression:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Function square needs a NUMBER value after RETURN. Add a NUMBER expression.
|
|
||||||
```
|
|
||||||
|
|
||||||
The same repair applies to compatibility methods written with `TAKES` / `LEARNED`, so older lessons also receive a method-specific hint instead of a generic type error.
|
|
||||||
|
|
||||||
If a return value has extra space-separated text, `claro typecheck` identifies the first value and explains that `RETURN` accepts one expression:
|
|
||||||
|
|
||||||
```claro
|
|
||||||
TEACH square amount RETURNS NUMBER
|
|
||||||
RETURN amount extra
|
|
||||||
END
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
Function square has extra text after return value amount. Keep only one expression after RETURN.
|
|
||||||
```
|
|
||||||
|
|
||||||
The same focused repair applies to modern and compatibility object methods, and names the complete method such as `Player.total`.
|
|
||||||
|
|
||||||
Compatibility functions using `TAKES` / `LEARNED` receive the same repair when a return value has extra text. For example, `RETURN amount extra` reports `Function square has extra text after return value amount. Keep only one expression after RETURN.`
|
|
||||||
|
|
||||||
If a function or method declares `RETURNS TYPE` but contains no `RETURN`, the checker points out the missing statement:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Function square declares RETURNS NUMBER but has no RETURN statement. Add RETURN with a NUMBER value.
|
|
||||||
```
|
|
||||||
|
|
||||||
Return declarations must use a known Claro type. If the type name is misspelled, the checker explains the supported beginner types:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Function square declares an unknown return type BANANA. Use a Claro type such as NUMBER, TEXT, YESNO, LIST, or MAP.
|
|
||||||
```
|
|
||||||
|
|
||||||
The same check applies to methods, so `Player.score RETURNS BANANA` reports `Method Player.score declares an unknown return type BANANA...` instead of allowing a misspelled type into a class definition. If a return expression cannot be understood yet, the diagnostic names the function or method and the type it needs; this is covered for both modern and older compatibility method syntax. When a NUMBER return contains a known TEXT operand in arithmetic, the checker names the operation and operand, for example: `Type mismatch for return from total: addition needs NUMBER values, but "oops" looks like TEXT.` Complete `IF`/`ELSE` return branches are also accepted inside typed functions and methods, including a nested conditional whose own branches all return; an incomplete path still gets a missing-return diagnostic.
|
|
||||||
|
|
||||||
The same return diagnostic names the arithmetic operation when a NUMBER return mixes in a known TEXT operand, so the learner sees both the operation rule and the offending value instead of only a generic return mismatch. This is covered for addition, subtraction, multiplication, and division in functions and object methods. For example, `Player.total` reports `subtraction needs NUMBER values` when its return expression mixes a checked NUMBER parameter with a TEXT value.
|
|
||||||
|
|
||||||
Claro keywords are case-insensitive here, so `returns NUMBER` is accepted as the same return declaration as `RETURNS NUMBER`. The type checker keeps the return-value diagnostic when a lowercase declaration is used, which helps learners who are still learning Claro's capitalization style.
|
|
||||||
|
|
||||||
The unknown-function diagnostic is validated for both modern `DO squre 4` and compatibility `CALL squre WITH 4` calls. Compatibility calls that leave `WITH` empty, such as `CALL greet WITH`, now count as zero arguments, so learners get the same missing-argument guidance as `DO greet` instead of the checker treating the blank as an argument.
|
|
||||||
|
|
||||||
For functions with more than one checked parameter, Claro reports each mismatched argument with the parameter name:
|
|
||||||
|
|
||||||
```claro
|
|
||||||
TEACH label TAKES name, age
|
|
||||||
CHECK TYPE name IS TEXT
|
|
||||||
CHECK TYPE age IS NUMBER
|
|
||||||
END
|
|
||||||
|
|
||||||
CALL label WITH 7, "old"
|
|
||||||
```
|
|
||||||
|
|
||||||
The same narrow diagnostic foundation now covers simple object method calls when the object was created with `NEW` and the method body uses `CHECK TYPE` for a parameter. Correct calls such as `DO player.add 5` and compatibility calls such as `CALL player.add WITH 5` are covered by validation, and wrong-type calls get a focused learner-facing error. This also works when the checked method appears after another method in the same class, so normal multi-method class examples still get the same wrong-type and extra-argument guidance:
|
|
||||||
|
|
||||||
```claro
|
|
||||||
CLASS Player
|
|
||||||
HAS score NUMBER
|
|
||||||
|
|
||||||
TEACH add points
|
|
||||||
CHECK TYPE points IS NUMBER
|
|
||||||
SET score score + points
|
|
||||||
END
|
|
||||||
END
|
|
||||||
|
|
||||||
NEW Player player
|
|
||||||
DO player.add "five"
|
|
||||||
CALL player.add WITH "five"
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
Type mismatch for method Player.add: parameter points needs NUMBER, but this argument looks like TEXT.
|
|
||||||
Method Player.add needs argument points as NUMBER, but this call does not provide it.
|
|
||||||
Method Player.rename needs 1 argument, but this call gives 0. Add the missing argument.
|
|
||||||
Method Player.add only accepts 1 argument, but this call gives 2. Remove the extra argument.
|
|
||||||
```
|
|
||||||
|
|
||||||
If a learner calls a simple object method before creating the object with `NEW`, `claro typecheck` now points to the missing setup instead of letting the dotted call look like an ordinary unknown function. This covers both the modern `DO player.add 5` form and the older compatibility `CALL player.add WITH 5` form, including `CALL` mistakes where the method name itself is wrong:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object player is not known yet. Create it with NEW ClassName player before calling player.add.
|
|
||||||
Object player is not known yet. Create it with NEW ClassName player before calling player.fly.
|
|
||||||
```
|
|
||||||
|
|
||||||
If the object exists but the class does not declare that method, Claro now names the class and suggests where to add the missing `TEACH` block. This is validated for both modern `DO player.fly 5` and compatibility `CALL player.fly WITH 5` calls:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object Player has no method fly. Check the method name or add TEACH fly inside CLASS Player.
|
|
||||||
```
|
|
||||||
|
|
||||||
For simple object fields created with `NEW Class name`, `claro typecheck` also catches direct wrong-type field assignments such as `SET player.score "ten"` when the class says `HAS score NUMBER`. This remains true if the `HAS` field appears after a simple method in the class, so learners get the field-type error instead of a misleading unknown-field hint:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.
|
|
||||||
```
|
|
||||||
|
|
||||||
It also catches direct assignments to undeclared fields and suggests the matching `HAS` line:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object Player has no field level. Check the field name or add HAS level NUMBER to the class.
|
|
||||||
```
|
|
||||||
|
|
||||||
The same field check follows a simple object alias. After `SET alias player`, assignments through `alias.score` still use the field type declared by `Player`:
|
|
||||||
|
|
||||||
```claro
|
|
||||||
NEW Player player
|
|
||||||
SET alias player
|
|
||||||
SET alias.score "ten"
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
Type mismatch for field alias.score: expected NUMBER, but this value looks like TEXT.
|
|
||||||
```
|
|
||||||
|
|
||||||
The same check follows simple arithmetic and text-concatenation expressions. For example, `SET player.score player.name + 1` now names the text operand and explains that addition into a `NUMBER` field needs numeric values, rather than silently accepting an expression whose result cannot fit the field.
|
|
||||||
|
|
||||||
The checker identifies text operands in arithmetic expressions and explains the operator rule instead of treating an invalid numeric field assignment as an unknown expression:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Type mismatch for field player.score: subtraction needs NUMBER values, but player.name looks like TEXT.
|
|
||||||
Type mismatch for field player.score: addition needs NUMBER values, but player.name looks like TEXT.
|
|
||||||
Type mismatch for field player.score: multiplication needs NUMBER values, but player.name looks like TEXT.
|
|
||||||
Type mismatch for field player.score: division needs NUMBER values, but player.name looks like TEXT.
|
|
||||||
```
|
|
||||||
|
|
||||||
Valid numeric subtraction is covered too:
|
|
||||||
|
|
||||||
```claro
|
|
||||||
SET player.score 10
|
|
||||||
SET player.score player.score - 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The focused typecheck validation keeps this valid subtraction example beside the existing valid multiplication and division examples, so a useful expression is not confused with a nearby text-operand mistake.
|
|
||||||
|
|
||||||
TEXT-valued and YESNO-valued field-name mistakes are validated too:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object Player has no field nickname. Check the field name or add HAS nickname TEXT to the class.
|
|
||||||
Object Player has no field ready. Check the field name or add HAS ready YESNO to the class.
|
|
||||||
```
|
|
||||||
|
|
||||||
If Claro cannot infer the value type for an undeclared field yet, it avoids exposing the internal `ANY` placeholder and keeps the hint beginner-facing:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object Player has no field level. Check the field name or add the field to the class with the right type.
|
|
||||||
```
|
|
||||||
|
|
||||||
Direct object-field `CHECK TYPE` metadata mismatches are validated for NUMBER, TEXT, and YESNO fields too. For example, after `HAS ready YESNO` and `SET player.ready YES`, `CHECK TYPE player.ready IS TEXT` reports:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Type check failed: expected TEXT, but player.ready looks like YESNO.
|
|
||||||
```
|
|
||||||
|
|
||||||
If `CHECK TYPE` names an undeclared field, Claro now gives the same class-and-field hint as direct field assignment. For example, `CHECK TYPE player.level IS NUMBER` after `NEW Player player` reports:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object Player has no field level. Check the field name or add HAS level NUMBER to the class.
|
|
||||||
```
|
|
||||||
|
|
||||||
TEXT expectations are validated with the same pattern, for example `CHECK TYPE player.nickname IS TEXT` reports:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object Player has no field nickname. Check the field name or add HAS nickname TEXT to the class.
|
|
||||||
```
|
|
||||||
|
|
||||||
YESNO expectations are validated too; `CHECK TYPE player.enabled IS YESNO` reports:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object Player has no field enabled. Check the field name or add HAS enabled YESNO to the class.
|
|
||||||
```
|
|
||||||
|
|
||||||
If a learner checks a field before creating the object with `NEW`, `claro typecheck` points to the missing object setup:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object player is not known yet. Create it with NEW ClassName player before checking player.score.
|
|
||||||
```
|
|
||||||
|
|
||||||
The same missing-object guidance is now validated for direct field assignment before `NEW`:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Object player is not known yet. Create it with NEW ClassName player before setting player.score.
|
|
||||||
```
|
|
||||||
|
|
||||||
The checker also names typed method-body field checks and assignments. Inside `Player.rename`, `CHECK TYPE name IS NUMBER` reports `Type check failed in Player.rename: expected NUMBER, but name looks like TEXT.` Inside `Player.add RETURNS NUMBER`, `SET score score + name` reports `Type mismatch for field score in Player.add: addition needs NUMBER values, but name looks like TEXT.` The same metadata check is covered for compatibility `TAKES` / `LEARNED` methods with YESNO fields, so `CHECK TYPE ready IS TEXT` reports `Type check failed in Player.toggle: expected TEXT, but ready looks like YESNO.` The learner sees that the field belongs to the current class method, not an unrelated variable.
|
|
||||||
|
|
||||||
The release validator also runs the modern and compatibility method-body field diagnostic fixtures, including `CHECK TYPE` checks in older `TAKES` / `LEARNED` methods, so these learner-facing errors remain part of `claro validate`, not only the standalone typecheck diagnostic script.
|
|
||||||
|
|
||||||
## Project and package workflow
|
|
||||||
|
|
||||||
v1.18.26 hardens Claro's project/package workflow.
|
|
||||||
|
|
||||||
Create a starter project:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
claro new MyProject
|
claro new MyProject
|
||||||
@@ -467,18 +183,17 @@ cd MyProject
|
|||||||
claro run
|
claro run
|
||||||
```
|
```
|
||||||
|
|
||||||
Manage packages:
|
Manage local project packages:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
claro package init
|
claro package init
|
||||||
claro package add text
|
claro package add text
|
||||||
claro package list
|
claro package list
|
||||||
claro package remove text
|
|
||||||
claro package doctor
|
claro package doctor
|
||||||
claro package lock
|
claro package lock
|
||||||
```
|
```
|
||||||
|
|
||||||
Claro now creates and maintains:
|
Project metadata is stored in:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
claro.project
|
claro.project
|
||||||
@@ -486,113 +201,92 @@ claro.lock
|
|||||||
packages/
|
packages/
|
||||||
```
|
```
|
||||||
|
|
||||||
`claro package doctor` also checks that every listed package has a manifest whose
|
The current package system is local and foundational. Remote registries, publishing, version constraints, and package signature verification are not yet stable features.
|
||||||
`name:` matches the package name in `claro.project`; a mismatch is reported as
|
|
||||||
`BAD package manifest name` instead of being treated as a healthy package.
|
|
||||||
It also compares the complete `checksum:` field, so extra or trailing checksum
|
|
||||||
text, or a duplicate checksum field, is rejected as `BAD package checksum`.
|
|
||||||
Package manifests must contain exactly one `version: 1` field; duplicate or
|
|
||||||
prefix-matching values such as `version: 10` are rejected as `BAD package version`.
|
|
||||||
The project manifest must contain exactly one `manifest-version: 1` field; duplicate
|
|
||||||
or prefix-matching values such as `manifest-version: 10` are rejected as
|
|
||||||
`BAD project manifest version: expected 1`. It must also contain exactly one non-empty `name:` field;
|
|
||||||
otherwise doctor reports `BAD project manifest name: expected a non-empty name`.
|
|
||||||
|
|
||||||
Starter projects created with `claro new MyProject` use the same `manifest-version: 1` and `lock-version: 1` headers as `claro package init`, so the first project files match the package maintenance tools.
|
|
||||||
|
|
||||||
Project names and package names are checked so unsafe names such as `../bad` are rejected before Claro creates folders. Names must also be 64 characters or fewer, which keeps generated project and package paths predictable. If an unsafe package name is already present in `claro.project`, `claro package doctor`, `claro package lock`, `claro package list`, `claro package init`, `claro package add`, and lockfile refreshes during `claro package remove` flag it instead of treating it as safe lockfile data. `claro package remove` can also remove the exact unsafe entry, so a learner can repair a bad project file without Claro using that unsafe name as a folder path. `claro package doctor` also verifies listed package lockfile checksums, reports stale lock entries, and rejects lockfile package entries that are not listed in `claro.project`.
|
|
||||||
|
|
||||||
|
|
||||||
## Standard-library path and collection helpers
|
|
||||||
|
|
||||||
Claro includes tested, namespaced helpers for everyday programs:
|
|
||||||
|
|
||||||
```claro
|
|
||||||
IMPORT "lib/path.claro" AS path
|
|
||||||
IMPORT "lib/collections.claro" AS collections
|
|
||||||
|
|
||||||
CALL path.join WITH "notes", "today.txt"
|
|
||||||
SAY RESULT
|
|
||||||
|
|
||||||
SET names AS LIST OF TEXT TO LIST
|
|
||||||
ADD "Ada" TO names
|
|
||||||
ADD "Grace" TO names
|
|
||||||
CALL collections.join WITH names, " and "
|
|
||||||
SAY RESULT
|
|
||||||
```
|
|
||||||
|
|
||||||
The `path` module includes `join`, `basename`, `dirname`, `ext`, `stem`, and `absolute`. The `collections` module includes list length/search/join/reverse helpers and map key/value/default-access helpers. See `docs/STDLIB.md` for the complete, current API.
|
|
||||||
|
|
||||||
## Networking
|
## Networking
|
||||||
|
|
||||||
v1.18.26 adds safer beginner networking commands with offline `claro://` test URLs.
|
Claro supports beginner-oriented HTTP commands and offline `claro://` test URLs:
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
HTTP CHECK "claro://hello" AS safe
|
|
||||||
SAY safe
|
|
||||||
|
|
||||||
HTTP GET "claro://hello" AS page STATUS status
|
HTTP GET "claro://hello" AS page STATUS status
|
||||||
SAY page
|
SAY page
|
||||||
SAY status
|
SAY status
|
||||||
|
|
||||||
HTTP SAVE "claro://json" TO "network_demo.json" AS saveStatus
|
|
||||||
SAY saveStatus
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The latest HTTP status is also stored in `LASTHTTP`. Real `http://` and `https://` requests use `curl` when available, while `claro://` works offline for lessons and tests.
|
Real HTTP and HTTPS requests use `curl` when available. The offline URLs make networking lessons and tests deterministic.
|
||||||
|
|
||||||
## Useful commands
|
## Current status
|
||||||
|
|
||||||
```bash
|
### Stable foundation
|
||||||
claro help
|
|
||||||
claro --version
|
|
||||||
claro test
|
|
||||||
claro validate
|
|
||||||
claro doctor
|
|
||||||
claro examples
|
|
||||||
claro check examples/quiz.claro
|
|
||||||
claro typecheck examples/type_hardening.claro
|
|
||||||
claro fmt examples/quiz.claro
|
|
||||||
claro repl
|
|
||||||
claro new MyProject
|
|
||||||
claro run
|
|
||||||
claro package init
|
|
||||||
claro package add text
|
|
||||||
claro package list
|
|
||||||
claro package doctor
|
|
||||||
claro ide
|
|
||||||
```
|
|
||||||
|
|
||||||
## Good first scripts
|
- Variables, output, input, expressions, and control flow
|
||||||
|
- Loops, lists, maps, text, JSON, and file operations
|
||||||
|
- Functions and compatibility syntax
|
||||||
|
- Friendly errors and script checking
|
||||||
|
- Path and collection helpers
|
||||||
|
- Offline networking examples
|
||||||
|
|
||||||
```bash
|
### Foundation present
|
||||||
./claro lessons/01_hello.claro
|
|
||||||
./claro examples/quiz.claro
|
- Static type checking
|
||||||
./claro examples/simple_functions.claro
|
- Objects and classes
|
||||||
./claro examples/objects_classes.claro
|
- Local projects and packages
|
||||||
./claro examples/text_polish.claro
|
- Networking beyond the offline test layer
|
||||||
./claro examples/networking.claro
|
- Cooperative tasks/concurrency helpers
|
||||||
```
|
- IDE metadata and completion helpers
|
||||||
|
|
||||||
|
These areas work in focused scenarios but still need broader coverage and polish.
|
||||||
|
|
||||||
|
### Experimental or planned
|
||||||
|
|
||||||
|
- Real SDL graphics and game-oriented graphics support
|
||||||
|
- Web-server syntax and routes
|
||||||
|
- Remote package registry
|
||||||
|
- Full editor extension/LSP support
|
||||||
|
- A complete concurrency scheduler
|
||||||
|
|
||||||
|
Do not treat experimental graphics as a stable cross-platform game runtime yet.
|
||||||
|
|
||||||
## Documentation map
|
## Documentation map
|
||||||
|
|
||||||
If you are new to Claro, start here:
|
Recommended reading order for new learners:
|
||||||
|
|
||||||
1. `docs/QUICK_START.md`
|
1. `docs/QUICK_START.md`
|
||||||
2. `docs/FIRST_HOUR.md`
|
2. `docs/FIRST_HOUR.md`
|
||||||
3. `lessons/README.md`
|
3. `lessons/README.md`
|
||||||
4. `docs/CLI.md`
|
4. `docs/CLI.md`
|
||||||
5. `docs/CURRENT_STATUS.md`
|
5. `docs/SPEC.md`
|
||||||
|
6. `docs/CURRENT_STATUS.md`
|
||||||
|
|
||||||
Feature references by current status:
|
Useful references:
|
||||||
|
|
||||||
- **Ready for beginner lessons:** language reference (`docs/SPEC.md`), friendly errors/checking (`docs/ERRORS.md`, `docs/LINTER.md`, `docs/TESTING.md`), formatter (`docs/FORMATTER.md`), and simple functions (`docs/SIMPLE_FUNCTIONS.md`).
|
- `docs/ERRORS.md` — friendly diagnostics
|
||||||
- **Foundation present, still being polished:** static typing (`docs/ADVANCED_STATIC_TYPING.md`), objects/classes (`docs/V1_15_OBJECTS_CLASSES.md`), local projects/packages (`docs/V1_16_PACKAGES_PROJECTS.md`), HTTP client networking (`docs/V1_17_NETWORKING.md`), cooperative tasks (`docs/CONCURRENCY.md`), and IDE metadata/helper support (`docs/IDE.md`).
|
- `docs/SIMPLE_FUNCTIONS.md` — function syntax
|
||||||
- **Planned or experimental, not stable beginner features yet:** remote package registry (`docs/PACKAGE_REGISTRY.md`), web server API (`docs/WEB_SERVER_PLAN.md`), full editor extension/LSP (`docs/EDITOR_EXTENSION_PLAN.md`), and real SDL graphics (`docs/GRAPHICS.md`, `docs/SDL12.md`).
|
- `docs/STDLIB.md` — standard helpers
|
||||||
- **Roadmaps:** `docs/ROADMAP.md` for the current v1 direction and `docs/COMPLETE_PLATFORM_ROADMAP.md` for the larger platform goal.
|
- `docs/TESTING.md` — test and validation workflow
|
||||||
|
- `docs/FORMATTER.md` — formatting scripts
|
||||||
|
- `docs/IDE.md` — editor metadata and helper support
|
||||||
|
- `docs/ROADMAP.md` — current development direction
|
||||||
|
|
||||||
Historical release notes and validation logs are kept in files such as `docs/RC*_NOTES.md`, `docs/RC*_VALIDATION.md`, and older `docs/V1_*_VALIDATION.md`. They are useful for project history, but they are not the beginner starting path.
|
Historical release notes are kept under `docs/RC*_*.md` and older `docs/V1_*_VALIDATION.md` files. They describe project history rather than the current beginner path.
|
||||||
|
|
||||||
## Release direction
|
## Development
|
||||||
|
|
||||||
Claro should remain in the `v1.xx.yy` line for normal development. A future Claro v2 should mean a full rewrite years later, not an ordinary feature update.
|
The interpreter is implemented in `src/claro.c` and currently builds as a C99 program:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
make test
|
||||||
|
make check
|
||||||
|
```
|
||||||
|
|
||||||
|
The repository includes examples, lessons, validation tools, and a test suite. Before submitting changes, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./claro test
|
||||||
|
./claro validate
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [`LICENSE`](LICENSE) for the project license.
|
||||||
|
|||||||
@@ -16,6 +16,24 @@ TYPE OF needs an expression before AS. Try: TYPE OF score AS kind.
|
|||||||
|
|
||||||
Add the value whose type should be stored, such as `TYPE OF score AS kind`.
|
Add the value whose type should be stored, such as `TYPE OF score AS kind`.
|
||||||
|
|
||||||
|
If the result name is missing after `AS`, `claro typecheck` names the missing piece and shows the complete beginner form:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
TYPE OF score AS
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
TYPE OF needs a result name after AS. Try: TYPE OF score AS kind.
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a result name after `AS`, such as `kind`.
|
||||||
|
|
||||||
|
Explicit short field annotations also need a value after the type. If a learner writes `SET player.score NUMBER` instead of `SET player.score NUMBER 10`, `claro typecheck` gives a repair hint:
|
||||||
|
|
||||||
|
```text
|
||||||
|
SET player.score needs a value after type NUMBER. Add one expression.
|
||||||
|
```
|
||||||
|
|
||||||
Class fields need a type after the field name. If a learner writes `HAS score` instead of `HAS score NUMBER`, `claro typecheck` explains the missing piece:
|
Class fields need a type after the field name. If a learner writes `HAS score` instead of `HAS score NUMBER`, `claro typecheck` explains the missing piece:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -785,6 +803,20 @@ For backward compatibility, a script with no `CLASS` declarations keeps the olde
|
|||||||
|
|
||||||
Claro also has a narrow static diagnostic for direct object-field assignments. If a class declares a typed field and a script creates a simple object with `NEW Class name`, `claro typecheck` remembers the field type:
|
Claro also has a narrow static diagnostic for direct object-field assignments. If a class declares a typed field and a script creates a simple object with `NEW Class name`, `claro typecheck` remembers the field type:
|
||||||
|
|
||||||
|
Keywords are case-insensitive, including object-field declarations and checks. This equivalent beginner example is accepted too:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
class Player
|
||||||
|
has score number
|
||||||
|
end
|
||||||
|
|
||||||
|
new Player player
|
||||||
|
set player.score number 10
|
||||||
|
check type player.score is number
|
||||||
|
```
|
||||||
|
|
||||||
|
The focused validation fixture `tests/typecheck_object_field_lowercase_good.claro` protects this lowercased spelling.
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
CLASS Player
|
CLASS Player
|
||||||
HAS score NUMBER
|
HAS score NUMBER
|
||||||
@@ -794,6 +826,74 @@ NEW Player player
|
|||||||
SET player.score 10
|
SET player.score 10
|
||||||
```
|
```
|
||||||
|
|
||||||
|
An assignment may repeat the field type when the explicit form is easier to read:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
CLASS Player
|
||||||
|
HAS name TEXT
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.name TEXT "Ada"
|
||||||
|
```
|
||||||
|
|
||||||
|
The compatibility-shaped `AS ... TO` form is also accepted when it is clearer to separate the annotation from the value:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
SET player.score AS NUMBER TO 10
|
||||||
|
```
|
||||||
|
|
||||||
|
Both explicit forms must agree with the class field declaration. The separated form also needs `TO` before its value; `SET player.score AS NUMBER` reports `SET player.score needs TO after type NUMBER. Try: SET player.score AS NUMBER TO 10.`
|
||||||
|
|
||||||
|
Each explicit field annotation accepts one value expression only. Extra words are rejected
|
||||||
|
with a repair hint instead of being silently ignored:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
SET player.score AS NUMBER TO 10 extra
|
||||||
|
```
|
||||||
|
|
||||||
|
The separated form also needs a value after `TO`:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
SET player.score AS NUMBER TO
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
SET player.score needs a value after type NUMBER and TO. Add one expression.
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
SET player.score has extra text after value 10. Keep only the field name, type, TO, and one expression.
|
||||||
|
```
|
||||||
|
|
||||||
|
The short explicit form is checked too:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
SET player.score NUMBER 10 extra
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
SET player.score has extra text after value 10. Keep only the field name, type, and one expression.
|
||||||
|
```
|
||||||
|
|
||||||
|
The `AS ... TO` spelling also works inside modern and compatibility object methods:
|
||||||
|
|
||||||
|
```claro
|
||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
|
||||||
|
TEACH set_score
|
||||||
|
SET score AS NUMBER TO 10
|
||||||
|
END
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
The older `TAKES` / `LEARNED` method form accepts the same field annotation. Both forms are covered by focused typecheck validation, so method examples can use either the short annotation or the separated `AS ... TO` spelling.
|
||||||
|
|
||||||
|
The explicit `TEXT` annotation must agree with the class field declaration. This
|
||||||
|
keeps typed field examples consistent with typed variable assignments while
|
||||||
|
still allowing the shorter `SET player.name "Ada"` form.
|
||||||
|
|
||||||
If a learner assigns the wrong value type directly to that known field:
|
If a learner assigns the wrong value type directly to that known field:
|
||||||
|
|
||||||
```claro
|
```claro
|
||||||
@@ -918,7 +1018,7 @@ Output:
|
|||||||
Object player is not known yet. Create it with NEW ClassName player before setting player.score.
|
Object player is not known yet. Create it with NEW ClassName player before setting player.score.
|
||||||
```
|
```
|
||||||
|
|
||||||
Both sides of this narrow field foundation are covered by validation: `tests/typecheck_object_field_good.claro` checks that `SET player.score 10` is accepted for a `HAS score NUMBER` field, `tests/typecheck_object_field_text_good.claro` checks that `SET player.name "Ada"` is accepted for a `HAS name TEXT` field, `tests/typecheck_object_field_yesno_good.claro` checks that `SET player.ready YES` is accepted for a `HAS ready YESNO` field, `tests/typecheck_object_field_unknown_object_bad.claro` checks that `SET player.score 10` before `NEW Player player` reports the missing-object assignment diagnostic, `tests/typecheck_object_field_check_type_unknown_object_bad.claro` checks the matching missing-object `CHECK TYPE` diagnostic, and the remaining object-field fixtures cover known-field mismatches, unknown NUMBER/TEXT/YESNO fields, and unknown-field assignments whose value type is not inferable yet.
|
Both sides of this narrow field foundation are covered by validation: `tests/typecheck_object_field_good.claro` checks that `SET player.score 10` is accepted for a `HAS score NUMBER` field, `tests/typecheck_object_field_typed_good.claro` checks the explicit `NUMBER` annotation, `tests/typecheck_object_field_text_good.claro` checks that `SET player.name "Ada"` is accepted for a `HAS name TEXT` field, `tests/typecheck_object_field_typed_text_good.claro` checks the explicit `TEXT` annotation, `tests/typecheck_object_field_yesno_good.claro` checks that `SET player.ready YES` is accepted for a `HAS ready YESNO` field, and `tests/typecheck_object_field_typed_yesno_good.claro` checks the explicit `YESNO` annotation. `tests/typecheck_object_field_unknown_object_bad.claro` checks that `SET player.score 10` before `NEW Player player` reports the missing-object assignment diagnostic, `tests/typecheck_object_field_check_type_unknown_object_bad.claro` checks the matching missing-object `CHECK TYPE` diagnostic, and the remaining object-field fixtures cover known-field mismatches, unknown NUMBER/TEXT/YESNO fields, and unknown-field assignments whose value type is not inferable yet.
|
||||||
|
|
||||||
This slice is intentionally small: it covers direct `NEW Class object` plus `SET object.field value` cases in one file. Field declarations are collected even when a learner writes a simple method before a later `HAS` field, so the type checker can still report the field's declared type. Broader object flows, aliases, method return checks, and richer object signatures remain future work.
|
This slice is intentionally small: it covers direct `NEW Class object` plus `SET object.field value` cases in one file. Field declarations are collected even when a learner writes a simple method before a later `HAS` field, so the type checker can still report the field's declared type. Broader object flows, aliases, method return checks, and richer object signatures remain future work.
|
||||||
|
|
||||||
|
|||||||
+57
-5
@@ -9,6 +9,44 @@ This file is the beginner-safe status map for the current package. It separates
|
|||||||
- **Experimental/planned**: do not rely on it in beginner lessons yet.
|
- **Experimental/planned**: do not rely on it in beginner lessons yet.
|
||||||
- **Historical**: kept for release history, not current instructions.
|
- **Historical**: kept for release history, not current instructions.
|
||||||
|
|
||||||
|
## Security and correctness review progress
|
||||||
|
|
||||||
|
The v1.18.26 review identified missing-argument reads in standard-library built-ins. The current runtime now validates required argument counts centrally before any builtin indexes `args[]`. Covered calls include `math.abs`, `math.clamp`, `random.seed`, `random.int`, text helpers, CSV helpers, path helpers, and collection helpers. Missing arguments produce a beginner-facing `needs N arguments` runtime error.
|
||||||
|
|
||||||
|
Focused regression coverage: `tests/38_builtin_arity.claro`, `tests/39_random_inverted_range.claro`, and `tests/40_call_depth_guard.claro`.
|
||||||
|
|
||||||
|
The runtime now rejects inverted `random.int` ranges before modulo arithmetic with: `random.int needs the lower bound to be less than or equal to the upper bound.` This prevents invalid ranges from producing incorrect values or a divide-by-zero signal.
|
||||||
|
|
||||||
|
User-defined function and object-method calls now have a documented maximum active call depth of 256. Exceeding it produces: `Claro function call depth exceeded the safe limit of 256. Simplify the recursion or add a stopping condition.` Ordinary non-recursive beginner programs are unaffected.
|
||||||
|
|
||||||
|
Verified in this checkout on 2026-09-23:
|
||||||
|
|
||||||
|
- `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /tmp/claro-call-depth-asan -lm` plus `ASAN_OPTIONS=detect_leaks=0 /tmp/claro-call-depth-asan tests/40_call_depth_guard.claro`: expected diagnostic; no AddressSanitizer or UndefinedBehaviorSanitizer report.
|
||||||
|
- `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /tmp/claro-arity-asan -lm` plus `ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 /tmp/claro-arity-asan tests/38_builtin_arity.claro`: all four missing-argument diagnostics; no sanitizer report.
|
||||||
|
- `make -s all`: rebuilt both `claro` and `claro.exe` from current source.
|
||||||
|
- `./claro test`: `PASS: 0 failure(s)`.
|
||||||
|
- `./claro doctor`: all checks `OK`.
|
||||||
|
- `./claro validate`: validation passed.
|
||||||
|
|
||||||
|
The memory cleanup slices release the previous deep value when a runtime variable or map entry is overwritten, release temporary split argument arrays and strings from `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM`, release loaded program paths, lines, and pointer arrays at the end of each script run, and now release the remaining runtime-owned variables, functions, modules, classes, import paths, captured output, return value, and error strings before the interpreter exits. The expression evaluator now releases discarded intermediate `Value` operands and command boundaries release evaluated `SET`/`SAY` values after copying or printing them. `FOR EACH` now releases its copied collection after iteration, preventing discarded list/map copies from accumulating in long scripts. `IF` and `DO ... TIMES` now release their temporary control-expression values after branch/loop selection. `ASK` now releases evaluated prompt values and temporary input values after converting/copying them into runtime storage. Focused coverage is `tools/validate_memory_cleanup.py`, `tools/validate_expression_cleanup.py`, `tools/validate_control_flow_cleanup.py`, `tools/validate_control_expression_cleanup.py`, and `tools/validate_ask_prompt_cleanup.py`; each focused cleanup validator runs an ASan/UBSan build with LeakSanitizer checking. Verified on 2026-09-24: `python3 tools/validate_ask_prompt_cleanup.py` passes (2,000 prompt/input operations under ASan/UBSan/LSan); `make -s all`, `./claro test`, `./claro doctor`, and `./claro validate` all pass. A malformed-input ASan/UBSan/LSan smoke run did not pass: it reports two leaked `rt_error` message strings (140 bytes total) at `src/claro.c:142`. This unrelated existing diagnostic-path leak remains unaddressed and blocks claiming sanitizer-clean malformed-input handling. The `COUNT ... AS` command now releases its evaluated list/map value after extracting the item count. `python3 tools/validate_count_expression_cleanup.py` first reproduced a 28,000-byte leak across 2,000 list expressions, then passed with LeakSanitizer enabled after the fix. `GET ... AT ... AS ...` now releases its copied collection and empty-result temporary after storing the selected item; `python3 tools/validate_get_expression_cleanup.py` first reproduced 34,000 bytes of leaks across 2,000 empty-list lookups, then passed with LeakSanitizer enabled after the fix. Remaining memory-growth areas include other expression temporaries and command boundaries. The `RUN COMMAND` path remains trusted shell execution, not a sandbox.
|
||||||
|
|
||||||
|
The `FIND ... IN ... AS ...` command now releases its evaluated search value and copied list/map after storing the result. Focused coverage is `tools/validate_find_expression_cleanup.py`; it runs 2,000 searches under ASan/UBSan with LeakSanitizer enabled. Verified on 2026-09-24: `python3 tools/validate_find_expression_cleanup.py` passes with no reported leaks; `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This slice is runtime-verified under sanitizers; broader malformed-input sanitizer coverage remains blocked by the previously documented `rt_error` message leak.
|
||||||
|
|
||||||
|
`REMOVE` now releases its evaluated needle and copied list/map value after updating runtime storage. `python3 tools/validate_remove_expression_cleanup.py` passed with 2,000 repeated string-needle operations under ASan/UBSan/LSan. Removed elements from populated copied lists remain a separate ownership case. This slice is runtime-verified under sanitizers; malformed-input diagnostics still have the previously recorded `rt_error` leak.
|
||||||
|
|
||||||
|
`ADD ... TO ...` now releases both the evaluated item and copied list after `list_add` and `rt_set` have made their owned copies. `python3 tools/validate_add_expression_cleanup.py` first reproduced leaks under ASan/UBSan/LSan (2,000 repeated string concatenations), then passed after the ownership cleanup. Full checks passed: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, all existing focused cleanup validators, and `git diff --check`. This slice is runtime-verified under sanitizers; malformed-input diagnostics still have the previously recorded `rt_error` leak.
|
||||||
|
|
||||||
|
`PUT ... KEY ... VALUE ...` now releases the evaluated map, key, and value copies after runtime storage has copied them. `python3 tools/validate_put_expression_cleanup.py` passes with 2,000 repeated string writes under ASan/UBSan/LSan. Verified 2026-09-26: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, the focused sanitizer validator, and `git diff --check` pass. Separate malformed-argument sanitizer smoke tests for the existing builtin-arity and inverted-range diagnostics do not report out-of-bounds access, but LeakSanitizer reports the already documented `rt_error` diagnostic-string leaks (552 bytes/14 allocations for arity cases; 79 bytes/2 allocations for the inverted-range case). This slice is runtime-verified; those error-path leaks remain a separate blocker to claiming sanitizer-clean diagnostics.
|
||||||
|
|
||||||
|
The `RUN COMMAND` path now decodes POSIX `pclose()` wait status before storing `LASTEXIT`, so a child that exits with code 3 exposes `3` rather than the encoded status 768. Focused coverage is `tests/42_last_exit_code.claro`. HTTP responses now have a 1,048,576-byte cap and marker-like response bodies are preserved while extracting the final HTTP status marker. Focused coverage is `tools/validate_http_hardening.py`. Remaining memory-growth areas include other expression temporaries. Claro remains a trusted-script interpreter, not a sandbox.
|
||||||
|
|
||||||
|
`RUN COMMAND` is documented and validated as a trusted-code capability: it executes shell commands with the user's permissions and is not a sandbox. Claro does not claim untrusted-script safety or use a fragile blacklist sanitizer. Focused documentation coverage is `tools/validate_trusted_command_docs.py`.
|
||||||
|
|
||||||
|
`GET ... KEY ... AS ...` now releases its temporary map and key values after lookup. `python3 tools/validate_get_key_expression_cleanup.py` reproduced 1,004,000 leaked bytes before the change and passes after it with ASan/UBSan/LSan enabled. `EXISTS FILE ... AS ...` now releases its evaluated path value after checking it; `python3 tools/validate_exists_expression_cleanup.py` first reproduced 62,000 leaked bytes across 2,000 calls, then passed with ASan/UBSan/LSan enabled. These are narrow expression-temporary cleanup slices, not a claim that all runtime allocations are leak-free.
|
||||||
|
|
||||||
|
`LIST FOLDER ... AS ...` now releases its evaluated path and the temporary folder-list value after runtime storage copies the list. `python3 tools/validate_list_folder_expression_cleanup.py` reproduced 220,000 leaked bytes across 2,000 operations under ASan/UBSan/LSan before the change and passes after it with no leak report. The working-tree checks `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This is a narrow ownership improvement, not a claim that all runtime allocations are leak-free.
|
||||||
|
The `DELETE FILE ...` and `DELETE FOLDER ...` commands now release their evaluated path values after converting the paths to owned strings. `python3 tools/validate_delete_expression_cleanup.py` first reproduced 178,000 leaked bytes across 2,000 `DELETE FILE` expression evaluations under ASan/UBSan/LSan, then passed after cleanup. This check covers expression ownership; malformed-input diagnostic paths still have the previously documented `rt_error` message leak. The `COPY FILE` and `MOVE FILE` commands now also release their evaluated source/destination path values after conversion to strings; `python3 tools/validate_copy_move_expression_cleanup.py` reproduced 74,000 bytes leaked across 1,000 copy/move pairs before the fix and is the focused ASan/UBSan/LSan regression gate. `CREATE FOLDER ...` now releases its evaluated path value after converting it to an owned path string; `python3 tools/validate_create_folder_expression_cleanup.py` reproduced 200,000 leaked bytes across 2,000 calls before the fix and passes with ASan/UBSan/LSan enabled. This narrow cleanup slice does not establish that all runtime allocations are leak-free.
|
||||||
|
|
||||||
## Feature matrix
|
## Feature matrix
|
||||||
|
|
||||||
### Beginner scripting core
|
### Beginner scripting core
|
||||||
@@ -60,7 +98,14 @@ Ready now:
|
|||||||
- `CHECK TYPE` rejects unknown expected type names with a beginner-facing list of supported types
|
- `CHECK TYPE` rejects unknown expected type names with a beginner-facing list of supported types
|
||||||
- typed function return diagnostics also identify an unknown return expression in compatibility `TAKES` / `LEARNED` functions, matching the modern function and object-method guidance
|
- typed function return diagnostics also identify an unknown return expression in compatibility `TAKES` / `LEARNED` functions, matching the modern function and object-method guidance
|
||||||
- `TYPE OF` reports a direct repair hint when the learner forgets `AS`, for example `TYPE OF score` suggests `TYPE OF score AS kind`
|
- `TYPE OF` reports a direct repair hint when the learner forgets `AS`, for example `TYPE OF score` suggests `TYPE OF score AS kind`
|
||||||
|
- bare `TYPE OF` reports that the expression, `AS`, and result name are all required, with a complete repair example
|
||||||
- `TYPE OF` reports a direct repair hint when the learner forgets the expression before `AS`, for example `TYPE OF AS kind` reports `TYPE OF needs an expression before AS. Try: TYPE OF score AS kind.`
|
- `TYPE OF` reports a direct repair hint when the learner forgets the expression before `AS`, for example `TYPE OF AS kind` reports `TYPE OF needs an expression before AS. Try: TYPE OF score AS kind.`
|
||||||
|
- `TYPE OF` prioritizes the missing expression diagnostic even when extra words follow the incomplete form, so `TYPE OF AS kind extra` still points the learner to the missing expression first.
|
||||||
|
- `TYPE OF` reports a direct repair hint when the learner forgets the result name after `AS`, for example `TYPE OF score AS` reports `TYPE OF needs a result name after AS. Try: TYPE OF score AS kind.`
|
||||||
|
- A valid `TYPE OF score AS kind` statement has a focused positive typecheck fixture, so the complete form is protected from diagnostic-only regression coverage.
|
||||||
|
- `TYPE OF` remains case-insensitive like other Claro keywords, with focused positive coverage for lowercase `type of score as kind` syntax.
|
||||||
|
- `CHECK TYPE` remains case-insensitive like other Claro keywords, with focused positive coverage for lowercase `check type score is number` syntax.
|
||||||
|
- Object declarations and field checks remain case-insensitive like other Claro keywords, with focused positive coverage for lowercase `class`, `has`, `end`, `new`, `set`, and `check type` in one typed object-field example.
|
||||||
- `TYPE OF` rejects extra words after its result name with a direct repair hint, so `TYPE OF score AS kind extra` explains that only one result name belongs there; typed `TEACH ... RETURNS TYPE` declarations likewise reject extra words after the return type with a direct repair hint
|
- `TYPE OF` rejects extra words after its result name with a direct repair hint, so `TYPE OF score AS kind extra` explains that only one result name belongs there; typed `TEACH ... RETURNS TYPE` declarations likewise reject extra words after the return type with a direct repair hint
|
||||||
- typed `TEACH ... RETURNS` declarations reject a missing return type with a direct repair hint, such as `Function greet needs a return type after RETURNS. Add a type such as NUMBER.` The same diagnostic names the complete class and method, including compatibility `TAKES` / `LEARNED` methods: `Method Player.score needs a return type after RETURNS. Add a type such as NUMBER.`
|
- typed `TEACH ... RETURNS` declarations reject a missing return type with a direct repair hint, such as `Function greet needs a return type after RETURNS. Add a type such as NUMBER.` The same diagnostic names the complete class and method, including compatibility `TAKES` / `LEARNED` methods: `Method Player.score needs a return type after RETURNS. Add a type such as NUMBER.`
|
||||||
- typed list/map checks through `claro typecheck`, including a nested `LIST OF MAP` insertion example
|
- typed list/map checks through `claro typecheck`, including a nested `LIST OF MAP` insertion example
|
||||||
@@ -75,10 +120,13 @@ Ready now:
|
|||||||
- class field declarations now reject missing names such as bare `HAS`, missing types such as `HAS score`, unknown types such as `HAS score BANANA`, and extra tokens such as `HAS score NUMBER TEXT` with the field name, class name, supported type examples, and a repair hint before object-field checks use that metadata
|
- class field declarations now reject missing names such as bare `HAS`, missing types such as `HAS score`, unknown types such as `HAS score BANANA`, and extra tokens such as `HAS score NUMBER TEXT` with the field name, class name, supported type examples, and a repair hint before object-field checks use that metadata
|
||||||
- class declarations now reject repeated class names with a direct repair hint, so two `CLASS Player` blocks cannot silently compete for the same name; extra words after a class name also get a direct repair hint instead of becoming part of the class identity
|
- class declarations now reject repeated class names with a direct repair hint, so two `CLASS Player` blocks cannot silently compete for the same name; extra words after a class name also get a direct repair hint instead of becoming part of the class identity
|
||||||
- object creation now rejects repeated object names with a direct repair hint, so two `NEW Player player` statements cannot silently replace one another during type checking; when a script declares at least one class, a misspelled `NEW` class name gets a direct declaration hint without changing the older permissive no-class form; a missing class name or object name gets a direct example repair; extra words after the object name get a direct repair hint instead of being silently ignored
|
- object creation now rejects repeated object names with a direct repair hint, so two `NEW Player player` statements cannot silently replace one another during type checking; when a script declares at least one class, a misspelled `NEW` class name gets a direct declaration hint without changing the older permissive no-class form; a missing class name or object name gets a direct example repair; extra words after the object name get a direct repair hint instead of being silently ignored
|
||||||
- a narrow object-field assignment/check-type check for simple `NEW Class object` plus direct `SET object.field value` and `CHECK TYPE object.field IS TYPE` cases when the class declares `HAS field TYPE`; validation now covers correct NUMBER, TEXT, and YESNO direct assignments, explicitly typed field assignments, explicitly typed method-body assignments to undeclared fields with a `HAS field TYPE` repair hint in modern and compatibility method syntax, dedicated positive NUMBER/TEXT/YESNO `CHECK TYPE` metadata fixtures, simple and chained object aliases for both field assignments and `CHECK TYPE` metadata (including a chained TEXT-field check), aliased object-method calls including chained aliases in modern `DO` and compatibility `CALL ... WITH` forms (with a dedicated positive modern `DO` chained-alias fixture), field-to-field, compound arithmetic field expressions, and arithmetic/text expression result types, negative NUMBER/TEXT/YESNO field `CHECK TYPE` metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type diagnostics, field collection when a `HAS` field appears after a simple method, NUMBER/TEXT/YESNO-valued unknown-field diagnostics for direct assignments to undeclared fields including explicitly typed assignments, numeric and text results from simple arithmetic/text expressions in object-field assignments, plus method-body field assignment and `CHECK TYPE` diagnostics for NUMBER, TEXT, and YESNO fields in modern and compatibility syntax, including compatibility YESNO `CHECK TYPE` metadata coverage, direct method assignments to undeclared fields now produce a beginner-facing missing-field diagnostic when the assigned expression has a known type, and a plain beginner-facing unknown-field diagnostic when the assigned expression is not inferable yet in both modern and compatibility method syntax; method-body `CHECK TYPE` now reports an undeclared bare field with the expected type as a repair hint in both modern and compatibility method syntax, including compatibility `TAKES` / ...; explicitly typed assignments to declared method fields now validate the class-declared field type instead of trusting only the inline type annotation, so a wrong value such as `SET score TEXT "oops"` reports the method and field in the diagnostic
|
- a narrow object-field assignment/check-type check for simple `NEW Class object` plus direct `SET object.field value` and `CHECK TYPE object.field IS TYPE` cases when the class declares `HAS field TYPE`; validation now covers correct NUMBER, TEXT, and YESNO direct assignments, dedicated positive and explicitly typed NUMBER/TEXT field assignments, missing values in untyped direct field assignments with a repair hint, explicitly typed method-body assignments to undeclared fields with a `HAS field TYPE` repair hint in modern and compatibility method syntax, dedicated positive NUMBER/TEXT/YESNO `CHECK TYPE` metadata fixtures, simple and chained object aliases for both field assignments and `CHECK TYPE` metadata (including a chained TEXT-field check), aliased object-method calls including chained aliases in modern `DO` and compatibility `CALL ... WITH` forms (with a dedicated positive modern `DO` chained-alias fixture), field-to-field, compound arithmetic field expressions, and arithmetic/text expression result types, negative NUMBER/TEXT/YESNO field `CHECK TYPE` metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, missing-object field assignment and `CHECK TYPE` diagnostics, NUMBER/TEXT/YESNO wrong-type diagnostics, field collection when a `HAS` field appears after a simple method, NUMBER/TEXT/YESNO-valued unknown-field diagnostics for direct assignments to undeclared fields including explicitly typed assignments, numeric and text results from simple arithmetic/text expressions in object-field assignments, plus method-body field assignment and `CHECK TYPE` diagnostics for NUMBER, TEXT, and YESNO fields in modern and compatibility syntax, including compatibility YESNO `CHECK TYPE` metadata coverage, direct method assignments to undeclared fields now produce a beginner-facing missing-field diagnostic when the assigned expression has a known type, and a plain beginner-facing unknown-field diagnostic when the assigned expression is not inferable yet in both modern and compatibility method syntax; method-body `CHECK TYPE` now reports an unknown field with a repair hint in both method syntaxes, including compatibility `TAKES` / `LEARNED` methods; explicitly typed assignments to declared method fields now validate the class-declared field type instead of trusting only the inline type annotation, so a wrong value such as `SET score TEXT \"oops\"` reports the method and field in the diagnostic.
|
||||||
|
- inline method-field annotations are checked against the class `HAS` declaration in modern and compatibility methods; conflicting known types and unknown annotation names produce field-specific repair guidance, with positive and negative focused fixtures for both syntaxes.
|
||||||
|
- direct object-field `AS ... TO` assignments reject extra words after the value with a repair hint, so `SET player.score AS NUMBER TO 10 extra` cannot silently ignore the trailing text; they explain when `TO` is missing after the type and when the value is missing after `TO`; short explicitly typed assignments such as `SET player.score NUMBER 10 extra` receive the same repair-oriented check and explain when the value is missing after the type
|
||||||
|
- Direct object-field validation also has positive coverage for explicit `NUMBER`, `TEXT`, and `YESNO` annotations, such as `SET player.ready YESNO YES`, plus the compatibility-shaped `AS ... TO` form such as `SET player.score AS NUMBER TO 10`, while the shorter unannotated form remains supported; method-body field assignments have matching positive coverage for both modern and compatibility `TEACH` forms using `SET score AS NUMBER TO 10`, and those two `AS ... TO` method fixtures are now included in `claro validate`; conflicting annotations such as `SET player.score TEXT "oops"` explain the class-declared type and how to repair it, and unknown annotations such as `SET player.score AS BANANA TO 10` or `SET player.score BANANA 10` identify the invalid type and suggest the declared field type.
|
||||||
|
|
||||||
Still needed:
|
Still needed:
|
||||||
- Keep the focused typecheck validator's fixture list complete as new positive and negative examples are added. `claro validate` now executes the complete focused fixture matrix as well, so release validation cannot silently omit a listed typecheck example.
|
- Keep the focused typecheck validator's fixture list complete as new positive and negative examples are added. `claro validate` now executes the complete focused fixture matrix as well, so release validation cannot silently omit a listed typecheck example. Short explicitly typed field assignments inside modern and compatibility methods also have focused coverage: `SET score NUMBER` explains that one value expression is required after the type, matching direct object-field assignment guidance.
|
||||||
- The expression checker now carries a known TEXT operand through all arithmetic operators so object-field and typed-function-return diagnostics do not hide addition, subtraction, multiplication, or division mistakes; focused numeric-subtraction, numeric-division, and numeric-multiplication positives plus text-operand addition, subtraction, multiplication, and division negatives protect this behavior. Typed-function return diagnostics now have focused subtraction and division positives alongside multiplication coverage. Text concatenation into a TEXT field has focused positive fixtures for both operand orders, including concatenation of two TEXT object fields. Each numeric operator names the text operand and explains that it needs NUMBER values.
|
- The expression checker now carries a known TEXT operand through all arithmetic operators so object-field and typed-function-return diagnostics do not hide addition, subtraction, multiplication, or division mistakes; focused numeric-subtraction, numeric-division, and numeric-multiplication positives plus text-operand addition, subtraction, multiplication, and division negatives protect this behavior. Typed-function return diagnostics now have focused subtraction and division positives alongside multiplication coverage. Text concatenation into a TEXT field has focused positive fixtures for both operand orders, including concatenation of two TEXT object fields. Each numeric operator names the text operand and explains that it needs NUMBER values.
|
||||||
- Method return validation now has focused positive subtraction and division fixtures alongside the existing arithmetic return coverage, including compatibility `TAKES` / `LEARNED` subtraction and division cases, so checked NUMBER method parameters and numeric subtraction or division remain accepted by the release validator in both method spellings.
|
- Method return validation now has focused positive subtraction and division fixtures alongside the existing arithmetic return coverage, including compatibility `TAKES` / `LEARNED` subtraction and division cases, so checked NUMBER method parameters and numeric subtraction or division remain accepted by the release validator in both method spellings.
|
||||||
- Modern and compatibility `TAKES` / `LEARNED` methods have positive NUMBER, TEXT, and YESNO field-assignment coverage, and modern plus compatibility method-body `CHECK TYPE` metadata now have positive YESNO coverage beside the wrong-value diagnostics.
|
- Modern and compatibility `TAKES` / `LEARNED` methods have positive NUMBER, TEXT, and YESNO field-assignment coverage, and modern plus compatibility method-body `CHECK TYPE` metadata now have positive YESNO coverage beside the wrong-value diagnostics.
|
||||||
@@ -88,9 +136,7 @@ Still needed:
|
|||||||
- type checking through branches and loops
|
- type checking through branches and loops
|
||||||
- richer object field type checking beyond simple direct assignments
|
- richer object field type checking beyond simple direct assignments
|
||||||
- typed imports/modules
|
- typed imports/modules
|
||||||
- inline method-field annotations must agree with the class `HAS` declaration in both modern `TEACH` / `END` and compatibility `TAKES` / `LEARNED` methods; conflicting annotations now get a repair-oriented diagnostic even when the value's inferred type is otherwise correct.
|
|
||||||
- inline method-field annotations also reject unknown names such as `BANANA`, naming the field and method and suggesting the class-declared type.
|
|
||||||
- compatibility `TEACH ... TAKES ...` / `LEARNED` methods have matching validation for unknown inline field annotations, so older lessons receive the same repair guidance.
|
|
||||||
|
|
||||||
Good starting docs:
|
Good starting docs:
|
||||||
- `ADVANCED_STATIC_TYPING.md`
|
- `ADVANCED_STATIC_TYPING.md`
|
||||||
@@ -254,3 +300,9 @@ Use feature docs with these expectations:
|
|||||||
- **Plans or experiments:** `PACKAGE_REGISTRY.md`, `WEB_SERVER_PLAN.md`, `EDITOR_EXTENSION_PLAN.md`, `GRAPHICS.md`, `SDL12.md`, `COMPLETE_PLATFORM_ROADMAP.md`, `FUTURE_FEATURES_ROADMAP.md`.
|
- **Plans or experiments:** `PACKAGE_REGISTRY.md`, `WEB_SERVER_PLAN.md`, `EDITOR_EXTENSION_PLAN.md`, `GRAPHICS.md`, `SDL12.md`, `COMPLETE_PLATFORM_ROADMAP.md`, `FUTURE_FEATURES_ROADMAP.md`.
|
||||||
|
|
||||||
If a doc sounds more ambitious than this status map, treat this file as the current source of truth and update the older doc before teaching from it.
|
If a doc sounds more ambitious than this status map, treat this file as the current source of truth and update the older doc before teaching from it.
|
||||||
|
|
||||||
|
## 2026-09-28 random.int full-range arithmetic follow-up
|
||||||
|
|
||||||
|
`random.int` now calculates its inclusive range width and result in `long long`, avoiding signed-int overflow for the full accepted C `int` range. The regression fixture is `tests/43_random_int_extreme_bounds.claro`.
|
||||||
|
|
||||||
|
Verified: built with `gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o /home/ubuntu/.hermes/profiles/maxwell/cache/scratch/claro-rng-probe -lm`; ran `ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 /home/ubuntu/.hermes/profiles/maxwell/cache/scratch/claro-rng-probe tests/43_random_int_extreme_bounds.claro` (exit 0, no sanitizer report). Before the change, the same full-range call failed with UBSan signed integer overflow at `src/claro.c:347`. This validates the extreme-range runtime behavior under ASan/UBSan; `make -s all`, `./claro test` (0 failures, including this fixture), `./claro doctor`, `./claro validate`, and `git diff --check` pass.
|
||||||
|
|||||||
@@ -8,3 +8,91 @@ Safety caps (to prevent memory abuse):
|
|||||||
- Maximum program lines: 500,000
|
- Maximum program lines: 500,000
|
||||||
|
|
||||||
If a file exceeds these limits, the loader fails cleanly.
|
If a file exceeds these limits, the loader fails cleanly.
|
||||||
|
|
||||||
|
## Expression-token cleanup
|
||||||
|
|
||||||
|
Each expression now releases its token strings and token-array storage before returning. This is a narrow cleanup boundary; runtime-owned variables, loaded programs, and other allocations remain separate follow-up work.
|
||||||
|
|
||||||
|
Focused verification:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 tools/validate_memory_cleanup.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The validator builds an AddressSanitizer/UndefinedBehaviorSanitizer binary, runs a repeated variable-overwrite probe, and confirms LeakSanitizer no longer reports allocations from `tokenize`/`toks_add`. The interpreter remains a trusted-script runtime, not a sandbox.
|
||||||
|
|
||||||
|
## Overwritten-value cleanup
|
||||||
|
|
||||||
|
Runtime variables and map entries own deep copies of their values. Replacing an existing variable or map entry now releases the previous string, list, or map value before storing its replacement. This is intentionally limited to overwrite boundaries; final runtime teardown remains a follow-up cleanup slice.
|
||||||
|
|
||||||
|
## Split-argument cleanup
|
||||||
|
|
||||||
|
Command argument lists created by `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM` are temporary parser storage. They now share one cleanup helper, so repeated calls do not retain the duplicated argument strings or pointer array. The helper does not change argument evaluation or syntax compatibility.
|
||||||
|
|
||||||
|
Focused verification builds with AddressSanitizer/UndefinedBehaviorSanitizer, repeatedly exercises a four-argument `DO`, and checks the cleanup helper before confirming the existing string, list, and map overwrite behavior.
|
||||||
|
|
||||||
|
The `REMOVE` command now releases its evaluated needle and copied list/map value after updating runtime storage. Focused verification: `python3 tools/validate_remove_expression_cleanup.py` runs 2,000 discarded string needles under ASan/UBSan/LSan; it passed with no reported leaks. This slice does not yet address the separate ownership of an item removed from a populated copied list.
|
||||||
|
|
||||||
|
The `PUT ... KEY ... VALUE ...` command now releases its evaluated map, key, and value copies after `map_put`/`rt_set` have copied the data they own. Focused verification: `python3 tools/validate_put_expression_cleanup.py` exercises 2,000 string-valued writes under ASan/UBSan/LSan and passes with no reported leaks. This covers expression-temporary ownership only; broader malformed-input sanitizer runs still report the previously documented `rt_error` diagnostic-string leaks.
|
||||||
|
|
||||||
|
## HTTP response handling
|
||||||
|
|
||||||
|
HTTP responses are capped at 1,048,576 bytes. Exceeding the cap produces a beginner-facing runtime error instead of retaining an unbounded response. The curl status suffix is taken from the final status marker, so a response body containing marker-like text is preserved. Existing `HTTP CHECK` URL safety rules remain unchanged.
|
||||||
|
|
||||||
|
Focused verification:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 tools/validate_http_hardening.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This validator uses a local HTTP server to check marker-like response text, status `200`, and the oversized-response diagnostic.
|
||||||
|
|
||||||
|
## External command trust boundary
|
||||||
|
|
||||||
|
`RUN COMMAND` intentionally executes a shell command with the user's permissions. It is a trusted-code capability, not a sandbox or an untrusted-script safety feature. Claro does not attempt a fragile blacklist sanitizer; users must review scripts before running them.
|
||||||
|
|
||||||
|
Focused documentation verification:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 tools/validate_trusted_command_docs.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Control-flow expression cleanup
|
||||||
|
|
||||||
|
Conditions evaluated by `IF` are temporary runtime values. The `IF` command now
|
||||||
|
releases its condition after choosing a branch, including when the condition is
|
||||||
|
a text expression. This is a narrow cleanup boundary; other expression
|
||||||
|
temporaries remain separate follow-up work.
|
||||||
|
|
||||||
|
Focused verification:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 tools/validate_control_expression_cleanup.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The validator builds with AddressSanitizer/UndefinedBehaviorSanitizer,
|
||||||
|
executes 2,000 temporary `IF` conditions under LeakSanitizer, and checks the
|
||||||
|
expected output.
|
||||||
|
|
||||||
|
## ASK temporary-value cleanup
|
||||||
|
|
||||||
|
`ASK` releases the evaluated prompt value after converting it to display text,
|
||||||
|
and releases the temporary input value after `rt_set_checked` copies it into
|
||||||
|
runtime storage. This keeps prompt and input strings from accumulating during
|
||||||
|
repeated input loops without changing prompt or input behavior.
|
||||||
|
|
||||||
|
Focused verification:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 tools/validate_ask_prompt_cleanup.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The validator runs 2,000 prompt/input operations with AddressSanitizer, UndefinedBehaviorSanitizer, and LeakSanitizer enabled. The dedicated validator passes. A separate malformed-input sanitizer smoke run (`gcc -std=c99 -O0 -g -fsanitize=address,undefined src/claro.c -o ... -lm` followed by the generated malformed script) exits 1 due to two existing leaked error-message strings (140 bytes total) allocated in `rt_error` at `src/claro.c:142`; this is outside the ASK cleanup slice and remains a blocker to sanitizer-clean malformed-input validation.
|
||||||
|
|
||||||
|
## GET KEY temporary-value cleanup
|
||||||
|
|
||||||
|
`GET ... KEY ... AS ...` releases its evaluated map copy and key value after storing the selected value. `python3 tools/validate_get_key_expression_cleanup.py` runs 2,000 lookups under ASan/UBSan/LSan; before the cleanup it reproduced 1,004,000 bytes leaked, and after it passes with the expected output and no reported leaks.
|
||||||
|
|
||||||
|
## COPY/MOVE expression temporary cleanup
|
||||||
|
|
||||||
|
`COPY FILE ... TO ...` and `MOVE FILE ... TO ...` release both evaluated path values after converting them to owned path strings. `python3 tools/validate_copy_move_expression_cleanup.py` runs 1,000 copy/move pairs under ASan/UBSan/LSan; before the cleanup it reproduced 74,000 bytes leaked across 4,000 path-expression values, and after it passes with no reported leaks.
|
||||||
|
|||||||
@@ -64,3 +64,5 @@ SAY LASTEXIT
|
|||||||
```
|
```
|
||||||
|
|
||||||
Use this carefully. It runs commands on the user's computer.
|
Use this carefully. It runs commands on the user's computer.
|
||||||
|
|
||||||
|
`RUN COMMAND` is for trusted code only. It is not a sandbox: it can start programs, read or change files, and use the same permissions as the user running Claro. Do not run scripts from an untrusted source, and do not treat this feature as a security boundary.
|
||||||
|
|||||||
+19
-3
@@ -34,8 +34,8 @@ See `CURRENT_STATUS.md` for the detailed feature matrix.
|
|||||||
3. Expand validation around typecheck diagnostics and package/networking safety. Current Forgejo/Gitea CI runs the documented release gates (`claro validate`, typecheck diagnostics validation, version convention validation, package security validation, compiler warning validation, and CI workflow coverage validation). The compiler warning validator rebuilds `src/claro.c` and blocks path-truncation warnings; the workflow coverage validator checks executable `run` steps rather than comments and requires its own command. Current package safety validation covers safe project creation, rejecting unsafe project names during `claro new`, safe manifest/lockfile creation, rejecting unsafe package names during `package add`, detecting unsafe package names already present in `claro.project` during `package doctor`, refusing to write lockfile data for unsafe package names during `package lock`, making `package list` fail instead of displaying unsafe package entries as normal package names, making `package init` fail instead of reporting the project ready when unsafe package names are already present, making `package add` fail before changing files when unsafe package names are already present, making `package remove` fail instead of reporting success when unsafe package names remain during lockfile refresh, allowing `package remove` to remove an exact unsafe package entry so learners can repair a bad `claro.project`, making `package doctor` reject stale lockfile checksums for listed packages, and making `package doctor` reject lockfile package entries that are not listed in `claro.project`. Current function validation covers correct checked calls, wrong-type arguments, missing checked-argument diagnostics, missing unchecked-argument diagnostics for simple functions in both modern `DO greet` and empty compatibility `CALL greet WITH` forms, extra-argument diagnostics, and beginner-facing unknown-function diagnostics for mistyped modern `DO` and compatibility `CALL ... WITH` calls; current object-method parameter validation covers correct modern `DO object.method ...` and compatibility `CALL object.method WITH ...` checked calls, wrong-type diagnostics for both call forms, checked methods after another method in the same class, missing and extra checked-argument diagnostics including the extra-argument case for a checked method after another method in the same class, missing unchecked-argument diagnostics for simple object methods, missing-object guidance when `DO player.method ...` or compatibility `CALL object.method WITH ...` appears before `NEW` even if the method name is also wrong, and class-specific unknown-method diagnostics for both modern and compatibility calls when a learner calls a method the class does not declare; current object-field validation covers direct NUMBER/TEXT/YESNO field-assignment positives, direct object-field `CHECK TYPE` metadata positives for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO field metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, direct wrong-type field assignment diagnostics, field collection when a `HAS` field appears after a simple method, direct unknown-field diagnostics for NUMBER/TEXT/YESNO values, a beginner-facing fallback for unknown fields assigned from expressions whose type is not inferable yet, and missing-object diagnostics for both `SET object.field value` and `CHECK TYPE object.field IS TYPE` before `NEW`.
|
3. Expand validation around typecheck diagnostics and package/networking safety. Current Forgejo/Gitea CI runs the documented release gates (`claro validate`, typecheck diagnostics validation, version convention validation, package security validation, compiler warning validation, and CI workflow coverage validation). The compiler warning validator rebuilds `src/claro.c` and blocks path-truncation warnings; the workflow coverage validator checks executable `run` steps rather than comments and requires its own command. Current package safety validation covers safe project creation, rejecting unsafe project names during `claro new`, safe manifest/lockfile creation, rejecting unsafe package names during `package add`, detecting unsafe package names already present in `claro.project` during `package doctor`, refusing to write lockfile data for unsafe package names during `package lock`, making `package list` fail instead of displaying unsafe package entries as normal package names, making `package init` fail instead of reporting the project ready when unsafe package names are already present, making `package add` fail before changing files when unsafe package names are already present, making `package remove` fail instead of reporting success when unsafe package names remain during lockfile refresh, allowing `package remove` to remove an exact unsafe package entry so learners can repair a bad `claro.project`, making `package doctor` reject stale lockfile checksums for listed packages, and making `package doctor` reject lockfile package entries that are not listed in `claro.project`. Current function validation covers correct checked calls, wrong-type arguments, missing checked-argument diagnostics, missing unchecked-argument diagnostics for simple functions in both modern `DO greet` and empty compatibility `CALL greet WITH` forms, extra-argument diagnostics, and beginner-facing unknown-function diagnostics for mistyped modern `DO` and compatibility `CALL ... WITH` calls; current object-method parameter validation covers correct modern `DO object.method ...` and compatibility `CALL object.method WITH ...` checked calls, wrong-type diagnostics for both call forms, checked methods after another method in the same class, missing and extra checked-argument diagnostics including the extra-argument case for a checked method after another method in the same class, missing unchecked-argument diagnostics for simple object methods, missing-object guidance when `DO player.method ...` or compatibility `CALL object.method WITH ...` appears before `NEW` even if the method name is also wrong, and class-specific unknown-method diagnostics for both modern and compatibility calls when a learner calls a method the class does not declare; current object-field validation covers direct NUMBER/TEXT/YESNO field-assignment positives, direct object-field `CHECK TYPE` metadata positives for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO field metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, direct wrong-type field assignment diagnostics, field collection when a `HAS` field appears after a simple method, direct unknown-field diagnostics for NUMBER/TEXT/YESNO values, a beginner-facing fallback for unknown fields assigned from expressions whose type is not inferable yet, and missing-object diagnostics for both `SET object.field value` and `CHECK TYPE object.field IS TYPE` before `NEW`.
|
||||||
3a. Keep package diagnostics actionable: invalid package manifest format fields now name the file and explain how to repair it, separately from an absent manifest. Keep package validation honest by checking that the project manifest has one non-empty project name, that the project and each listed package manifest declare the exact supported manifest version, package version, local source, and expected package name, and that lockfiles declare exactly one current release version.
|
3a. Keep package diagnostics actionable: invalid package manifest format fields now name the file and explain how to repair it, separately from an absent manifest. Keep package validation honest by checking that the project manifest has one non-empty project name, that the project and each listed package manifest declare the exact supported manifest version, package version, local source, and expected package name, and that lockfiles declare exactly one current release version.
|
||||||
3b. Keep package security validation in Forgejo/Gitea CI so unsafe names, exact manifest-version/name/version/checksum mismatches, missing manifests, duplicate project manifest versions and packages, duplicate lock packages, duplicate package manifest names, versions, and checksums, and lockfile errors remain release blockers.
|
3b. Keep package security validation in Forgejo/Gitea CI so unsafe names, exact manifest-version/name/version/checksum mismatches, missing manifests, duplicate project manifest versions and packages, duplicate lock packages, duplicate package manifest names, versions, and checksums, and lockfile errors remain release blockers.
|
||||||
4. Add small examples for each foundation feature before adding bigger syntax. The object-field foundation now includes positive and negative validation for field expressions such as `SET player.score player.name`, `SET player.score player.score + 1`, `SET player.name player.score + 1`, explicitly typed field assignments, explicit typed assignments to undeclared fields, simple and chained aliases such as `SET alias player`, `SET backup alias`, followed by `SET backup.score ...` or `CHECK TYPE backup.score IS ...`, chained aliases in object-method calls through both modern `DO` and compatibility `CALL ... WITH` forms (including a dedicated positive modern `DO` fixture), and arithmetic/text expression mismatches; typed containers also accept a map with nested type metadata when it is added to a `LIST OF MAP`; broader alias/control-flow checking remains planned.
|
4. Add small examples for each foundation feature before adding bigger syntax. The object-field foundation now includes positive and negative validation for explicitly typed NUMBER/TEXT assignments and field expressions such as `SET player.score player.name`, `SET player.score player.score + 1`, `SET player.name player.score + 1`, explicitly typed field assignments, explicit typed assignments to undeclared fields, simple and chained aliases such as `SET alias player`, `SET backup alias`, followed by `SET backup.score ...` or `CHECK TYPE backup.score IS ...`, chained aliases in object-method calls through both modern `DO` and compatibility `CALL ... WITH` forms (including a dedicated positive modern `DO` fixture), field-to-field, compound arithmetic field expressions, and arithmetic/text expression mismatches; typed containers also accept a map with nested type metadata when it is added to a `LIST OF MAP`; broader alias/control-flow checking remains planned.
|
||||||
5. Keep the focused typecheck validator complete: the method-body field diagnostic fixtures are now exercised by `claro validate` alongside the standalone diagnostic validator; continue wiring each new `typecheck_*.claro` fixture into both gates. Modern and compatibility unknown-field expression diagnostics now have matching focused coverage, and explicitly typed unknown method-field assignments have matching modern and compatibility fixtures with the expected `HAS field TYPE` repair hint.
|
5. Keep the focused typecheck validator complete: the method-body field diagnostic fixtures are now exercised by `claro validate` alongside the standalone diagnostic validator; continue wiring each new `typecheck_*.claro` fixture into both gates. Modern and compatibility unknown-field expression diagnostics now have matching focused coverage, explicitly typed unknown method-field assignments have matching modern and compatibility fixtures with the expected `HAS field TYPE` repair hint, and short explicitly typed method-field assignments have matching modern and compatibility missing-value coverage.
|
||||||
-6. Continue narrowing expression diagnostics: known TEXT operands now remain visible through arithmetic, with focused numeric-subtraction, numeric-division, and numeric-multiplication positives plus text-operand addition, subtraction, multiplication, and division coverage. Typed function returns now include positive subtraction and division examples alongside multiplication, including compatibility `TAKES` / `LEARNED` numeric-addition, subtraction, division, and multiplication return fixtures. Text concatenation into TEXT fields is covered in both operand orders, including field-to-field concatenation. Each numeric operator names the text operand and explains that it needs NUMBER values. Compatibility method return diagnostics now cover addition, subtraction, division, and multiplication. `CHECK TYPE` also rejects misspelled expected type names.
|
-6. Continue narrowing expression diagnostics: known TEXT operands now remain visible through arithmetic, with focused numeric-subtraction, numeric-division, and numeric-multiplication positives plus text-operand addition, subtraction, multiplication, and division coverage. Typed function returns now include positive subtraction and division examples alongside multiplication, including compatibility `TAKES` / `LEARNED` numeric-addition, subtraction, division, and multiplication return fixtures. Text concatenation into TEXT fields is covered in both operand orders, including field-to-field concatenation. Each numeric operator names the text operand and explains that it needs NUMBER values. Compatibility method return diagnostics now cover addition, subtraction, division, and multiplication. `CHECK TYPE` also rejects misspelled expected type names.
|
||||||
- Keep typed method return examples balanced: numeric addition, subtraction, division, and multiplication now have dedicated positive compatibility `TAKES` / `LEARNED` method fixtures in the focused validator, alongside compatibility addition, subtraction, and division mismatch coverage and the existing method return mismatch diagnostics.
|
- Keep typed method return examples balanced: numeric addition, subtraction, division, and multiplication now have dedicated positive compatibility `TAKES` / `LEARNED` method fixtures in the focused validator, alongside compatibility addition, subtraction, and division mismatch coverage and the existing method return mismatch diagnostics.
|
||||||
- Keep method field examples balanced across syntax generations: modern and compatibility `TAKES` / `LEARNED` NUMBER, TEXT, and YESNO assignments now have positive fixtures beside the modern and compatibility mismatch fixtures, and modern plus compatibility YESNO method-field metadata now have dedicated positive fixtures beside the negative fixture.
|
- Keep method field examples balanced across syntax generations: modern and compatibility `TAKES` / `LEARNED` NUMBER, TEXT, and YESNO assignments now have positive fixtures beside the modern and compatibility mismatch fixtures, and modern plus compatibility YESNO method-field metadata now have dedicated positive fixtures beside the negative fixture.
|
||||||
@@ -48,7 +48,16 @@ Keep declared return types honest: `claro typecheck` now reports friendly diagno
|
|||||||
8a. Keep method-body text expressions balanced: modern TEXT field concatenation is covered when the field is on either side of `+`, so both common beginner word-order patterns remain accepted.
|
8a. Keep method-body text expressions balanced: modern TEXT field concatenation is covered when the field is on either side of `+`, so both common beginner word-order patterns remain accepted.
|
||||||
8b. Keep compatibility method-body text expressions aligned: `TAKES` / `LEARNED` methods now have positive TEXT concatenation coverage in both operand orders beside the modern `TEACH` / `END` examples.
|
8b. Keep compatibility method-body text expressions aligned: `TAKES` / `LEARNED` methods now have positive TEXT concatenation coverage in both operand orders beside the modern `TEACH` / `END` examples.
|
||||||
8c. Keep explicit method-field annotations aligned with class declarations: `SET score TEXT "oops"` inside a method with `HAS score NUMBER` now reports the declared field mismatch instead of accepting the inline annotation; broader annotation consistency remains planned.
|
8c. Keep explicit method-field annotations aligned with class declarations: `SET score TEXT "oops"` inside a method with `HAS score NUMBER` now reports the declared field mismatch instead of accepting the inline annotation; broader annotation consistency remains planned.
|
||||||
8d. Keep inline method-field annotations consistent with `HAS` declarations: `claro typecheck` now rejects a conflicting annotation even when the assigned value itself has the class-declared type, and explains which type to use.
|
8d. Keep inline field annotations consistent with `HAS` declarations: `claro typecheck` now rejects conflicting annotations on method-body and direct object-field assignments, explains which type to use, and gives a known-type repair hint for unknown direct object-field annotations in both `AS ... TO` and short `SET field TYPE value` forms.
|
||||||
|
8d.1. Keep direct field annotation forms balanced: the compatibility-shaped `SET player.score AS NUMBER TO 10` form now has a dedicated positive fixture beside short explicit annotations, so both accepted spellings remain protected by focused validation.
|
||||||
|
8d.2. Keep method field annotation forms balanced: modern and compatibility methods now have dedicated positive fixtures for `SET score AS NUMBER TO 10`, so the compatibility-shaped annotation remains protected inside both supported method syntaxes.
|
||||||
|
8d.3. Keep release validation aligned with method annotation coverage: the modern and compatibility `AS ... TO` method fixtures now run through `claro validate`, not only the focused Python diagnostic validator.
|
||||||
|
8d.4. Keep separated direct field assignments unambiguous: `SET player.score AS NUMBER TO 10 extra` now reports the trailing text and explains the one-expression form instead of accepting a partially parsed value.
|
||||||
|
8d.5. Keep short direct field assignments unambiguous: `SET player.score NUMBER 10 extra` now rejects trailing words with a repair hint, matching the separated `AS ... TO` form.
|
||||||
|
8d.6. Keep separated direct field assignments complete: `SET player.score AS NUMBER TO` now explains that one value expression is required after `TO`.
|
||||||
|
8d.7. Keep short direct field assignments complete: `SET player.score NUMBER` now explains that one value expression is required after the type.
|
||||||
|
8d.8. Keep separated direct field assignments structurally complete: `SET player.score AS NUMBER` now explains that `TO` must appear before the value, with a complete repair example.
|
||||||
|
8d.9. Keep untyped direct field assignments complete: `SET player.score` now explains that a value expression is required after the field name instead of silently accepting an empty assignment.
|
||||||
8e. Keep inline method-field annotation coverage aligned across syntax generations: compatibility `TAKES` / `LEARNED` methods now have matching positive and negative fixtures, so older lessons retain the same class-declared-type guidance.
|
8e. Keep inline method-field annotation coverage aligned across syntax generations: compatibility `TAKES` / `LEARNED` methods now have matching positive and negative fixtures, so older lessons retain the same class-declared-type guidance.
|
||||||
8f. Keep inline method-field annotations learner-facing: an unknown annotation such as `BANANA` now names the field and method and suggests the `HAS` type instead of silently treating the annotation as an expression.
|
8f. Keep inline method-field annotations learner-facing: an unknown annotation such as `BANANA` now names the field and method and suggests the `HAS` type instead of silently treating the annotation as an expression.
|
||||||
8g. Keep unknown inline method-field annotation diagnostics aligned across syntax generations: compatibility `TAKES` / `LEARNED` methods now have matching focused coverage for misspelled annotations.
|
8g. Keep unknown inline method-field annotation diagnostics aligned across syntax generations: compatibility `TAKES` / `LEARNED` methods now have matching focused coverage for misspelled annotations.
|
||||||
@@ -80,6 +89,13 @@ Keep declared return types honest: `claro typecheck` now reports friendly diagno
|
|||||||
8q.4. Keep `CHECK TYPE` declarations ordered: `CHECK TYPE IS NUMBER` now explains that an expression belongs before `IS`, with a complete repair example.
|
8q.4. Keep `CHECK TYPE` declarations ordered: `CHECK TYPE IS NUMBER` now explains that an expression belongs before `IS`, with a complete repair example.
|
||||||
8q.5. Keep `TYPE OF` declarations understandable: a missing `AS` now gets a direct repair hint with a complete `TYPE OF score AS kind` example.
|
8q.5. Keep `TYPE OF` declarations understandable: a missing `AS` now gets a direct repair hint with a complete `TYPE OF score AS kind` example.
|
||||||
8q.5.1. Keep `TYPE OF` declarations complete: a missing expression before `AS` now gets a direct repair hint with a complete `TYPE OF score AS kind` example.
|
8q.5.1. Keep `TYPE OF` declarations complete: a missing expression before `AS` now gets a direct repair hint with a complete `TYPE OF score AS kind` example.
|
||||||
|
8q.5.1a. Keep incomplete `TYPE OF` diagnostics focused: when the expression is missing, report that repair before secondary trailing-token errors.
|
||||||
|
8q.5.2. Keep `TYPE OF` declarations named: a missing result name after `AS` now gets a direct repair hint with a complete `TYPE OF score AS kind` example.
|
||||||
|
8q.5.3. Keep complete `TYPE OF` examples protected: the valid `TYPE OF score AS kind` form now has a positive fixture in the focused typecheck validation matrix.
|
||||||
|
8q.5.3a. Keep `TYPE OF` keyword handling consistent: lowercase `type of score as kind` now has positive fixture coverage alongside the uppercase form.
|
||||||
|
8q.5.3b. Keep `CHECK TYPE` keyword handling consistent: lowercase `check type score is number` now has positive fixture coverage alongside the uppercase form.
|
||||||
|
8q.5.3c. Keep object-field examples consistent with keyword rules: lowercase `class`, `has`, `new`, `set`, and `check type` now have one focused positive typed-field fixture.
|
||||||
|
8q.5.4. Keep bare `TYPE OF` declarations understandable: report that the expression, `AS`, and result name are all required, with a complete repair example.
|
||||||
8q.6. Keep `TYPE OF` declarations unambiguous: extra words after the result name now get a direct repair hint instead of being silently included in the variable name.
|
8q.6. Keep `TYPE OF` declarations unambiguous: extra words after the result name now get a direct repair hint instead of being silently included in the variable name.
|
||||||
8q.7. Keep typed `TEACH` declarations unambiguous: extra words after a declared return type now get a direct repair hint instead of being treated as part of an unknown type.
|
8q.7. Keep typed `TEACH` declarations unambiguous: extra words after a declared return type now get a direct repair hint instead of being treated as part of an unknown type.
|
||||||
8q.8. Keep typed `TEACH` declarations complete: a bare `RETURNS` now gets a direct repair hint naming the missing type and showing a supported example such as `NUMBER`.
|
8q.8. Keep typed `TEACH` declarations complete: a bare `RETURNS` now gets a direct repair hint naming the missing type and showing a supported example such as `NUMBER`.
|
||||||
|
|||||||
+105
-54
File diff suppressed because one or more lines are too long
@@ -0,0 +1,26 @@
|
|||||||
|
IMPORT "lib/math.claro" AS math
|
||||||
|
IMPORT "lib/random.claro" AS random
|
||||||
|
|
||||||
|
TRY
|
||||||
|
CALL math.abs
|
||||||
|
CATCH
|
||||||
|
ENDTRY
|
||||||
|
SAY LASTERROR
|
||||||
|
|
||||||
|
TRY
|
||||||
|
CALL math.clamp WITH 1, 0
|
||||||
|
CATCH
|
||||||
|
ENDTRY
|
||||||
|
SAY LASTERROR
|
||||||
|
|
||||||
|
TRY
|
||||||
|
CALL random.seed
|
||||||
|
CATCH
|
||||||
|
ENDTRY
|
||||||
|
SAY LASTERROR
|
||||||
|
|
||||||
|
TRY
|
||||||
|
CALL random.int WITH 1
|
||||||
|
CATCH
|
||||||
|
ENDTRY
|
||||||
|
SAY LASTERROR
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
math.abs needs 1 argument, but got 0. Try: CALL math.abs WITH value.
|
||||||
|
math.clamp needs 3 arguments, but got 2. Try: CALL math.clamp WITH value, lower, upper.
|
||||||
|
random.seed needs 1 argument, but got 0. Try: CALL random.seed WITH value.
|
||||||
|
random.int needs 2 arguments, but got 1. Try: CALL random.int WITH lower, upper.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
IMPORT "lib/random.claro" AS random
|
||||||
|
|
||||||
|
TRY
|
||||||
|
CALL random.int WITH 10, 1
|
||||||
|
CATCH
|
||||||
|
ENDTRY
|
||||||
|
SAY LASTERROR
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
random.int needs the lower bound to be less than or equal to the upper bound.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
TEACH recurse
|
||||||
|
DO recurse
|
||||||
|
LEARNED
|
||||||
|
|
||||||
|
TRY
|
||||||
|
DO recurse
|
||||||
|
CATCH
|
||||||
|
SAY LASTERROR
|
||||||
|
ENDTRY
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Claro function call depth exceeded the safe limit of 256. Simplify the recursion or add a stopping condition.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
RUN COMMAND "exit 3" AS output
|
||||||
|
SAY LASTEXIT
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
IMPORT "lib/random.claro" AS random
|
||||||
|
CALL random.seed WITH 17
|
||||||
|
CALL random.int WITH -2147483648, 2147483647
|
||||||
|
SET value RESULT
|
||||||
|
SAY value >= -2147483648
|
||||||
|
SAY value <= 2147483647
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
YES
|
||||||
|
YES
|
||||||
@@ -39,6 +39,40 @@ class ValidateTypecheckDiagnosticsTests(unittest.TestCase):
|
|||||||
MODULE.EXPECTED_OK,
|
MODULE.EXPECTED_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_includes_explicitly_typed_text_field_success_fixture(self):
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_object_field_typed_text_good.claro",
|
||||||
|
MODULE.EXPECTED_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_includes_explicitly_typed_yesno_field_success_fixture(self):
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_object_field_typed_yesno_good.claro",
|
||||||
|
MODULE.EXPECTED_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_includes_as_to_typed_field_success_fixture(self):
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_object_field_typed_as_to_good.claro",
|
||||||
|
MODULE.EXPECTED_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_includes_as_to_typed_field_extra_tokens_fixture(self):
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_object_field_typed_as_to_extra_tokens_bad.claro",
|
||||||
|
MODULE.EXPECTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_includes_as_to_method_field_success_fixtures(self):
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_method_inline_field_annotation_as_to_good.claro",
|
||||||
|
MODULE.EXPECTED_OK,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_method_compat_inline_field_annotation_as_to_good.claro",
|
||||||
|
MODULE.EXPECTED_OK,
|
||||||
|
)
|
||||||
|
|
||||||
def test_includes_reverse_text_concatenation_success_fixture(self):
|
def test_includes_reverse_text_concatenation_success_fixture(self):
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"tests/typecheck_object_field_text_concat_reverse_good.claro",
|
"tests/typecheck_object_field_text_concat_reverse_good.claro",
|
||||||
@@ -95,6 +129,24 @@ class ValidateTypecheckDiagnosticsTests(unittest.TestCase):
|
|||||||
MODULE.EXPECTED,
|
MODULE.EXPECTED,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_includes_missing_type_of_expression_before_extra_tokens_fixture(self):
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_missing_type_of_expression_extra_tokens_bad.claro",
|
||||||
|
MODULE.EXPECTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_includes_lowercase_type_of_success_fixture(self):
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_lowercase_type_of_good.claro",
|
||||||
|
MODULE.EXPECTED_OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_includes_lowercase_check_type_success_fixture(self):
|
||||||
|
self.assertIn(
|
||||||
|
"tests/typecheck_lowercase_check_type_good.claro",
|
||||||
|
MODULE.EXPECTED_OK,
|
||||||
|
)
|
||||||
|
|
||||||
def test_includes_lowercase_compatibility_return_success_fixture(self):
|
def test_includes_lowercase_compatibility_return_success_fixture(self):
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"tests/typecheck_function_lowercase_return_good.claro",
|
"tests/typecheck_function_lowercase_return_good.claro",
|
||||||
@@ -371,6 +423,8 @@ class ValidateTypecheckDiagnosticsTests(unittest.TestCase):
|
|||||||
def test_release_validation_runs_method_field_diagnostic_fixtures(self):
|
def test_release_validation_runs_method_field_diagnostic_fixtures(self):
|
||||||
source = (ROOT / "src" / "claro.c").read_text()
|
source = (ROOT / "src" / "claro.c").read_text()
|
||||||
for fixture in (
|
for fixture in (
|
||||||
|
"tests/typecheck_method_inline_field_annotation_as_to_good.claro",
|
||||||
|
"tests/typecheck_method_compat_inline_field_annotation_as_to_good.claro",
|
||||||
"tests/typecheck_method_field_assignment_bad.claro",
|
"tests/typecheck_method_field_assignment_bad.claro",
|
||||||
"tests/typecheck_method_text_field_assignment_bad.claro",
|
"tests/typecheck_method_text_field_assignment_bad.claro",
|
||||||
"tests/typecheck_method_compat_text_field_assignment_bad.claro",
|
"tests/typecheck_method_compat_text_field_assignment_bad.claro",
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
set score number 10
|
||||||
|
check type score is number
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
set score number 10
|
||||||
|
type of score as kind
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
|
||||||
|
TEACH set_score TAKES value
|
||||||
|
SET score AS NUMBER TO 10
|
||||||
|
LEARNED
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
CALL player.set_score WITH 10
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
|
||||||
|
TEACH set_score TAKES value
|
||||||
|
SET score NUMBER
|
||||||
|
LEARNED
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
CALL player.set_score WITH 10
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
|
||||||
|
TEACH set_score
|
||||||
|
SET score AS NUMBER TO 10
|
||||||
|
END
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
DO player.set_score
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
|
||||||
|
TEACH set_score
|
||||||
|
SET score NUMBER
|
||||||
|
END
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
DO player.set_score
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
TYPE OF AS kind extra
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
TYPE OF
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
TYPE OF score AS
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
class Player
|
||||||
|
has score number
|
||||||
|
end
|
||||||
|
|
||||||
|
new Player player
|
||||||
|
set player.score number 10
|
||||||
|
check type player.score is number
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score NUMBER 10 extra
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score NUMBER
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score AS NUMBER
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score AS NUMBER TO 10 extra
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score AS NUMBER TO 10
|
||||||
|
CHECK TYPE player.score IS NUMBER
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score AS NUMBER TO
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score TEXT 10
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score NUMBER 10
|
||||||
|
CHECK TYPE player.score IS NUMBER
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS name TEXT
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.name NUMBER 10
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS name TEXT
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.name TEXT "Ada"
|
||||||
|
CHECK TYPE player.name IS TEXT
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score BANANA 10
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS score NUMBER
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.score AS BANANA TO 10
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
CLASS Player
|
||||||
|
HAS ready YESNO
|
||||||
|
END
|
||||||
|
|
||||||
|
NEW Player player
|
||||||
|
SET player.ready YESNO YES
|
||||||
|
CHECK TYPE player.ready IS YESNO
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
SET score NUMBER 10
|
||||||
|
TYPE OF score AS kind
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check that ADD releases its evaluated item under LeakSanitizer."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-add-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "add_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET items TO LIST\n'
|
||||||
|
+ 'ADD "transient" + " item" TO items\n' * 2000,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: ADD expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
print("PASS: ADD expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check ASK prompt expression values are released under LeakSanitizer."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-ask-prompt-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "ask_prompts.claro"
|
||||||
|
script.write_text(
|
||||||
|
''.join('ASK "question" + " value" AS answer\n' for _ in range(2000)),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="", file=sys.stderr)
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, input="\n" * 2000,
|
||||||
|
text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode:
|
||||||
|
print("FAIL: ASK prompt cleanup probe failed")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: ASK prompt expression values still leak")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if run.stdout.count("question value\n") != 2000:
|
||||||
|
print("FAIL: ASK prompt cleanup probe produced the wrong output")
|
||||||
|
return 1
|
||||||
|
print("PASS: ASK prompt expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for discarded control-flow expression Values."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-control-expression-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "control_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
''.join(
|
||||||
|
'IF "condition" + " value"\n'
|
||||||
|
' SET seen TO YES\n'
|
||||||
|
'ENDIF\n'
|
||||||
|
for _ in range(2000)
|
||||||
|
)
|
||||||
|
+ ''.join(
|
||||||
|
'DO "1" TIMES\n'
|
||||||
|
' SET seen TO YES\n'
|
||||||
|
'DONE\n'
|
||||||
|
for _ in range(2000)
|
||||||
|
)
|
||||||
|
+ 'SAY seen\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
[
|
||||||
|
"gcc", "-std=c99", "-O0", "-g",
|
||||||
|
"-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm",
|
||||||
|
], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="", file=sys.stderr)
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True,
|
||||||
|
capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode:
|
||||||
|
print("FAIL: control-expression cleanup probe failed")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: discarded control-flow expression values still leak")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if run.stdout != "YES\n":
|
||||||
|
print("FAIL: control-expression cleanup probe produced the wrong output")
|
||||||
|
print(run.stdout, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print("PASS: discarded control-flow expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for control-flow expression Value cleanup under LeakSanitizer."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-control-flow-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "control_flow.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET items TO LIST\n'
|
||||||
|
'FOR EACH item IN items\n'
|
||||||
|
' SAY item\n'
|
||||||
|
'DONE\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
[
|
||||||
|
"gcc", "-std=c99", "-O0", "-g",
|
||||||
|
"-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm",
|
||||||
|
], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="", file=sys.stderr)
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True,
|
||||||
|
capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode:
|
||||||
|
print("FAIL: control-flow cleanup probe failed")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: control-flow expression values still leak")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if run.stdout != "":
|
||||||
|
print("FAIL: control-flow cleanup probe produced the wrong output")
|
||||||
|
print(run.stdout, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print("PASS: control-flow expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for COPY/MOVE FILE expression temporary ownership."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-copy-move-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "copy_move_expressions.claro"
|
||||||
|
(tmp_path / "copy-source.txt").write_text("sample", encoding="utf-8")
|
||||||
|
script.write_text(
|
||||||
|
('COPY FILE "copy-source.txt" TO "copy-destination.txt"\n'
|
||||||
|
'MOVE FILE "copy-destination.txt" TO "copy-source.txt"\n') * 1000,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=tmp_path, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: COPY/MOVE FILE expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
print("PASS: COPY/MOVE FILE expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for discarded COUNT expression Values."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-count-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "count_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET items TO LIST\n'
|
||||||
|
+ ''.join('COUNT items AS item_count\n' for _ in range(2000))
|
||||||
|
+ 'SAY item_count\n', encoding="utf-8"
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="", file=sys.stderr)
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: discarded COUNT expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if run.stdout != "0\n":
|
||||||
|
print("FAIL: COUNT cleanup probe produced the wrong output")
|
||||||
|
print(run.stdout, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print("PASS: discarded COUNT expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for CREATE FOLDER expression temporary ownership."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-create-folder-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "create_folder_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET path TO "' + str(tmp_path / "new-folder") + '"\n'
|
||||||
|
+ "CREATE FOLDER path\n" * 2000,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: CREATE FOLDER expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
print("PASS: CREATE FOLDER expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for DELETE FILE expression temporary ownership."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-delete-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "delete_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET path TO "' + str(tmp_path / "unused") + '"\n'
|
||||||
|
+ 'DELETE FILE path + ""\n' * 2000,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: DELETE FILE expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
print("PASS: DELETE FILE expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for EXISTS FILE expression temporary ownership."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-exists-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "exists_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET path TO "missing-file-for-cleanup-probe"\n'
|
||||||
|
+ 'EXISTS FILE path AS present\n' * 2000
|
||||||
|
+ "SAY present\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: EXISTS FILE expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
if run.stdout != "NO\n":
|
||||||
|
print(f"FAIL: unexpected EXISTS FILE probe output: {run.stdout!r}")
|
||||||
|
return 1
|
||||||
|
print("PASS: EXISTS FILE expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for discarded expression Value cleanup under LeakSanitizer."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-expression-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET value TO "seed"\n'
|
||||||
|
+ ''.join(
|
||||||
|
'SET value TO "a" + "b"\n'
|
||||||
|
'SET answer TO 1 + 2 * 3\n'
|
||||||
|
for _ in range(2000)
|
||||||
|
)
|
||||||
|
+ 'SAY value\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
[
|
||||||
|
"gcc", "-std=c99", "-O0", "-g",
|
||||||
|
"-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm",
|
||||||
|
], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="", file=sys.stderr)
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True,
|
||||||
|
capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode:
|
||||||
|
print("FAIL: expression temporary cleanup probe failed")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: discarded expression values still leak")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if run.stdout != "ab\n":
|
||||||
|
print("FAIL: expression cleanup probe produced the wrong output")
|
||||||
|
print(run.stdout, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print("PASS: discarded expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for FIND expression temporaries."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-find-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "find_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
"SET items TO LIST\n"
|
||||||
|
+ 'FIND "Missing" IN items AS position\n' * 2000
|
||||||
|
+ "SAY position\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: FIND expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
if run.stdout != "0\n":
|
||||||
|
print(f"FAIL: unexpected FIND probe output: {run.stdout!r}")
|
||||||
|
return 1
|
||||||
|
print("PASS: FIND expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for GET AT expression temporaries."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-get-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "get_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
"SET items TO LIST\n"
|
||||||
|
+ "GET items AT 1 AS selected\n" * 2000
|
||||||
|
+ "SAY selected\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: GET AT expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
if run.stdout != "\n":
|
||||||
|
print(f"FAIL: unexpected GET AT probe output: {run.stdout!r}")
|
||||||
|
return 1
|
||||||
|
print("PASS: GET AT expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for GET KEY expression temporaries."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-get-key-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "get_key_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET items TO MAP\nPUT items KEY "selected" VALUE "value"\n'
|
||||||
|
+ 'GET items KEY "selected" AS selected\n' * 2000
|
||||||
|
+ "SAY selected\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: GET KEY expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
if run.stdout != "value\n":
|
||||||
|
print(f"FAIL: unexpected GET KEY probe output: {run.stdout!r}")
|
||||||
|
return 1
|
||||||
|
print("PASS: GET KEY expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Focused HTTP regression checks for marker-safe bodies and bounded responses."""
|
||||||
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
MAX_HTTP_BYTES = 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path == "/marker":
|
||||||
|
body = b"before\n__CLARO_HTTP_STATUS__999\nafter"
|
||||||
|
status = 200
|
||||||
|
elif self.path == "/large":
|
||||||
|
body = b"x" * (MAX_HTTP_BYTES + 1)
|
||||||
|
status = 200
|
||||||
|
else:
|
||||||
|
body = b"not found"
|
||||||
|
status = 404
|
||||||
|
self.send_response(status)
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-http-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
script = tmp_path / "http.claro"
|
||||||
|
script.write_text(
|
||||||
|
'HTTP GET URL_MARKER AS body STATUS status\n'
|
||||||
|
'SAY body\n'
|
||||||
|
'SAY status\n'
|
||||||
|
'HTTP GET URL_LARGE AS ignored\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
binary = tmp_path / "claro-http"
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||||
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
try:
|
||||||
|
marker = f'"http://127.0.0.1:{server.server_port}/marker"'
|
||||||
|
large = f'"http://127.0.0.1:{server.server_port}/large"'
|
||||||
|
text = script.read_text(encoding="utf-8").replace("URL_MARKER", marker).replace("URL_LARGE", large)
|
||||||
|
script.write_text(text, encoding="utf-8")
|
||||||
|
run = subprocess.run([str(binary), str(script)], cwd=ROOT, text=True, capture_output=True)
|
||||||
|
finally:
|
||||||
|
server.shutdown()
|
||||||
|
server.server_close()
|
||||||
|
expected_body = "before\n__CLARO_HTTP_STATUS__999\nafter\n200\n"
|
||||||
|
if run.returncode == 0:
|
||||||
|
print("FAIL: oversized HTTP response was accepted")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
return 1
|
||||||
|
if expected_body not in run.stdout:
|
||||||
|
print("FAIL: marker-like response body was altered")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
if "HTTP response exceeds the safe size limit" not in run.stderr and "HTTP response exceeds the safe size limit" not in run.stdout:
|
||||||
|
print("FAIL: oversized response did not get a beginner-facing diagnostic")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
print("PASS: HTTP status is separated from marker-like bodies and responses are bounded")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Regression check for LIST FOLDER expression temporary ownership."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-list-folder-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "list_folder_expressions.claro"
|
||||||
|
folder = tmp_path / "folder"
|
||||||
|
folder.mkdir()
|
||||||
|
script.write_text(
|
||||||
|
'SET path TO "' + str(folder) + '"\n'
|
||||||
|
+ "LIST FOLDER path AS entries\n" * 2000,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: LIST FOLDER expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
print("PASS: LIST FOLDER expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Focused regression check for expression-token cleanup under LeakSanitizer."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-memory-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "overwrite.claro"
|
||||||
|
script.write_text(
|
||||||
|
'TEACH consume first, second, third, fourth\n'
|
||||||
|
' RETURN first\n'
|
||||||
|
'END\n'
|
||||||
|
'SET value TO "first"\n'
|
||||||
|
'SET value TO "second"\n'
|
||||||
|
'SET items TO LIST\n'
|
||||||
|
'ADD "one" TO items\n'
|
||||||
|
'SET items TO LIST\n'
|
||||||
|
'SET profile TO MAP\n'
|
||||||
|
'PUT profile KEY "name" VALUE "Ada"\n'
|
||||||
|
'PUT profile KEY "name" VALUE "Grace"\n'
|
||||||
|
+ ''.join('DO consume "a", "b", "c", "d"\n' for _ in range(2000))
|
||||||
|
+ 'SAY profile\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
[
|
||||||
|
"gcc", "-std=c99", "-O0", "-g",
|
||||||
|
"-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm",
|
||||||
|
], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="", file=sys.stderr)
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True,
|
||||||
|
capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if "toks_add" in run.stderr or "tokenize" in run.stderr:
|
||||||
|
print("FAIL: expression token allocations still leak")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if "load_program" in run.stderr:
|
||||||
|
print("FAIL: loaded program storage still leaks")
|
||||||
|
print(run.stderr, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
source = (ROOT / "src" / "claro.c").read_text(encoding="utf-8")
|
||||||
|
if "static void split_args_free(char **parts, int count)" not in source:
|
||||||
|
print("FAIL: split argument storage has no cleanup boundary")
|
||||||
|
return 1
|
||||||
|
if "static void value_free(Value v)" not in source or "value_free(v->val);" not in source:
|
||||||
|
print("FAIL: overwritten runtime values are not released")
|
||||||
|
return 1
|
||||||
|
if "static void runtime_free(Runtime *rt)" not in source:
|
||||||
|
print("FAIL: runtime-owned values have no final cleanup boundary")
|
||||||
|
return 1
|
||||||
|
functional = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True,
|
||||||
|
capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=0"},
|
||||||
|
)
|
||||||
|
if functional.returncode or "[map]\n" not in functional.stdout:
|
||||||
|
print("FAIL: overwrite cleanup probe produced the wrong output")
|
||||||
|
print(functional.stdout, end="", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print("PASS: expression token allocations are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check that PUT releases evaluated map/key/value copies under LeakSanitizer."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-put-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "put_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'SET items TO MAP\n'
|
||||||
|
+ 'PUT items KEY "key" VALUE "transient" + " value"\n' * 2000,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: PUT expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
print("PASS: PUT expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check that REMOVE releases its evaluated needle under LeakSanitizer."""
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if os.name == "nt":
|
||||||
|
print("SKIP: LeakSanitizer check is POSIX-only")
|
||||||
|
return 0
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-remove-cleanup-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
binary = tmp_path / "claro-lsan"
|
||||||
|
script = tmp_path / "remove_expressions.claro"
|
||||||
|
script.write_text(
|
||||||
|
'REMOVE "transient" + " needle" FROM missing\n' * 2000,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
|
||||||
|
"src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stdout, end="")
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
run = subprocess.run(
|
||||||
|
[str(binary), str(script)], cwd=ROOT, text=True, capture_output=True,
|
||||||
|
env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
|
||||||
|
)
|
||||||
|
if run.returncode or "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
|
||||||
|
print("FAIL: REMOVE expression values still leak")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
if run.stdout != "":
|
||||||
|
print(f"FAIL: unexpected REMOVE probe output: {run.stdout!r}")
|
||||||
|
return 1
|
||||||
|
print("PASS: REMOVE expression values are released")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Ensure the learner-facing RUN COMMAND docs describe its trust boundary."""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
DOC = ROOT / "docs" / "PRACTICAL_SCRIPTING.md"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
text = DOC.read_text(encoding="utf-8").lower()
|
||||||
|
required = (
|
||||||
|
"trusted code",
|
||||||
|
"not a sandbox",
|
||||||
|
"runs commands on the user's computer",
|
||||||
|
)
|
||||||
|
missing = [phrase for phrase in required if phrase not in text]
|
||||||
|
if missing:
|
||||||
|
raise SystemExit("RUN COMMAND documentation is missing: " + ", ".join(missing))
|
||||||
|
print("Trusted command documentation validation complete")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -7,9 +7,15 @@ 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 = {
|
EXPECTED = {
|
||||||
|
"tests/typecheck_missing_type_of_expression_extra_tokens_bad.claro": [
|
||||||
|
"tests/typecheck_missing_type_of_expression_extra_tokens_bad.claro:1: TYPE OF needs an expression before AS. Try: TYPE OF score AS kind.",
|
||||||
|
],
|
||||||
"tests/typecheck_missing_type_of_expression_bad.claro": [
|
"tests/typecheck_missing_type_of_expression_bad.claro": [
|
||||||
"tests/typecheck_missing_type_of_expression_bad.claro:1: TYPE OF needs an expression before AS. Try: TYPE OF score AS kind.",
|
"tests/typecheck_missing_type_of_expression_bad.claro:1: TYPE OF needs an expression before AS. Try: TYPE OF score AS kind.",
|
||||||
],
|
],
|
||||||
|
"tests/typecheck_missing_type_of_result_name_bad.claro": [
|
||||||
|
"tests/typecheck_missing_type_of_result_name_bad.claro:1: TYPE OF needs a result name after AS. Try: TYPE OF score AS kind.",
|
||||||
|
],
|
||||||
"tests/typecheck_function_duplicate_param_bad.claro": [
|
"tests/typecheck_function_duplicate_param_bad.claro": [
|
||||||
"tests/typecheck_function_duplicate_param_bad.claro:1: Function greet declares parameter name more than once. Give each parameter a different name.",
|
"tests/typecheck_function_duplicate_param_bad.claro:1: Function greet declares parameter name more than once. Give each parameter a different name.",
|
||||||
],
|
],
|
||||||
@@ -249,7 +255,37 @@ EXPECTED = {
|
|||||||
"tests/typecheck_object_field_bad.claro:6: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.",
|
"tests/typecheck_object_field_bad.claro:6: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.",
|
||||||
],
|
],
|
||||||
"tests/typecheck_object_field_typed_bad.claro": [
|
"tests/typecheck_object_field_typed_bad.claro": [
|
||||||
"tests/typecheck_object_field_typed_bad.claro:6: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.",
|
"tests/typecheck_object_field_typed_bad.claro:6: Type mismatch for field player.score: class declares NUMBER, but this assignment says TEXT. Use NUMBER for score.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_typed_conflict_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_typed_conflict_bad.claro:6: Type mismatch for field player.score: class declares NUMBER, but this assignment says TEXT. Use NUMBER for score.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_typed_as_to_extra_tokens_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_typed_as_to_extra_tokens_bad.claro:6: SET player.score has extra text after value 10. Keep only the field name, type, TO, and one expression.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_typed_as_to_missing_value_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_typed_as_to_missing_value_bad.claro:6: SET player.score needs a value after type NUMBER and TO. Add one expression.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_typed_as_missing_to_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_typed_as_missing_to_bad.claro:6: SET player.score needs TO after type NUMBER. Try: SET player.score AS NUMBER TO 10.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_short_extra_tokens_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_short_extra_tokens_bad.claro:6: SET player.score has extra text after value 10. Keep only the field name, type, and one expression.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_short_missing_value_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_short_missing_value_bad.claro:6: SET player.score needs a value after type NUMBER. Add one expression.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_missing_value_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_missing_value_bad.claro:6: SET player.score needs a value. Add one expression after the field name.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_typed_unknown_type_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_typed_unknown_type_bad.claro:6: Field player.score needs a known type such as NUMBER, TEXT, or YESNO, but BANANA is not a Claro type. Use NUMBER for score.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_typed_unknown_short_type_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_typed_unknown_short_type_bad.claro:6: Field player.score needs a known type such as NUMBER, TEXT, or YESNO, but BANANA is not a Claro type. Use NUMBER for score.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_object_field_typed_text_bad.claro": [
|
||||||
|
"tests/typecheck_object_field_typed_text_bad.claro:6: Type mismatch for field player.name: class declares TEXT, but this assignment says NUMBER. Use TEXT for name.",
|
||||||
],
|
],
|
||||||
"tests/typecheck_object_field_after_method_bad.claro": [
|
"tests/typecheck_object_field_after_method_bad.claro": [
|
||||||
"tests/typecheck_object_field_after_method_bad.claro:10: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.",
|
"tests/typecheck_object_field_after_method_bad.claro:10: Type mismatch for field player.score: expected NUMBER, but this value looks like TEXT.",
|
||||||
@@ -335,6 +371,9 @@ EXPECTED = {
|
|||||||
"tests/typecheck_missing_type_of_as_bad.claro": [
|
"tests/typecheck_missing_type_of_as_bad.claro": [
|
||||||
"tests/typecheck_missing_type_of_as_bad.claro:2: TYPE OF needs AS. Try: TYPE OF score AS kind.",
|
"tests/typecheck_missing_type_of_as_bad.claro:2: TYPE OF needs AS. Try: TYPE OF score AS kind.",
|
||||||
],
|
],
|
||||||
|
"tests/typecheck_missing_type_of_parts_bad.claro": [
|
||||||
|
"tests/typecheck_missing_type_of_parts_bad.claro:1: TYPE OF needs an expression, AS, and a result name. Try: TYPE OF score AS kind.",
|
||||||
|
],
|
||||||
"tests/typecheck_extra_type_of_tokens_bad.claro": [
|
"tests/typecheck_extra_type_of_tokens_bad.claro": [
|
||||||
"tests/typecheck_extra_type_of_tokens_bad.claro:2: TYPE OF score has extra text after result name kind. Keep only the expression, AS, and one result name.",
|
"tests/typecheck_extra_type_of_tokens_bad.claro:2: TYPE OF score has extra text after result name kind. Keep only the expression, AS, and one result name.",
|
||||||
],
|
],
|
||||||
@@ -410,6 +449,12 @@ EXPECTED = {
|
|||||||
"tests/typecheck_method_field_assignment_bad.claro": [
|
"tests/typecheck_method_field_assignment_bad.claro": [
|
||||||
"tests/typecheck_method_field_assignment_bad.claro:5: Type mismatch for field score in Player.add: addition needs NUMBER values, but name looks like TEXT.",
|
"tests/typecheck_method_field_assignment_bad.claro:5: Type mismatch for field score in Player.add: addition needs NUMBER values, but name looks like TEXT.",
|
||||||
],
|
],
|
||||||
|
"tests/typecheck_method_short_field_missing_value_bad.claro": [
|
||||||
|
"tests/typecheck_method_short_field_missing_value_bad.claro:5: SET score needs a value after type NUMBER. Add one expression.",
|
||||||
|
],
|
||||||
|
"tests/typecheck_method_compat_short_field_missing_value_bad.claro": [
|
||||||
|
"tests/typecheck_method_compat_short_field_missing_value_bad.claro:5: SET score needs a value after type NUMBER. Add one expression.",
|
||||||
|
],
|
||||||
"tests/typecheck_method_unknown_field_bad.claro": [
|
"tests/typecheck_method_unknown_field_bad.claro": [
|
||||||
"tests/typecheck_method_unknown_field_bad.claro:5: Object Player has no field level. Check the field name or add HAS level NUMBER to the class.",
|
"tests/typecheck_method_unknown_field_bad.claro:5: Object Player has no field level. Check the field name or add HAS level NUMBER to the class.",
|
||||||
],
|
],
|
||||||
@@ -473,6 +518,10 @@ EXPECTED = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EXPECTED_OK = [
|
EXPECTED_OK = [
|
||||||
|
"tests/typecheck_type_of_good.claro",
|
||||||
|
"tests/typecheck_lowercase_type_of_good.claro",
|
||||||
|
"tests/typecheck_lowercase_check_type_good.claro",
|
||||||
|
"tests/typecheck_object_field_lowercase_good.claro",
|
||||||
"tests/typecheck_function_good.claro",
|
"tests/typecheck_function_good.claro",
|
||||||
"tests/typecheck_function_return_good.claro",
|
"tests/typecheck_function_return_good.claro",
|
||||||
"tests/typecheck_function_compat_return_good.claro",
|
"tests/typecheck_function_compat_return_good.claro",
|
||||||
@@ -519,11 +568,17 @@ EXPECTED_OK = [
|
|||||||
"tests/typecheck_method_yesno_field_check_type_good.claro",
|
"tests/typecheck_method_yesno_field_check_type_good.claro",
|
||||||
"tests/typecheck_method_inline_field_annotation_good.claro",
|
"tests/typecheck_method_inline_field_annotation_good.claro",
|
||||||
"tests/typecheck_method_compat_inline_field_annotation_good.claro",
|
"tests/typecheck_method_compat_inline_field_annotation_good.claro",
|
||||||
|
"tests/typecheck_method_inline_field_annotation_as_to_good.claro",
|
||||||
|
"tests/typecheck_method_compat_inline_field_annotation_as_to_good.claro",
|
||||||
"tests/typecheck_method_nested_branch_complete_good.claro",
|
"tests/typecheck_method_nested_branch_complete_good.claro",
|
||||||
"tests/typecheck_function_multi_good.claro",
|
"tests/typecheck_function_multi_good.claro",
|
||||||
"tests/typecheck_method_good.claro",
|
"tests/typecheck_method_good.claro",
|
||||||
"tests/typecheck_method_call_good.claro",
|
"tests/typecheck_method_call_good.claro",
|
||||||
"tests/typecheck_object_field_good.claro",
|
"tests/typecheck_object_field_good.claro",
|
||||||
|
"tests/typecheck_object_field_typed_good.claro",
|
||||||
|
"tests/typecheck_object_field_typed_text_good.claro",
|
||||||
|
"tests/typecheck_object_field_typed_yesno_good.claro",
|
||||||
|
"tests/typecheck_object_field_typed_as_to_good.claro",
|
||||||
"tests/typecheck_object_field_expression_good.claro",
|
"tests/typecheck_object_field_expression_good.claro",
|
||||||
"tests/typecheck_object_field_compound_good.claro",
|
"tests/typecheck_object_field_compound_good.claro",
|
||||||
"tests/typecheck_object_field_subtraction_good.claro",
|
"tests/typecheck_object_field_subtraction_good.claro",
|
||||||
|
|||||||
Reference in New Issue
Block a user