hoodscript 1.1.0__py3-none-any.whl
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/__init__.py +9 -0
- hoodscript/__main__.py +6 -0
- hoodscript/_version.py +24 -0
- hoodscript/cache.py +36 -0
- hoodscript/cli.py +468 -0
- hoodscript/constants.py +69 -0
- hoodscript/data/curriculum.md +343 -0
- hoodscript/debugger.py +71 -0
- hoodscript/errors/__init__.py +12 -0
- hoodscript/errors/catalog.py +315 -0
- hoodscript/errors/renderer.py +85 -0
- hoodscript/errors/translator.py +207 -0
- hoodscript/formatter.py +78 -0
- hoodscript/importer.py +40 -0
- hoodscript/linter.py +253 -0
- hoodscript/lsp.py +253 -0
- hoodscript/migrator.py +31 -0
- hoodscript/patterns.py +442 -0
- hoodscript/repl.py +177 -0
- hoodscript/sandbox.py +215 -0
- hoodscript/stubs.py +16 -0
- hoodscript/traceback_handler.py +63 -0
- hoodscript/transpiler.py +46 -0
- hoodscript/tutor.py +232 -0
- hoodscript-1.1.0.dist-info/METADATA +183 -0
- hoodscript-1.1.0.dist-info/RECORD +29 -0
- hoodscript-1.1.0.dist-info/WHEEL +4 -0
- hoodscript-1.1.0.dist-info/entry_points.txt +5 -0
- hoodscript-1.1.0.dist-info/licenses/LICENSE +3 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# HoodScript 10-Lesson Curriculum
|
|
2
|
+
|
|
3
|
+
Each lesson is one runnable file. Type it in, run it with `hoodscript
|
|
4
|
+
lessonN.hs`, then run `hoodscript -c lessonN.hs` to see the Python it becomes.
|
|
5
|
+
That second step is the whole trick: you are learning Python with a handful
|
|
6
|
+
of different words, and the words are the only thing that changes.
|
|
7
|
+
|
|
8
|
+
Expected output is shown under each program. Every program here is executed
|
|
9
|
+
by the test suite, so if yours differs, the crash report will point at the
|
|
10
|
+
line.
|
|
11
|
+
|
|
12
|
+
Keyword reference: [`keywords.md`](keywords.md). The words this curriculum
|
|
13
|
+
teaches: `holla` `ask` `bet` `dip` `fam` `be` `skip` `chill` `tryna` `catch`
|
|
14
|
+
`regardless` `throw` `finna` `done` `cap` and the `*Trip` exception names.
|
|
15
|
+
Everything else is Python.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Lesson 1 — Values and `holla`
|
|
20
|
+
|
|
21
|
+
**Keywords:** `holla` `cap` `no cap`
|
|
22
|
+
|
|
23
|
+
```hood
|
|
24
|
+
name = "Khris"
|
|
25
|
+
age = 30
|
|
26
|
+
height = 1.8
|
|
27
|
+
ready = no cap
|
|
28
|
+
lying = cap
|
|
29
|
+
nothing = None
|
|
30
|
+
holla name, age, height, ready, lying, nothing
|
|
31
|
+
holla type(age), int("42") + 1, str(3.5)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Khris 30 1.8 True False None
|
|
36
|
+
<class 'int'> 43 3.5
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Variables don't need declaring. `holla` is `print` — with or without
|
|
40
|
+
parentheses. `cap` is `False` (a lie); `no cap` is `True`. `type`, `int`,
|
|
41
|
+
`str` and every other builtin keep their Python names.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Lesson 2 — Decisions
|
|
46
|
+
|
|
47
|
+
**Keywords:** `else if` `ain't` `ain't nobody` `it's`
|
|
48
|
+
|
|
49
|
+
```hood
|
|
50
|
+
score = 87
|
|
51
|
+
if score >= 90:
|
|
52
|
+
holla "A"
|
|
53
|
+
else if score >= 80:
|
|
54
|
+
holla "B"
|
|
55
|
+
else:
|
|
56
|
+
holla "C or below"
|
|
57
|
+
if ain't score < 50 and it's score:
|
|
58
|
+
holla "passing"
|
|
59
|
+
name = ""
|
|
60
|
+
if ain't nobody name:
|
|
61
|
+
holla "no name given"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
B
|
|
66
|
+
passing
|
|
67
|
+
no name given
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`if`/`else` are Python's; `else if` is `elif`. `ain't` is `not`; `ain't
|
|
71
|
+
nobody` is the emphatic guard form (also `not`). `it's x` means `x` exists —
|
|
72
|
+
`(x is not None)` — so `it's 0` is true.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Lesson 3 — Loops: `be` and `skip`
|
|
77
|
+
|
|
78
|
+
**Keywords:** `be` `steady` `skip` and inclusive ranges `a..b`
|
|
79
|
+
|
|
80
|
+
```hood
|
|
81
|
+
be i in 1..3:
|
|
82
|
+
holla "lap", i
|
|
83
|
+
n = 0
|
|
84
|
+
steady:
|
|
85
|
+
n = n + 1
|
|
86
|
+
if n == 2:
|
|
87
|
+
skip
|
|
88
|
+
if n > 4:
|
|
89
|
+
break
|
|
90
|
+
holla "n is", n
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
lap 1
|
|
95
|
+
lap 2
|
|
96
|
+
lap 3
|
|
97
|
+
n is 1
|
|
98
|
+
n is 3
|
|
99
|
+
n is 4
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`be x in xs` is `for x in xs` — habitual *be*, the thing that happens over
|
|
103
|
+
and over. `1..3` is 1, 2, 3 — inclusive, unlike Python's `range`. `steady:`
|
|
104
|
+
is `while True:` — keep going until `break`. `skip` is `continue`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Lesson 4 — Functions: `bet` and `dip`
|
|
109
|
+
|
|
110
|
+
**Keywords:** `bet` `dip` `chill`
|
|
111
|
+
|
|
112
|
+
```hood
|
|
113
|
+
bet greet(who, greeting="wassup"):
|
|
114
|
+
dip greeting + ", " + who
|
|
115
|
+
double = lambda x: x * 2
|
|
116
|
+
holla(greet("fam"), greet("Khris", "yo"), double(21))
|
|
117
|
+
bet countdown(n):
|
|
118
|
+
while n > 0:
|
|
119
|
+
yield n
|
|
120
|
+
n = n - 1
|
|
121
|
+
holla(list(countdown(3)))
|
|
122
|
+
bet nothing_yet():
|
|
123
|
+
chill
|
|
124
|
+
holla(nothing_yet())
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
wassup, fam yo, Khris 42
|
|
129
|
+
[3, 2, 1]
|
|
130
|
+
None
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`bet` defines a function — a commitment. `dip` returns — you leave with the
|
|
134
|
+
value. `chill` is `pass`. `lambda` and `yield` are Python's.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Lesson 5 — Collections
|
|
139
|
+
|
|
140
|
+
**Keywords:** `BIN`
|
|
141
|
+
|
|
142
|
+
```hood
|
|
143
|
+
nums = [3, 1, 2]
|
|
144
|
+
holla(len(nums), sum(nums), sorted(nums))
|
|
145
|
+
book = dict(a=1, b=2)
|
|
146
|
+
be k, v in book.items():
|
|
147
|
+
holla(k, v)
|
|
148
|
+
be i, x in enumerate(["x", "y"]):
|
|
149
|
+
holla(i, x)
|
|
150
|
+
holla(list(zip([1, 2], ["a", "b"])), set([1, 1, 2]), tuple([1, 2]))
|
|
151
|
+
holla([x for x in nums if x > 1], list(map(lambda x: x * 10, nums)))
|
|
152
|
+
holla(all([no cap, True]), any([cap, True]))
|
|
153
|
+
BIN LIMIT = 2
|
|
154
|
+
holla nums[:LIMIT]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
3 6 [1, 2, 3]
|
|
159
|
+
a 1
|
|
160
|
+
b 2
|
|
161
|
+
0 x
|
|
162
|
+
1 y
|
|
163
|
+
[(1, 'a'), (2, 'b')] {1, 2} (1, 2)
|
|
164
|
+
[3, 2] [30, 10, 20]
|
|
165
|
+
True True
|
|
166
|
+
[3, 1]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Lists, dicts, sets, tuples, comprehensions, `len`, `sum`, `zip`, `map` —
|
|
170
|
+
all Python, all unchanged. There was no cited word for any of them, so
|
|
171
|
+
HoodScript doesn't pretend. `BIN X = v` is a constant (`X: Final = v`) —
|
|
172
|
+
stressed *BIN*: true for a long time, and still is.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Lesson 6 — Classes: `fam`
|
|
177
|
+
|
|
178
|
+
**Keywords:** `fam`
|
|
179
|
+
|
|
180
|
+
```hood
|
|
181
|
+
fam Animal:
|
|
182
|
+
bet __init__(self, name):
|
|
183
|
+
self.name = name
|
|
184
|
+
bet speak(self):
|
|
185
|
+
dip self.name + " makes a sound"
|
|
186
|
+
fam Dog(Animal):
|
|
187
|
+
bet speak(self):
|
|
188
|
+
dip super().speak() + " (woof)"
|
|
189
|
+
d = Dog("Rex")
|
|
190
|
+
holla(d.speak(), isinstance(d, Animal))
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
Rex makes a sound (woof) True
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`fam` is `class` — the close group an object belongs to. `self` and
|
|
198
|
+
`super()` are Python's.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Lesson 7 — Errors: `tryna`, `catch`, `regardless`, `throw`
|
|
203
|
+
|
|
204
|
+
**Keywords:** `tryna` `catch` `regardless` `throw` · **Exceptions:** `ZeroSplitTrip` `BadValueTrip`
|
|
205
|
+
|
|
206
|
+
```hood
|
|
207
|
+
bet divide(a, b):
|
|
208
|
+
tryna:
|
|
209
|
+
dip a / b
|
|
210
|
+
catch ZeroSplitTrip:
|
|
211
|
+
holla("can't split by zero")
|
|
212
|
+
dip None
|
|
213
|
+
regardless:
|
|
214
|
+
holla("done dividing")
|
|
215
|
+
holla(divide(6, 3))
|
|
216
|
+
holla(divide(1, 0))
|
|
217
|
+
bet check(x):
|
|
218
|
+
if x < 0:
|
|
219
|
+
throw BadValueTrip("x gotta be positive, got " + str(x))
|
|
220
|
+
dip x
|
|
221
|
+
tryna:
|
|
222
|
+
check(-1)
|
|
223
|
+
catch BadValueTrip as e:
|
|
224
|
+
holla("caught:", e)
|
|
225
|
+
assert check(1) == 1
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
done dividing
|
|
230
|
+
2.0
|
|
231
|
+
can't split by zero
|
|
232
|
+
done dividing
|
|
233
|
+
None
|
|
234
|
+
caught: x gotta be positive, got -1
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`tryna` is `try`. `catch`/`regardless`/`throw` are `except`/`finally`/`raise`
|
|
238
|
+
— plain English kept deliberately. The `*Trip` names are Python's exception
|
|
239
|
+
classes: `ZeroSplitTrip` *is* `ZeroDivisionError` (run `-c` and see). Any
|
|
240
|
+
exception without a `*Trip` name is caught under its Python name.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Lesson 8 — Modules and files
|
|
245
|
+
|
|
246
|
+
**Keywords:** none
|
|
247
|
+
|
|
248
|
+
```hood
|
|
249
|
+
import json
|
|
250
|
+
from pathlib import Path
|
|
251
|
+
import math as m
|
|
252
|
+
holla(json.dumps({"ok": True}), m.sqrt(16), Path("."))
|
|
253
|
+
with open("out.txt", "w") as f:
|
|
254
|
+
f.write("written from HoodScript\n")
|
|
255
|
+
holla(open("out.txt").read().strip())
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
{"ok": true} 4.0 .
|
|
260
|
+
written from HoodScript
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The whole Python ecosystem is available, spelled the Python way. To import
|
|
264
|
+
your own `.hs` files, see the onboarding guide §6–7.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Lesson 9 — Async: `finna` and `done`
|
|
269
|
+
|
|
270
|
+
**Keywords:** `finna` `done`
|
|
271
|
+
|
|
272
|
+
```hood
|
|
273
|
+
import asyncio
|
|
274
|
+
finna bet fetch(n):
|
|
275
|
+
done asyncio.sleep(0)
|
|
276
|
+
dip n * 2
|
|
277
|
+
finna bet main():
|
|
278
|
+
results = done asyncio.gather(fetch(1), fetch(2))
|
|
279
|
+
holla(results)
|
|
280
|
+
asyncio.run(main())
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
[2, 4]
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`finna bet` is `async def` — *fixin' to*: prepared, not yet running, which
|
|
288
|
+
is exactly what a coroutine object is. `done` is `await` — perfective: the
|
|
289
|
+
thing, completed. Note that `fut.done()` — the *method* — is untouched;
|
|
290
|
+
keywords are never rewritten after a dot.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Lesson 10 — Pattern matching, scope, and the escape hatch
|
|
295
|
+
|
|
296
|
+
**Keywords:** none
|
|
297
|
+
|
|
298
|
+
```hood
|
|
299
|
+
bet describe(cmd):
|
|
300
|
+
match cmd:
|
|
301
|
+
case ["go", direction]:
|
|
302
|
+
dip "going " + direction
|
|
303
|
+
case ["stop"]:
|
|
304
|
+
dip "stopping"
|
|
305
|
+
case _:
|
|
306
|
+
dip "unknown"
|
|
307
|
+
holla(describe(["go", "north"]), describe(["stop"]), describe([]))
|
|
308
|
+
total = 0
|
|
309
|
+
bet bump():
|
|
310
|
+
global total
|
|
311
|
+
total = total + 1
|
|
312
|
+
bump(); bump()
|
|
313
|
+
holla(total)
|
|
314
|
+
bet outer():
|
|
315
|
+
x = 1
|
|
316
|
+
bet inner():
|
|
317
|
+
nonlocal x
|
|
318
|
+
x = x + 1
|
|
319
|
+
inner()
|
|
320
|
+
dip x
|
|
321
|
+
holla(outer())
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
going north stopping unknown
|
|
326
|
+
2
|
|
327
|
+
2
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`match`/`case`, `global`, `nonlocal` are Python's. And here is the escape
|
|
331
|
+
hatch: **every Python keyword still works in HoodScript.** `def` and `return`
|
|
332
|
+
are accepted alongside `bet` and `dip`. You can paste any Python file into a
|
|
333
|
+
`.hs` file and it runs. The HoodScript words are preferred, not required.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Graduation
|
|
338
|
+
|
|
339
|
+
You now know every keyword in the language — fifteen words, eleven two-word
|
|
340
|
+
forms, plus the exception names. Run `hoodscript -c` on any of these files one last time and
|
|
341
|
+
read the Python: that is what you've been writing all along. From here, the
|
|
342
|
+
[Python documentation](https://docs.python.org/3/) is the HoodScript
|
|
343
|
+
documentation — substitute the words from [`keywords.md`](keywords.md).
|
hoodscript/debugger.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""The HoodScript debugger shim.
|
|
2
|
+
|
|
3
|
+
Integrates with debugpy (DAP) and Python debugging infrastructure.
|
|
4
|
+
Because HoodScript transpilation preserves exact line numbers, debugpy
|
|
5
|
+
and Python debuggers operate directly on .hs and .hood files once the
|
|
6
|
+
import hook is installed and the entry point is transpiled.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from hoodscript import importer, traceback_handler
|
|
13
|
+
from hoodscript.transpiler import transpile
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_debug(
|
|
17
|
+
filepath: str,
|
|
18
|
+
port: int = 5678,
|
|
19
|
+
host: str = "127.0.0.1",
|
|
20
|
+
wait_for_client: bool = False,
|
|
21
|
+
listen: bool = False,
|
|
22
|
+
) -> int:
|
|
23
|
+
"""Run a HoodScript program under debugpy or with debugging support."""
|
|
24
|
+
path = Path(filepath).resolve()
|
|
25
|
+
if not path.exists():
|
|
26
|
+
sys.stderr.write(f"File not found: {filepath}\n")
|
|
27
|
+
return 1
|
|
28
|
+
|
|
29
|
+
# Install import hook & traceback handler
|
|
30
|
+
importer.install()
|
|
31
|
+
traceback_handler.install()
|
|
32
|
+
|
|
33
|
+
# Put script directory on sys.path
|
|
34
|
+
file_dir = str(path.parent)
|
|
35
|
+
if file_dir not in sys.path:
|
|
36
|
+
sys.path.insert(0, file_dir)
|
|
37
|
+
|
|
38
|
+
# If debugpy listener is requested
|
|
39
|
+
if listen or wait_for_client:
|
|
40
|
+
try:
|
|
41
|
+
import debugpy
|
|
42
|
+
|
|
43
|
+
debugpy.listen((host, port))
|
|
44
|
+
sys.stderr.write(f"debugpy listening on {host}:{port}\n")
|
|
45
|
+
if wait_for_client:
|
|
46
|
+
sys.stderr.write("Waiting for debugger client to attach...\n")
|
|
47
|
+
debugpy.wait_for_client()
|
|
48
|
+
except ImportError:
|
|
49
|
+
sys.stderr.write("debugpy is not installed. Install with: pip install debugpy\n")
|
|
50
|
+
return 1
|
|
51
|
+
|
|
52
|
+
src = path.read_text(encoding="utf-8")
|
|
53
|
+
py_code = transpile(src, str(path))
|
|
54
|
+
code_obj = compile(py_code, str(path), "exec")
|
|
55
|
+
|
|
56
|
+
scope = {
|
|
57
|
+
"__file__": str(path),
|
|
58
|
+
"__name__": "__main__",
|
|
59
|
+
"__doc__": None,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
exec(code_obj, scope)
|
|
64
|
+
return 0
|
|
65
|
+
except SystemExit as e: # the program called exit(): honour it, don't report it
|
|
66
|
+
return int(e.code) if isinstance(e.code, int) else (0 if e.code is None else 1)
|
|
67
|
+
except KeyboardInterrupt:
|
|
68
|
+
return 130
|
|
69
|
+
except Exception as e:
|
|
70
|
+
traceback_handler.hood_excepthook(type(e), e, e.__traceback__)
|
|
71
|
+
return 1
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""The JPE (Just Plain English) error engine.
|
|
2
|
+
|
|
3
|
+
diagnose(exc_type, exc, tb) -> Diagnostic # errors.translator
|
|
4
|
+
render(diagnostic, stream) # errors.renderer
|
|
5
|
+
CATALOG # errors.catalog
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from hoodscript.errors.catalog import CATALOG, Entry, by_code, find
|
|
9
|
+
from hoodscript.errors.renderer import render, render_plain
|
|
10
|
+
from hoodscript.errors.translator import Diagnostic, diagnose
|
|
11
|
+
|
|
12
|
+
__all__ = ["CATALOG", "Entry", "Diagnostic", "by_code", "diagnose", "find", "render", "render_plain"]
|