hoodscript 1.4.0__tar.gz → 1.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hoodscript-1.4.0 → hoodscript-1.6.0}/CHANGELOG.md +68 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/PKG-INFO +1 -1
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/curriculum.md +7 -5
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/grammar.ebnf +4 -2
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/keywords.md +5 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/_version.py +2 -2
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/cli.py +2 -1
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/constants.py +5 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/errors/catalog.py +28 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/errors/translator.py +20 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/lsp.py +1 -1
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/patterns.py +41 -6
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/sourcing.py +8 -2
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_conformance.py +2 -1
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_errors.py +10 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_fuzz_ebnf.py +3 -0
- hoodscript-1.6.0/tests/test_lint_fuzz.py +37 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_lsp_features.py +1 -1
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_patterns.py +10 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_rule_zero.py +12 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/.claude/skills/align-prompt/SKILL.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/.gitignore +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/CODE_OF_CONDUCT.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/CONTRIBUTING.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/LICENSE +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/README.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/SECURITY.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/SPRINT_PLAN.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/adr/0001-token-pipeline-over-parser.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/adr/0002-keyword-reconciliation.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/adr/0003-inclusive-ranges.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/adr/0004-no-regional-dialect-overlays.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/adr/README.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/archive/HOODSCRIPT_COMPLETE_MASTER_SYSTEM.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/archive/HOODSCRIPT_FULL_ENGINEERING_SUITE.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/archive/HOODSCRIPT_UNSPRED_FEATURES_ROADMAP.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/hoodlib.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/docs/onboarding.md +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/quickstart.ipynb +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/beatlab/stage1.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/beatlab/stage2.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/beatlab/stage3.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/beatlab/stage4.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/beatlab/stage5.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/cookout/stage1.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/cookout/stage2.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/cookout/stage3.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/cookout/stage4.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/cookout/stage5.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/dropday/stage1.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/dropday/stage2.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/dropday/stage3.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/dropday/stage4.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/dropday/stage5.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/level1.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/level2.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/level3.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/level4.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/level5.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/level6.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/level7.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/examples/solutions/level8.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/out.txt +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/playground/app.js +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/playground/bundle.js +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/playground/examples.js +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/playground/hoodscript-latest-py3-none-any.whl +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/playground/index.html +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/playground/style.css +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/playground/worker.js +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/pyproject.toml +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/scripts/align_prompt.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/scripts/build_playground.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/scripts/coraal_hits.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/scripts/gen_ebnf.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/scripts/gen_keywords.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/scripts/hooks/pre-commit +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/scripts/hooks/pre-push +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/scripts/install-hooks.sh +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodarcade/__init__.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodarcade/cli.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodarcade/core/runtime.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodarcade/core/ui.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodarcade/games/beatlab.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodarcade/games/cookout.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodarcade/games/dropday.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodfarm/__init__.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodfarm/cli.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodfarm/drone.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodfarm/grid.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodfarm/levels.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodfarm/renderer.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodfarm/runner.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodlib/__init__.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodlib/chart.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodlib/disk.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/__init__.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/__main__.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/cache.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/debugger.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/errors/__init__.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/errors/renderer.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/formatter.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/importer.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/kernel/__init__.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/kernel/__main__.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/kernel/installer.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/kernel/kernel.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/kernel/magics.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/linter.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/migrate.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/migrator.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/repl.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/replay.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/sandbox.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/stubs.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/traceback_handler.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/transpiler.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/tutor.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/hoodscript/zen.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/src/vibe.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/conftest.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/deep_nesting.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/fork_bomb.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/huge_literal.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/import_escapes.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/infinite_recursion.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/open_file.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/pathological_indent.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/runaway_output.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/corpus/runaway_time.hs +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/test_corpus.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/test_exec_sites.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/test_fuzz.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/security/test_sandbox.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_align_prompt.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_arcade.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_arcade_gates.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_chart.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_cli.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_coraal_hits.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_debugger.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_docs.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_fmt.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_game_engine.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_grammar_canonical.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_grammar_ebnf.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_hoodlib_disk.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_importer.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_kernel.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_kernel_zmq.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_keywords_doc.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_learn.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_lint.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_migrate_sourcing.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_playground.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_release.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_repl.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_replay.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_transpiler.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_vscode_extension.py +0 -0
- {hoodscript-1.4.0 → hoodscript-1.6.0}/tests/test_wheel_contents.py +0 -0
|
@@ -5,6 +5,66 @@ Versioning: the keyword set is the public API; changing it is at least a minor b
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.6.0] — 2026-09-12
|
|
9
|
+
|
|
10
|
+
Generalized intensive continuative `steady` (Rule R6), 5 new exception mappings
|
|
11
|
+
in the `*Trip` hierarchy, VS Code extension v1.5.0 package, and curriculum updates.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Generalized Intensive Continuative `steady` (Rule R6):**
|
|
16
|
+
`steady` now supports conditional loops in addition to infinite loops:
|
|
17
|
+
`steady <expr>:` → `while <expr>:` (and `steady:` → `while True:`).
|
|
18
|
+
Fully bidirectional: `python_to_hood` (`hood fmt`) maps `while <expr>:` → `steady <expr>:`
|
|
19
|
+
and `while True:` → `steady:`. Grounded in the Yale Grammatical Diversity Project
|
|
20
|
+
intensive continuative aspect (*Steady*, Baugh 1984). EBNF and Hypothesis fuzzer updated.
|
|
21
|
+
- **Five New Exception Mappings in the `*Trip` Hierarchy:**
|
|
22
|
+
`TimeoutTrip` (`TimeoutError`), `InterruptTrip` (`KeyboardInterrupt`), `SystemProblem`
|
|
23
|
+
(`OSError`), `NotDoneTrip` (`NotImplementedError`), and `DisconnectTrip` (`ConnectionError`).
|
|
24
|
+
All 1:1 token-substituted, reverse-translated via `python_to_hood`, and equipped with rich
|
|
25
|
+
conversational plain-English diagnostic cards in the JPE error catalog (HS0140, HS0150,
|
|
26
|
+
HS0160, HS0170).
|
|
27
|
+
- **VS Code Extension Update (`v1.5.0`):**
|
|
28
|
+
Added syntax highlighting in `hoodscript.tmLanguage.json` for `be done`, `finna be done`,
|
|
29
|
+
and `SystemProblem`. Added snippets for `be done` and `steady cond`. Built standalone
|
|
30
|
+
`hoodscript-1.5.0.vsix` distribution package.
|
|
31
|
+
- **Curriculum & CLI Updates:**
|
|
32
|
+
Updated `docs/curriculum.md` Lessons 3, 4, and 8 to showcase `steady <cond>:` and `be done`
|
|
33
|
+
file handling, verified by `tests/test_docs.py` and `hoodscript learn`.
|
|
34
|
+
|
|
35
|
+
## [1.5.0] — 2026-09-12
|
|
36
|
+
|
|
37
|
+
Rule R13 (future resultative `be done` → `with`), JPE guidance for retired words,
|
|
38
|
+
and owner sign-off on the sourcing audit (`cap`/`no cap` retained with liability notes).
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `tests/test_lint_fuzz.py`: the grammar fuzzer from `test_fuzz_ebnf.py`
|
|
43
|
+
driven against the linter's pure-`ast` rules (HL002, HL004–HL006) — 2,000
|
|
44
|
+
generated programs on the thorough profile, no crashes, every finding has a
|
|
45
|
+
filled what/why/fix. PROMPTS.md §2.2 now asks for both fuzz files together
|
|
46
|
+
after a `patterns.py`/`linter.py` change.
|
|
47
|
+
- **Sourcing audit signed off by owner (2026-09-12):**
|
|
48
|
+
Owner decision on `cap` / `no cap`: kept in Tier B, with the liability screen
|
|
49
|
+
hit (GDoS/UD secondary sense) and 2005-test failure (2012 earliest) notes
|
|
50
|
+
recorded in `GRAMMAR.md` and `hoodscript/docs/linguistics.md`. Sourcing audit
|
|
51
|
+
status corrections applied to the spec: `trip` single-source caveat lifted
|
|
52
|
+
(two sources confirmed); `dip` updated to dictionary + corpus; `chill` cited
|
|
53
|
+
to Lee 1999 (*American Speech*); `fam` cited to OED and Dictionary.com;
|
|
54
|
+
`tryna` re-ranked below `finna`; Labov 1972 cited for `ain't`.
|
|
55
|
+
- **Rule R13: Future Resultative `be done` → Context Manager (`with`):**
|
|
56
|
+
Added normative rule R13 implementing `be done expr [as target]:` → `with expr [as target]:`
|
|
57
|
+
and `finna be done expr [as target]:` → `async with expr [as target]:`. Sourced to
|
|
58
|
+
Labov (1972), Dayton (1996), and Green (2002) as the future resultative aspect marking
|
|
59
|
+
guaranteed, inevitable completion — matching Python's context manager contract. Reversible
|
|
60
|
+
via `python_to_hood` (`hood fmt`). EBNF updated in `docs/grammar.ebnf` and tested in
|
|
61
|
+
`tests/test_patterns.py` and `tests/test_fuzz_ebnf.py`.
|
|
62
|
+
- **JPE Did-You-Mean for Retired Lexicon:**
|
|
63
|
+
Added retired/cut keyword recognition in `errors.translator._suggest()` so typos or legacy
|
|
64
|
+
code (`cook`, `serve`, `Facts`, `Ghost`, `stand_on_it`, `nada`, `peep`, `crew`, `bounce`)
|
|
65
|
+
receive immediate, conversational guidance pointing to their canonical HoodScript keywords
|
|
66
|
+
(`bet`, `dip`, `no cap`, `MissingFileTrip`, `AssertTrip`, `None`, `import`, `list`).
|
|
67
|
+
|
|
8
68
|
## [1.4.0] — 2026-09-12
|
|
9
69
|
|
|
10
70
|
New library module, new CLI command, three new lint rules, and the prompt
|
|
@@ -25,6 +85,14 @@ aligner. Keyword set unchanged (v1.1).
|
|
|
25
85
|
2026-09-12 — DRAFT" (`cap`/`no cap` liability + 2018 dating; `trip` now
|
|
26
86
|
two-source; `dip` meets the corpus clause; `tryna` re-ranked; Rickford →
|
|
27
87
|
Labov on the `ain't` row). No keyword changed.
|
|
88
|
+
- **Tier B sourcing sprint** (PROMPTS.md §3.8): an evidence ledger for the
|
|
89
|
+
eight naming rows in `hoodscript/docs/linguistics.md` — per row, the
|
|
90
|
+
source stating Black/AAVE use (quoted, opened 2026-09-13), the second
|
|
91
|
+
source, CORAAL count, the clause of the Tier B rule it meets, and the
|
|
92
|
+
2005 test. `trip` is two-source; `dip` meets dictionary + corpus; `chill`'s
|
|
93
|
+
second source is Lee 1999 (*American Speech*); `no cap` fails the 2005
|
|
94
|
+
test (2012 earliest). Primaries (Smitherman, Major, Widawski, ODAAE)
|
|
95
|
+
remain unopenable from here and the section says exactly why.
|
|
28
96
|
- **`hoodscript migrate <file.hs>`** — rewrites v1.0.0 code to the v1.1
|
|
29
97
|
lexicon from the CHANGELOG migration table: two line-preserving token
|
|
30
98
|
passes (v1.0.0 map → Python → `py2hood`), so `cook`→`bet`, `serve`→`dip`,
|
|
@@ -101,7 +101,8 @@ n is 4
|
|
|
101
101
|
|
|
102
102
|
`be x in xs` is `for x in xs` — habitual *be*, the thing that happens over
|
|
103
103
|
and over. `1..3` is 1, 2, 3 — inclusive, unlike Python's `range`. `steady:`
|
|
104
|
-
is `while True:` — keep going until `break`. `
|
|
104
|
+
is `while True:` — keep going until `break`. `steady <cond>:` is `while <cond>:`.
|
|
105
|
+
`skip` is `continue`.
|
|
105
106
|
|
|
106
107
|
---
|
|
107
108
|
|
|
@@ -115,7 +116,7 @@ bet greet(who, greeting="wassup"):
|
|
|
115
116
|
double = lambda x: x * 2
|
|
116
117
|
holla(greet("fam"), greet("Khris", "yo"), double(21))
|
|
117
118
|
bet countdown(n):
|
|
118
|
-
|
|
119
|
+
steady n > 0:
|
|
119
120
|
yield n
|
|
120
121
|
n = n - 1
|
|
121
122
|
holla(list(countdown(3)))
|
|
@@ -243,14 +244,14 @@ exception without a `*Trip` name is caught under its Python name.
|
|
|
243
244
|
|
|
244
245
|
## Lesson 8 — Modules and files
|
|
245
246
|
|
|
246
|
-
**Keywords:**
|
|
247
|
+
**Keywords:** `be done`
|
|
247
248
|
|
|
248
249
|
```hood
|
|
249
250
|
import json
|
|
250
251
|
from pathlib import Path
|
|
251
252
|
import math as m
|
|
252
253
|
holla(json.dumps({"ok": True}), m.sqrt(16), Path("."))
|
|
253
|
-
|
|
254
|
+
be done open("out.txt", "w") as f:
|
|
254
255
|
f.write("written from HoodScript\n")
|
|
255
256
|
holla(open("out.txt").read().strip())
|
|
256
257
|
```
|
|
@@ -260,7 +261,8 @@ holla(open("out.txt").read().strip())
|
|
|
260
261
|
written from HoodScript
|
|
261
262
|
```
|
|
262
263
|
|
|
263
|
-
The whole Python ecosystem is available
|
|
264
|
+
The whole Python ecosystem is available. A context manager (`with`) is
|
|
265
|
+
`be done ... as f:` — future resultative: guaranteed completion. To import
|
|
264
266
|
your own `.hs` files, see the onboarding guide §6–7.
|
|
265
267
|
|
|
266
268
|
---
|
|
@@ -10,7 +10,7 @@ program = { statement } ;
|
|
|
10
10
|
|
|
11
11
|
statement = output_stmt | assignment | const_decl | func_def | class_def
|
|
12
12
|
| return_stmt | habitual_loop | continuative_loop | while_loop
|
|
13
|
-
| conditional | try_stmt | throw_stmt | import_stmt
|
|
13
|
+
| context_stmt | conditional | try_stmt | throw_stmt | import_stmt
|
|
14
14
|
| loop_control | pass_stmt | python_stmt | expr_stmt ;
|
|
15
15
|
|
|
16
16
|
output_stmt = "holla" [ expression { "," expression } | "(" [ args ] ")" ] ;
|
|
@@ -18,12 +18,14 @@ input_expr = "ask" ( expression | "(" [ args ] ")" ) ;
|
|
|
18
18
|
assignment = target "=" expression ;
|
|
19
19
|
const_decl = "BIN" IDENT "=" expression ;
|
|
20
20
|
|
|
21
|
+
context_stmt = [ "finna" ] "be" "done" expression [ "as" target ] ":" block ;
|
|
22
|
+
|
|
21
23
|
func_def = [ "finna" ] "bet" IDENT "(" [ params ] ")" ":" block ;
|
|
22
24
|
return_stmt = ( "dip" | "return" ) [ expression ] ;
|
|
23
25
|
class_def = "fam" IDENT [ "(" IDENT { "," IDENT } ")" ] ":" block ;
|
|
24
26
|
|
|
25
27
|
habitual_loop = "be" target "in" expression ":" block ;
|
|
26
|
-
continuative_loop = "steady" ":" block ;
|
|
28
|
+
continuative_loop = "steady" [ expression ] ":" block ;
|
|
27
29
|
while_loop = "while" expression ":" block ;
|
|
28
30
|
loop_control = "skip" | "break" ;
|
|
29
31
|
pass_stmt = "chill" ;
|
|
@@ -72,6 +72,11 @@ Every other Python keyword and builtin is used under its Python name.
|
|
|
72
72
|
| `MissingModuleTrip` | `ModuleNotFoundError` |
|
|
73
73
|
| `BadImportTrip` | `ImportError` |
|
|
74
74
|
| `NoAccessTrip` | `PermissionError` |
|
|
75
|
+
| `TimeoutTrip` | `TimeoutError` |
|
|
76
|
+
| `InterruptTrip` | `KeyboardInterrupt` |
|
|
77
|
+
| `SystemProblem` | `OSError` |
|
|
78
|
+
| `NotDoneTrip` | `NotImplementedError` |
|
|
79
|
+
| `DisconnectTrip` | `ConnectionError` |
|
|
75
80
|
|
|
76
81
|
## Reserved words
|
|
77
82
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.6.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 6, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -74,12 +74,13 @@ def print_tokens() -> None:
|
|
|
74
74
|
("no cap", "True", "R3 Truth assertion"),
|
|
75
75
|
("cap", "False", "R3 Falsehood"),
|
|
76
76
|
("it's x", "(x is not None)", "R4 Existence test"),
|
|
77
|
-
("steady:", "while True:", "R5 Persistent loop"),
|
|
77
|
+
("steady [x]:", "while True: / while x:", "R5 Persistent loop"),
|
|
78
78
|
("a..b", "range(a, (b) + 1)", "R6 Inclusive range"),
|
|
79
79
|
("BIN X = v", "X: Final = v", "R7 Constant declaration"),
|
|
80
80
|
("else if", "elif", "R8 Chained conditional"),
|
|
81
81
|
("holla x, y", "print(x, y)", "R9 Statement-form print"),
|
|
82
82
|
("ask x", "input(x)", "R10 Statement-form input"),
|
|
83
|
+
("be done x:", "with x:", "R13 Context manager"),
|
|
83
84
|
]
|
|
84
85
|
for src, py, desc in patterns:
|
|
85
86
|
print(f" {src:<20} -> {py:<25} ({desc})")
|
|
@@ -54,6 +54,11 @@ HOOD_EXCEPTION_NAMES = {
|
|
|
54
54
|
"MissingModuleTrip": "ModuleNotFoundError",
|
|
55
55
|
"BadImportTrip": "ImportError",
|
|
56
56
|
"NoAccessTrip": "PermissionError",
|
|
57
|
+
"TimeoutTrip": "TimeoutError",
|
|
58
|
+
"InterruptTrip": "KeyboardInterrupt",
|
|
59
|
+
"SystemProblem": "OSError",
|
|
60
|
+
"NotDoneTrip": "NotImplementedError",
|
|
61
|
+
"DisconnectTrip": "ConnectionError",
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
HOOD_KEYWORD_MAP = {**TIER_A, **TIER_B, **TIER_C, **HOOD_EXCEPTION_NAMES}
|
|
@@ -215,6 +215,34 @@ CATALOG: list[Entry] = [
|
|
|
215
215
|
pattern=r"'(?P<value>[^']+)'$",
|
|
216
216
|
fields=("value",)),
|
|
217
217
|
|
|
218
|
+
# --- TimeoutError ------------------------------------------------------
|
|
219
|
+
Entry("HS0140", TimeoutError,
|
|
220
|
+
what="The operation took too long and timed out: {raw}.",
|
|
221
|
+
why="Something you asked for took longer than the time limit allowed.",
|
|
222
|
+
fix="Increase the timeout limit, or check whether the network or task is stuck.",
|
|
223
|
+
example="throw TimeoutTrip(\"timed out\")\n"),
|
|
224
|
+
|
|
225
|
+
# --- NotImplementedError -----------------------------------------------
|
|
226
|
+
Entry("HS0150", NotImplementedError,
|
|
227
|
+
what="This part hasn't been built yet.",
|
|
228
|
+
why="A function or method was called that was left as a placeholder or needs to be overridden.",
|
|
229
|
+
fix="Fill in the body of the function, or provide the implementation in your class.",
|
|
230
|
+
example="throw NotDoneTrip(\"not ready\")\n"),
|
|
231
|
+
|
|
232
|
+
# --- ConnectionError ---------------------------------------------------
|
|
233
|
+
Entry("HS0160", ConnectionError,
|
|
234
|
+
what="Could not connect to the network service.",
|
|
235
|
+
why="The network connection was refused, dropped, or the destination could not be reached.",
|
|
236
|
+
fix="Check your internet connection, verify the address and port, or try again later.",
|
|
237
|
+
example="throw DisconnectTrip(\"connection refused\")\n"),
|
|
238
|
+
|
|
239
|
+
# --- OSError -----------------------------------------------------------
|
|
240
|
+
Entry("HS0170", OSError,
|
|
241
|
+
what="The operating system reported a problem: {raw}.",
|
|
242
|
+
why="The operating system could not complete a disk, network, or hardware operation.",
|
|
243
|
+
fix="Check system resources, permissions, and available disk space.",
|
|
244
|
+
example="throw SystemProblem(\"device unavailable\")\n"),
|
|
245
|
+
|
|
218
246
|
# ---------------------------------------------------------------------------
|
|
219
247
|
# Compile-time (SyntaxError family). Line/col come from the exception itself.
|
|
220
248
|
# ---------------------------------------------------------------------------
|
|
@@ -70,13 +70,33 @@ def _module_names(filename):
|
|
|
70
70
|
return names
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
RETIRED_SUGGESTIONS = {
|
|
74
|
+
"cook": "bet",
|
|
75
|
+
"serve": "dip",
|
|
76
|
+
"Facts": "no cap",
|
|
77
|
+
"Ghost": "MissingFileTrip",
|
|
78
|
+
"GhostFileTrip": "MissingFileTrip",
|
|
79
|
+
"stand_on_it": "assert",
|
|
80
|
+
"StandOnItTrip": "AssertTrip",
|
|
81
|
+
"nada": "None",
|
|
82
|
+
"peep": "import",
|
|
83
|
+
"crew": "list",
|
|
84
|
+
"bounce": "dip",
|
|
85
|
+
"holler": "holla",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
73
89
|
def _suggest(exc, tb, filename):
|
|
74
90
|
if isinstance(exc, NameError) and not isinstance(exc, UnboundLocalError):
|
|
75
91
|
name = getattr(exc, "name", None)
|
|
92
|
+
if name in RETIRED_SUGGESTIONS:
|
|
93
|
+
return RETIRED_SUGGESTIONS[name]
|
|
76
94
|
return _close(name, _scope_names(tb)) if name else None
|
|
77
95
|
if isinstance(exc, AttributeError):
|
|
78
96
|
obj, name = getattr(exc, "obj", None), getattr(exc, "name", None)
|
|
79
97
|
if name is not None and obj is not None:
|
|
98
|
+
if name in RETIRED_SUGGESTIONS:
|
|
99
|
+
return RETIRED_SUGGESTIONS[name]
|
|
80
100
|
return _close(name, dir(obj))
|
|
81
101
|
if isinstance(exc, ModuleNotFoundError):
|
|
82
102
|
name = getattr(exc, "name", None)
|
|
@@ -17,7 +17,7 @@ server = LanguageServer("hoodscript-lsp", __version__)
|
|
|
17
17
|
_WORD = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
|
18
18
|
|
|
19
19
|
_PATTERNS = {
|
|
20
|
-
"steady": "`steady:` → `while True:`",
|
|
20
|
+
"steady": "`steady:` → `while True:` / `steady expr:` → `while expr:`",
|
|
21
21
|
"BIN": "`BIN X = v` → `X: Final = v`",
|
|
22
22
|
"no": "`no cap` → `True`",
|
|
23
23
|
"nobody": "`ain't nobody x` → `not x`",
|
|
@@ -211,9 +211,28 @@ def _edits(text):
|
|
|
211
211
|
edits.append((row, end, end, " is not None)")) # wrap; inner edits survive
|
|
212
212
|
i += 1; continue # keep scanning inside the expression
|
|
213
213
|
|
|
214
|
-
# R6 steady: -> while True:
|
|
215
|
-
if _is(t, tokenize.NAME, "steady") and _stmt_start(toks, i)
|
|
216
|
-
|
|
214
|
+
# R6 steady: -> while True: / steady expr: -> while expr:
|
|
215
|
+
if _is(t, tokenize.NAME, "steady") and _stmt_start(toks, i):
|
|
216
|
+
if nxt is not None and _is(nxt, tokenize.OP, ":"):
|
|
217
|
+
edits.append((row, col, t.end[1], "while True")); i += 1; continue
|
|
218
|
+
else:
|
|
219
|
+
k = i + 1
|
|
220
|
+
has_header_colon = False
|
|
221
|
+
depth = 0
|
|
222
|
+
while k < len(toks) and toks[k].type not in (tokenize.NEWLINE, tokenize.ENDMARKER):
|
|
223
|
+
if toks[k].type == tokenize.OP:
|
|
224
|
+
if toks[k].string in _OPENERS:
|
|
225
|
+
depth += 1
|
|
226
|
+
elif toks[k].string in _CLOSERS:
|
|
227
|
+
depth -= 1
|
|
228
|
+
elif depth == 0 and toks[k].string == ":":
|
|
229
|
+
has_header_colon = True
|
|
230
|
+
break
|
|
231
|
+
elif depth == 0 and toks[k].string == "=":
|
|
232
|
+
break
|
|
233
|
+
k += 1
|
|
234
|
+
if has_header_colon:
|
|
235
|
+
edits.append((row, col, t.end[1], "while")); i += 1; continue
|
|
217
236
|
|
|
218
237
|
# R9 BIN X = v -> [from typing import Final; ]X: Final = v
|
|
219
238
|
if _is(t, tokenize.NAME, "BIN") and _stmt_start(toks, i):
|
|
@@ -270,6 +289,15 @@ def _edits(text):
|
|
|
270
289
|
and _is(nxt, tokenize.NAME, "if") and nxt.start[0] == row):
|
|
271
290
|
edits.append((row, col, nxt.end[1], "elif")); i += 2; continue
|
|
272
291
|
|
|
292
|
+
# R13 be done expr: -> with expr: (or finna be done -> async with)
|
|
293
|
+
if (_is(t, tokenize.NAME, "be") and nxt is not None and _is(nxt, tokenize.NAME, "done")
|
|
294
|
+
and nxt.start[0] == row):
|
|
295
|
+
at_be_done = _stmt_start(toks, i) or (
|
|
296
|
+
i > 0 and _is(toks[i - 1], tokenize.NAME, "finna") and _stmt_start(toks, i - 1)
|
|
297
|
+
)
|
|
298
|
+
if at_be_done:
|
|
299
|
+
edits.append((row, col, nxt.end[1], "with")); i += 2; continue
|
|
300
|
+
|
|
273
301
|
i += 1
|
|
274
302
|
return lines, edits
|
|
275
303
|
|
|
@@ -331,9 +359,16 @@ def _reverse_edits(text):
|
|
|
331
359
|
same_row = i + 1 < len(toks) and toks[i + 1].start[0] == row
|
|
332
360
|
stmt = _stmt_start(toks, i)
|
|
333
361
|
|
|
334
|
-
# while True: -> steady:
|
|
335
|
-
if name(i, "while") and stmt
|
|
336
|
-
|
|
362
|
+
# while True: -> steady: / while expr: -> steady expr:
|
|
363
|
+
if name(i, "while") and stmt:
|
|
364
|
+
if same_row and name(i + 1, "True") and op(i + 2, ":"):
|
|
365
|
+
edits.append((row, col, toks[i + 1].end[1], "steady")); i += 2; continue
|
|
366
|
+
else:
|
|
367
|
+
edits.append((row, col, t.end[1], "steady")); i += 1; continue
|
|
368
|
+
|
|
369
|
+
# with expr: -> be done expr: (also async with -> finna be done)
|
|
370
|
+
if name(i, "with") and (stmt or (i > 0 and name(i - 1, "async") and _stmt_start(toks, i - 1))):
|
|
371
|
+
edits.append((row, col, t.end[1], "be done")); i += 1; continue
|
|
337
372
|
|
|
338
373
|
# elif -> else if
|
|
339
374
|
if name(i, "elif") and stmt:
|
|
@@ -57,8 +57,14 @@ CUT: frozenset[str] = frozenset({
|
|
|
57
57
|
})
|
|
58
58
|
|
|
59
59
|
# Multi-token forms (patterns.py) that are keywords without being NAME entries.
|
|
60
|
-
PATTERN_FORMS = {
|
|
61
|
-
|
|
60
|
+
PATTERN_FORMS = {
|
|
61
|
+
"steady": "while True: / while expr:",
|
|
62
|
+
"BIN": "X: Final = v",
|
|
63
|
+
"ain't": "not",
|
|
64
|
+
"it's": "(x is not None)",
|
|
65
|
+
"no cap": "True",
|
|
66
|
+
"trip": "*Trip exception suffix",
|
|
67
|
+
}
|
|
62
68
|
|
|
63
69
|
|
|
64
70
|
# ----------------------------------------------------------------------------- 1. standing
|
|
@@ -80,4 +80,5 @@ def test_exception_name_prefers_most_specific():
|
|
|
80
80
|
assert get_hood_exception_name(SyntaxError) == "BadSyntaxTrip"
|
|
81
81
|
assert get_hood_exception_name(KeyError) == "MissingKeyTrip"
|
|
82
82
|
assert get_hood_exception_name(RuntimeError) == "Problem"
|
|
83
|
-
assert get_hood_exception_name(KeyboardInterrupt) == "
|
|
83
|
+
assert get_hood_exception_name(KeyboardInterrupt) == "InterruptTrip"
|
|
84
|
+
assert get_hood_exception_name(SystemExit) == "BaseProblem"
|
|
@@ -85,6 +85,16 @@ def test_did_you_mean_knows_hoodscript_keywords():
|
|
|
85
85
|
assert d.suggestion == "holla"
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
def test_did_you_mean_knows_retired_words():
|
|
89
|
+
exc = _run_example("x = cook\n")
|
|
90
|
+
d = diagnose(type(exc), exc, exc.__traceback__)
|
|
91
|
+
assert d.suggestion == "bet"
|
|
92
|
+
exc = _run_example("x = Facts\n")
|
|
93
|
+
assert diagnose(type(exc), exc, exc.__traceback__).suggestion == "no cap"
|
|
94
|
+
exc = _run_example("x = Ghost\n")
|
|
95
|
+
assert diagnose(type(exc), exc, exc.__traceback__).suggestion == "MissingFileTrip"
|
|
96
|
+
|
|
97
|
+
|
|
88
98
|
def test_did_you_mean_for_attributes_and_modules():
|
|
89
99
|
exc = _run_example("fam Dog:\n bet speak(self):\n chill\nDog().speek()\n")
|
|
90
100
|
assert diagnose(type(exc), exc, exc.__traceback__).suggestion == "speak"
|
|
@@ -113,12 +113,15 @@ def _stmt(depth=0):
|
|
|
113
113
|
st.tuples(_ident, _postfix(), _postfix(), body).map(
|
|
114
114
|
lambda t: f"be {t[0]} in {t[1]}..{t[2]}:\n{_indent(t[3])}"),
|
|
115
115
|
body.map(lambda b: f"steady:\n{_indent(b)}"),
|
|
116
|
+
st.tuples(_expr(), body).map(lambda t: f"steady {t[0]}:\n{_indent(t[1])}"),
|
|
116
117
|
st.tuples(_expr(), body).map(lambda t: f"while {t[0]}:\n{_indent(t[1])}"),
|
|
117
118
|
st.tuples(_ident, st.lists(_ident, max_size=2, unique=True), body).map(
|
|
118
119
|
lambda t: f"bet {t[0]}({', '.join(t[1])}):\n{_indent(t[2])}"),
|
|
119
120
|
st.tuples(_ident, st.lists(_ident, max_size=2, unique=True), body).map(
|
|
120
121
|
lambda t: f"finna bet {t[0]}({', '.join(t[1])}):\n{_indent(t[2])}"),
|
|
121
122
|
st.tuples(_ident, body).map(lambda t: f"fam {t[0]}:\n{_indent(t[1])}"),
|
|
123
|
+
st.tuples(_ident, body).map(lambda t: f"be done {t[0]}:\n{_indent(t[1])}"),
|
|
124
|
+
st.tuples(_ident, _ident, body).map(lambda t: f"be done {t[0]} as {t[1]}:\n{_indent(t[2])}"),
|
|
122
125
|
st.tuples(body, body).map(lambda t: f"tryna:\n{_indent(t[0])}\ncatch BadValueTrip as e:\n{_indent(t[1])}"),
|
|
123
126
|
st.tuples(body, body, body).map(
|
|
124
127
|
lambda t: f"tryna:\n{_indent(t[0])}\ncatch:\n{_indent(t[1])}\nregardless:\n{_indent(t[2])}"),
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Property test for the linter: `lint_source`'s ast-only checks (HL002, HL004,
|
|
2
|
+
HL005, HL006) must never crash, no matter what valid HoodScript the grammar
|
|
3
|
+
fuzzer generates. tests/test_fuzz_ebnf.py already builds a grammar-directed
|
|
4
|
+
generator of valid programs; this reuses it rather than duplicating one.
|
|
5
|
+
|
|
6
|
+
ruff- and mypy-backed findings (HL001, HL003) are not exercised here — they
|
|
7
|
+
shell out, which is what test_lint.py's few hand-written cases already cover;
|
|
8
|
+
this test is about the pure-ast rules added with hoodlib.chart/replay."""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from hypothesis import HealthCheck, given, settings
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
17
|
+
|
|
18
|
+
from hoodscript.linter import _check_ast_rules, _check_unreachable # noqa: E402
|
|
19
|
+
from hoodscript.migrator import hood_to_python # noqa: E402
|
|
20
|
+
from test_fuzz_ebnf import _python_accepts, programs # noqa: E402
|
|
21
|
+
|
|
22
|
+
settings.register_profile("thorough", max_examples=2000, deadline=None)
|
|
23
|
+
settings.register_profile("default", max_examples=200, deadline=None,
|
|
24
|
+
suppress_health_check=[HealthCheck.too_slow])
|
|
25
|
+
settings.load_profile(os.environ.get("HYPOTHESIS_PROFILE", "default"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@given(programs)
|
|
29
|
+
def test_ast_lint_rules_never_crash_on_valid_programs(src):
|
|
30
|
+
py = hood_to_python(src)
|
|
31
|
+
if not _python_accepts(py):
|
|
32
|
+
return
|
|
33
|
+
findings = _check_unreachable(py, "fuzz.hs") + _check_ast_rules(py, "fuzz.hs")
|
|
34
|
+
for f in findings:
|
|
35
|
+
assert f.line >= 1 and f.col >= 1
|
|
36
|
+
assert f.what and f.why and f.fix
|
|
37
|
+
assert "{" not in f.what and "{" not in f.why and "{" not in f.fix # no unfilled template braces
|
|
@@ -31,7 +31,7 @@ def test_lsp_hover_keywords_and_patterns():
|
|
|
31
31
|
assert _describe("bet") == "**`bet`** → Python `def`"
|
|
32
32
|
assert _describe("holla") == "**`holla`** → Python `print`"
|
|
33
33
|
assert _describe("ZeroSplitTrip") == "**`ZeroSplitTrip`** → Python exception `ZeroDivisionError`"
|
|
34
|
-
assert _describe("steady") == "`steady:` → `while True:`"
|
|
34
|
+
assert _describe("steady") == "`steady:` → `while True:` / `steady expr:` → `while expr:`"
|
|
35
35
|
assert _describe("unknown_word") is None
|
|
36
36
|
|
|
37
37
|
|
|
@@ -15,6 +15,8 @@ RULES = [
|
|
|
15
15
|
("R5b", "if it's a and b:\n chill\n", "if (a is not None) and b:\n pass\n"),
|
|
16
16
|
("R5c", "f(it's x, 2)\n", "f((x is not None), 2)\n"),
|
|
17
17
|
("R6", "steady:\n break\n", "while True:\n break\n"),
|
|
18
|
+
("R6b", "steady count < 5:\n chill\n", "while count < 5:\n pass\n"),
|
|
19
|
+
("R6c", "steady it's item:\n chill\n", "while (item is not None):\n pass\n"),
|
|
18
20
|
("R7", "be x in xs:\n chill\n", "for x in xs:\n pass\n"),
|
|
19
21
|
("R8", "finna bet f():\n chill\n", "async def f():\n pass\n"),
|
|
20
22
|
("R9", "BIN LIMIT = 5\n", "from typing import Final; LIMIT: Final = 5\n"),
|
|
@@ -30,6 +32,11 @@ RULES = [
|
|
|
30
32
|
("R11d", 'holla("call form")\n', 'print("call form")\n'),
|
|
31
33
|
("R12", 'ask "name? "\n', 'input("name? ")\n'),
|
|
32
34
|
("R12b", 'line = ask("> ")\n', 'line = input("> ")\n'),
|
|
35
|
+
("R13", "be done open('f') as f:\n chill\n", "with open('f') as f:\n pass\n"),
|
|
36
|
+
("R13b", "finna bet f():\n finna be done open('f') as f:\n chill\n",
|
|
37
|
+
"async def f():\n async with open('f') as f:\n pass\n"),
|
|
38
|
+
("R13c", "be done lock:\n chill\n", "with lock:\n pass\n"),
|
|
39
|
+
("R13d", "be done a() as x, b() as y:\n chill\n", "with a() as x, b() as y:\n pass\n"),
|
|
33
40
|
("elif", "if x:\n chill\nelse if y:\n chill\n", "if x:\n pass\nelif y:\n pass\n"),
|
|
34
41
|
]
|
|
35
42
|
|
|
@@ -84,6 +91,7 @@ def test_patterns_compose():
|
|
|
84
91
|
|
|
85
92
|
@pytest.mark.parametrize("py,hood", [
|
|
86
93
|
("while True:\n break\n", "steady:\n break\n"),
|
|
94
|
+
("while count < 5:\n pass\n", "steady count < 5:\n chill\n"),
|
|
87
95
|
("if a:\n pass\nelif b:\n pass\n", "if a:\n chill\nelse if b:\n chill\n"),
|
|
88
96
|
("x = True\ny = not x\nz = a is not b\nw = a not in b\n",
|
|
89
97
|
"x = no cap\ny = ain't x\nz = a is not b\nw = a not in b\n"),
|
|
@@ -95,6 +103,8 @@ def test_patterns_compose():
|
|
|
95
103
|
("print(a, b)\nprint()\nx = print(1)\ninput(\"> \")\n", 'holla a, b\nholla\nx = holla(1)\nask "> "\n'),
|
|
96
104
|
("print(i, True, not x)\n", "holla i, no cap, ain't x\n"),
|
|
97
105
|
("s = \"it's not True\" # not a comment\n", "s = \"it's not True\" # not a comment\n"),
|
|
106
|
+
("with open('f') as f:\n pass\n", "be done open('f') as f:\n chill\n"),
|
|
107
|
+
("async with open('f') as f:\n pass\n", "finna be done open('f') as f:\n chill\n"),
|
|
98
108
|
])
|
|
99
109
|
def test_reverse_patterns(py, hood):
|
|
100
110
|
from hoodscript.migrator import python_to_hood
|
|
@@ -110,3 +110,15 @@ def test_screen_script_uses_word_boundaries():
|
|
|
110
110
|
def test_cut_list_is_not_empty_and_has_no_live_keyword():
|
|
111
111
|
assert len(CUT) >= 50
|
|
112
112
|
assert not (CUT & set(HOOD_KEYWORD_MAP))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.parametrize("word", sorted(TIER_B) + ["no cap", "trip"])
|
|
116
|
+
def test_tier_b_ledger_names_the_clause_met(word):
|
|
117
|
+
"""The Tier B rule has two clauses (two dictionaries, or one + CORAAL).
|
|
118
|
+
The evidence ledger must say which one each row meets."""
|
|
119
|
+
text = LINGUISTICS.read_text(encoding="utf-8")
|
|
120
|
+
ledger = text.split("### Evidence ledger")[1].split("### What changes")[0]
|
|
121
|
+
rows = [ln for ln in ledger.splitlines() if ln.startswith(f"| `{word}`")]
|
|
122
|
+
assert rows, f"no ledger row for `{word}`"
|
|
123
|
+
clauses = ("two dictionaries", "dictionary + corpus", "two sources")
|
|
124
|
+
assert any(c in rows[0] for c in clauses), f"ledger row for `{word}` does not name a clause"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|