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
hoodscript/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from hoodscript.importer import install, uninstall
|
|
2
|
+
from hoodscript.transpiler import transpile
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from hoodscript._version import __version__
|
|
6
|
+
except ImportError:
|
|
7
|
+
__version__ = "1.1.0"
|
|
8
|
+
|
|
9
|
+
__all__ = ["transpile", "install", "uninstall", "__version__"]
|
hoodscript/__main__.py
ADDED
hoodscript/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '1.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 1, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
hoodscript/cache.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import marshal
|
|
3
|
+
import os
|
|
4
|
+
import struct
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
FLAG_HASH_CHECKED = 0x03
|
|
9
|
+
HEADER_SIZE = 16
|
|
10
|
+
|
|
11
|
+
def get_cache_path(source_path):
|
|
12
|
+
p = Path(source_path).resolve()
|
|
13
|
+
tag = sys.implementation.cache_tag or f"cpython-{sys.version_info.major}{sys.version_info.minor}"
|
|
14
|
+
return p.parent / "__pycache__" / f"{p.stem}.{tag}.pyc"
|
|
15
|
+
|
|
16
|
+
def read_cache(source_path, source_bytes):
|
|
17
|
+
f = get_cache_path(source_path)
|
|
18
|
+
if not f.is_file(): return None
|
|
19
|
+
try:
|
|
20
|
+
d = f.read_bytes()
|
|
21
|
+
if len(d) < HEADER_SIZE or d[0:4] != importlib.util.MAGIC_NUMBER: return None
|
|
22
|
+
if d[8:16] != importlib.util.source_hash(source_bytes): return None
|
|
23
|
+
return marshal.loads(d[HEADER_SIZE:])
|
|
24
|
+
except Exception: return None
|
|
25
|
+
|
|
26
|
+
def write_cache(source_path, source_bytes, code_obj):
|
|
27
|
+
f = get_cache_path(source_path)
|
|
28
|
+
try:
|
|
29
|
+
f.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
hdr = (importlib.util.MAGIC_NUMBER + struct.pack("<I", FLAG_HASH_CHECKED)
|
|
31
|
+
+ importlib.util.source_hash(source_bytes))
|
|
32
|
+
tmp = f.parent / f"{f.name}.tmp.{os.getpid()}"
|
|
33
|
+
tmp.write_bytes(hdr + marshal.dumps(code_obj))
|
|
34
|
+
tmp.replace(f)
|
|
35
|
+
return True
|
|
36
|
+
except Exception: return False
|
hoodscript/cli.py
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from hoodscript import __version__, importer, sandbox, traceback_handler
|
|
7
|
+
from hoodscript.constants import (
|
|
8
|
+
HOOD_EXCEPTION_NAMES,
|
|
9
|
+
TIER_A,
|
|
10
|
+
TIER_B,
|
|
11
|
+
TIER_C,
|
|
12
|
+
)
|
|
13
|
+
from hoodscript.transpiler import transpile
|
|
14
|
+
|
|
15
|
+
TOUR_TEXT = f"""HoodScript v{__version__} — AAVE keywords mapped 1:1 to Python 3
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
hoodscript <file.hs> Run a HoodScript program (or `hoodscript run <file.hs>`)
|
|
19
|
+
hoodscript repl [--mirror] Start interactive REPL (--mirror echoes Python)
|
|
20
|
+
hoodscript build <file.hs> Transpile HoodScript to Python (-c)
|
|
21
|
+
hoodscript check <file.hs> Check syntax without executing
|
|
22
|
+
hoodscript fmt <file.hs> Format source files
|
|
23
|
+
hoodscript lint <file.hs> Lint source files
|
|
24
|
+
hoodscript learn [topic] Interactive tutor (try: hoodscript learn basics)
|
|
25
|
+
hoodscript debug <file.hs> Debug program under debugpy / DAP
|
|
26
|
+
hoodscript tokens Show all keywords and mappings
|
|
27
|
+
hoodscript playground [--port] Launch Web Playground in browser
|
|
28
|
+
hoodscript lsp Start Language Server
|
|
29
|
+
hoodscript version Show version information
|
|
30
|
+
hoodscript completion [bash|zsh] Generate shell autocompletion
|
|
31
|
+
|
|
32
|
+
Topics for learn:
|
|
33
|
+
basics, flow, functions, classes, errors, async, mirror, all
|
|
34
|
+
|
|
35
|
+
Quick start:
|
|
36
|
+
$ hoodscript learn basics # Start the interactive curriculum
|
|
37
|
+
$ hoodscript repl --mirror # REPL echoing Python code line-by-line
|
|
38
|
+
$ hoodscript build app.hs # See transpiled Python
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _utf8_pipes() -> None:
|
|
43
|
+
"""Crash reports carry non-ASCII (the header, em dashes). A real console handles
|
|
44
|
+
them; a pipe on Windows defaults to the legacy code page and mangles them."""
|
|
45
|
+
for stream in (sys.stdout, sys.stderr):
|
|
46
|
+
if not stream.isatty() and hasattr(stream, "reconfigure"):
|
|
47
|
+
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def print_tokens() -> None:
|
|
51
|
+
print(f"HoodScript v{__version__} Keyword and Exception Reference\n")
|
|
52
|
+
print(f"{'HoodScript':<18} {'Python':<18} {'Category':<15} {'Description'}")
|
|
53
|
+
print(f"{'-' * 18} {'-' * 18} {'-' * 15} {'-' * 30}")
|
|
54
|
+
for k, v in sorted(TIER_A.items()):
|
|
55
|
+
print(f"{k:<18} {v:<18} {'Tier A':<15} Core grammatical feature")
|
|
56
|
+
for k, v in sorted(TIER_B.items()):
|
|
57
|
+
print(f"{k:<18} {v:<18} {'Tier B':<15} High-frequency keyword")
|
|
58
|
+
for k, v in sorted(TIER_C.items()):
|
|
59
|
+
print(f"{k:<18} {v:<18} {'Tier C':<15} Control / statement")
|
|
60
|
+
for k, v in sorted(HOOD_EXCEPTION_NAMES.items()):
|
|
61
|
+
print(f"{k:<18} {v:<18} {'Exception':<15} Builtin exception")
|
|
62
|
+
|
|
63
|
+
print("\nMulti-token Patterns (normative R1–R12):")
|
|
64
|
+
patterns = [
|
|
65
|
+
("ain't x", "not x", "R1 Negation"),
|
|
66
|
+
("ain't nobody x", "not x", "R2 Emphatic negative concord"),
|
|
67
|
+
("no cap", "True", "R3 Truth assertion"),
|
|
68
|
+
("cap", "False", "R3 Falsehood"),
|
|
69
|
+
("it's x", "(x is not None)", "R4 Existence test"),
|
|
70
|
+
("steady:", "while True:", "R5 Persistent loop"),
|
|
71
|
+
("a..b", "range(a, (b) + 1)", "R6 Inclusive range"),
|
|
72
|
+
("BIN X = v", "X: Final = v", "R7 Constant declaration"),
|
|
73
|
+
("else if", "elif", "R8 Chained conditional"),
|
|
74
|
+
("holla x, y", "print(x, y)", "R9 Statement-form print"),
|
|
75
|
+
("ask x", "input(x)", "R10 Statement-form input"),
|
|
76
|
+
]
|
|
77
|
+
for src, py, desc in patterns:
|
|
78
|
+
print(f" {src:<20} -> {py:<25} ({desc})")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def print_completion(shell: str) -> int:
|
|
82
|
+
shell = shell.lower().strip()
|
|
83
|
+
if shell == "bash":
|
|
84
|
+
print("""# bash completion for hoodscript
|
|
85
|
+
_hoodscript_completion() {
|
|
86
|
+
local cur prev words cword
|
|
87
|
+
_init_completion || return
|
|
88
|
+
|
|
89
|
+
local commands="run build check fmt lint repl tokens learn lsp version completion make-stubs debug playground"
|
|
90
|
+
local topics="basics flow functions classes errors async mirror all"
|
|
91
|
+
|
|
92
|
+
if [[ $cword -eq 1 ]]; then
|
|
93
|
+
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
94
|
+
return 0
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
case "${words[1]}" in
|
|
98
|
+
learn)
|
|
99
|
+
COMPREPLY=( $(compgen -W "$topics" -- "$cur") )
|
|
100
|
+
return 0
|
|
101
|
+
;;
|
|
102
|
+
completion)
|
|
103
|
+
COMPREPLY=( $(compgen -W "bash zsh" -- "$cur") )
|
|
104
|
+
return 0
|
|
105
|
+
;;
|
|
106
|
+
run|build|check|fmt|lint|debug)
|
|
107
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
108
|
+
return 0
|
|
109
|
+
;;
|
|
110
|
+
repl)
|
|
111
|
+
COMPREPLY=( $(compgen -W "--mirror" -- "$cur") )
|
|
112
|
+
return 0
|
|
113
|
+
;;
|
|
114
|
+
esac
|
|
115
|
+
}
|
|
116
|
+
complete -F _hoodscript_completion hoodscript
|
|
117
|
+
""")
|
|
118
|
+
return 0
|
|
119
|
+
elif shell == "zsh":
|
|
120
|
+
print("""#compdef hoodscript
|
|
121
|
+
|
|
122
|
+
_hoodscript() {
|
|
123
|
+
local -a commands
|
|
124
|
+
commands=(
|
|
125
|
+
'run:Run a HoodScript program'
|
|
126
|
+
'build:Transpile HoodScript to Python stdout'
|
|
127
|
+
'check:Check syntax without executing'
|
|
128
|
+
'fmt:Format HoodScript source files'
|
|
129
|
+
'lint:Lint HoodScript source files'
|
|
130
|
+
'repl:Start interactive REPL'
|
|
131
|
+
'tokens:Display language keywords and mappings'
|
|
132
|
+
'learn:Interactive curriculum lessons'
|
|
133
|
+
'debug:Debug program under debugpy / DAP'
|
|
134
|
+
'lsp:Start Language Server'
|
|
135
|
+
'version:Show version'
|
|
136
|
+
'completion:Generate shell completion script'
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
_arguments -C \\
|
|
140
|
+
'1: :->command' \\
|
|
141
|
+
'*:: :->args'
|
|
142
|
+
|
|
143
|
+
case $state in
|
|
144
|
+
command)
|
|
145
|
+
_describe -t commands 'hoodscript command' commands
|
|
146
|
+
;;
|
|
147
|
+
args)
|
|
148
|
+
case $line[1] in
|
|
149
|
+
learn)
|
|
150
|
+
_values 'topics' basics flow functions classes errors async mirror all
|
|
151
|
+
;;
|
|
152
|
+
completion)
|
|
153
|
+
_values 'shell' bash zsh
|
|
154
|
+
;;
|
|
155
|
+
run|build|check|fmt|lint)
|
|
156
|
+
_files
|
|
157
|
+
;;
|
|
158
|
+
repl)
|
|
159
|
+
_arguments '--mirror[Echo Python equivalent under each line]'
|
|
160
|
+
;;
|
|
161
|
+
esac
|
|
162
|
+
;;
|
|
163
|
+
esac
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_hoodscript "$@"
|
|
167
|
+
""")
|
|
168
|
+
return 0
|
|
169
|
+
else:
|
|
170
|
+
sys.stderr.write(f"Unknown shell '{shell}'. Supported: bash, zsh\n")
|
|
171
|
+
return 1
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def check_command(args: list[str]) -> int:
|
|
175
|
+
parser = argparse.ArgumentParser(prog="hoodscript check")
|
|
176
|
+
parser.add_argument("file", help="HoodScript file to check")
|
|
177
|
+
pargs = parser.parse_args(args)
|
|
178
|
+
|
|
179
|
+
traceback_handler.install()
|
|
180
|
+
try:
|
|
181
|
+
with open(pargs.file, encoding="utf-8") as fh:
|
|
182
|
+
src = fh.read()
|
|
183
|
+
py = transpile(src, pargs.file)
|
|
184
|
+
compile(py, pargs.file, "exec")
|
|
185
|
+
print(f"Syntax OK: {pargs.file}")
|
|
186
|
+
return 0
|
|
187
|
+
except BaseException as e:
|
|
188
|
+
traceback_handler.hood_excepthook(type(e), e, e.__traceback__)
|
|
189
|
+
return 1
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def fmt_command(args: list[str]) -> int:
|
|
193
|
+
parser = argparse.ArgumentParser(prog="hoodscript fmt")
|
|
194
|
+
parser.add_argument("files", nargs="+", help="Files or directories to format")
|
|
195
|
+
parser.add_argument("--check", action="store_true", help="Check if files are formatted without writing")
|
|
196
|
+
parser.add_argument("--diff", action="store_true", help="Show diff for changes")
|
|
197
|
+
pargs = parser.parse_args(args)
|
|
198
|
+
|
|
199
|
+
from hoodscript.formatter import format_file
|
|
200
|
+
from hoodscript.linter import ToolMissing
|
|
201
|
+
|
|
202
|
+
any_modified = False
|
|
203
|
+
for path_str in pargs.files:
|
|
204
|
+
p = Path(path_str)
|
|
205
|
+
targets = list(p.rglob("*.hs")) + list(p.rglob("*.hood")) if p.is_dir() else [p]
|
|
206
|
+
|
|
207
|
+
for target in targets:
|
|
208
|
+
try:
|
|
209
|
+
mod, msg = format_file(target, check_only=pargs.check, show_diff=pargs.diff, write=not pargs.check)
|
|
210
|
+
except ToolMissing as e:
|
|
211
|
+
sys.stderr.write(f"{e}\n"); return 2
|
|
212
|
+
if mod:
|
|
213
|
+
any_modified = True
|
|
214
|
+
if msg:
|
|
215
|
+
print(msg)
|
|
216
|
+
|
|
217
|
+
if pargs.check and any_modified:
|
|
218
|
+
return 1
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def lint_command(args: list[str]) -> int:
|
|
223
|
+
parser = argparse.ArgumentParser(prog="hoodscript lint")
|
|
224
|
+
parser.add_argument("files", nargs="+", help="Files or directories to lint")
|
|
225
|
+
parser.add_argument("--types", action="store_true", help="Run mypy type checker (HL003)")
|
|
226
|
+
pargs = parser.parse_args(args)
|
|
227
|
+
|
|
228
|
+
from hoodscript.linter import ToolMissing, lint_file
|
|
229
|
+
|
|
230
|
+
all_findings = []
|
|
231
|
+
for path_str in pargs.files:
|
|
232
|
+
p = Path(path_str)
|
|
233
|
+
targets = list(p.rglob("*.hs")) + list(p.rglob("*.hood")) if p.is_dir() else [p]
|
|
234
|
+
|
|
235
|
+
for target in targets:
|
|
236
|
+
try:
|
|
237
|
+
findings = lint_file(target, check_types=pargs.types)
|
|
238
|
+
except ToolMissing as e:
|
|
239
|
+
sys.stderr.write(f"{e}\n"); return 2
|
|
240
|
+
all_findings.extend(findings)
|
|
241
|
+
|
|
242
|
+
for f in all_findings:
|
|
243
|
+
print(f.format_plain())
|
|
244
|
+
|
|
245
|
+
if all_findings:
|
|
246
|
+
print(f"\n{len(all_findings)} issue(s) found.")
|
|
247
|
+
return 1
|
|
248
|
+
|
|
249
|
+
print("No issues found.")
|
|
250
|
+
return 0
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def repl_command(args: list[str]) -> int:
|
|
254
|
+
parser = argparse.ArgumentParser(prog="hoodscript repl")
|
|
255
|
+
parser.add_argument("--mirror", action="store_true", help="Echo Python equivalent under each input line")
|
|
256
|
+
pargs = parser.parse_args(args)
|
|
257
|
+
|
|
258
|
+
try:
|
|
259
|
+
from hoodscript.repl import HoodScriptREPL
|
|
260
|
+
except ImportError:
|
|
261
|
+
sys.stderr.write(
|
|
262
|
+
"The interactive REPL requires prompt-toolkit.\n"
|
|
263
|
+
"Install with: pip install 'hoodscript[repl]'\n"
|
|
264
|
+
)
|
|
265
|
+
return 1
|
|
266
|
+
|
|
267
|
+
HoodScriptREPL(mirror=pargs.mirror).start()
|
|
268
|
+
return 0
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def learn_command(args: list[str]) -> int:
|
|
272
|
+
parser = argparse.ArgumentParser(prog="hoodscript learn")
|
|
273
|
+
parser.add_argument(
|
|
274
|
+
"topic",
|
|
275
|
+
nargs="?",
|
|
276
|
+
default=None,
|
|
277
|
+
help="Topic (basics, flow, functions, classes, errors, async, mirror) or lesson #",
|
|
278
|
+
)
|
|
279
|
+
parser.add_argument("--mirror", action="store_true", help="Show transpiled Python for each lesson")
|
|
280
|
+
parser.add_argument("--auto", action="store_true", help="Run lessons automatically without prompting")
|
|
281
|
+
pargs = parser.parse_args(args)
|
|
282
|
+
|
|
283
|
+
from hoodscript.tutor import run_curriculum
|
|
284
|
+
|
|
285
|
+
return run_curriculum(pargs.topic, mirror=pargs.mirror, auto_run=pargs.auto)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def debug_command(args: list[str]) -> int:
|
|
289
|
+
parser = argparse.ArgumentParser(prog="hoodscript debug")
|
|
290
|
+
parser.add_argument("file", help="HoodScript file to debug")
|
|
291
|
+
parser.add_argument("--port", type=int, default=5678, help="debugpy port (default 5678)")
|
|
292
|
+
parser.add_argument("--host", default="127.0.0.1", help="debugpy host (default 127.0.0.1)")
|
|
293
|
+
parser.add_argument("--wait", action="store_true", help="Wait for debugger client to attach")
|
|
294
|
+
parser.add_argument("--listen", action="store_true", help="Start debugpy listener")
|
|
295
|
+
pargs = parser.parse_args(args)
|
|
296
|
+
|
|
297
|
+
from hoodscript.debugger import run_debug
|
|
298
|
+
|
|
299
|
+
return run_debug(
|
|
300
|
+
pargs.file,
|
|
301
|
+
port=pargs.port,
|
|
302
|
+
host=pargs.host,
|
|
303
|
+
wait_for_client=pargs.wait,
|
|
304
|
+
listen=pargs.listen or pargs.wait,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def playground_command(args: list[str]) -> int:
|
|
309
|
+
import http.server
|
|
310
|
+
import socketserver
|
|
311
|
+
import webbrowser
|
|
312
|
+
from functools import partial
|
|
313
|
+
|
|
314
|
+
parser = argparse.ArgumentParser(prog="hoodscript playground", description="Launch the client-side Web Playground")
|
|
315
|
+
parser.add_argument("--port", type=int, default=8000, help="port to serve on (default 8000)")
|
|
316
|
+
parser.add_argument("--no-browser", action="store_true", help="do not open browser automatically")
|
|
317
|
+
pargs = parser.parse_args(args)
|
|
318
|
+
|
|
319
|
+
playground_dir = Path(__file__).resolve().parents[2] / "playground"
|
|
320
|
+
if not playground_dir.exists():
|
|
321
|
+
sys.stderr.write("The playground is a static site that lives in the repository, not in the installed package.\n"
|
|
322
|
+
"Clone https://github.com/khaoticdev62/hoodscript and run `hoodscript playground` "
|
|
323
|
+
"from the checkout.\n")
|
|
324
|
+
return 1
|
|
325
|
+
|
|
326
|
+
handler = partial(http.server.SimpleHTTPRequestHandler, directory=str(playground_dir))
|
|
327
|
+
port = pargs.port
|
|
328
|
+
url = f"http://localhost:{port}"
|
|
329
|
+
|
|
330
|
+
sys.stdout.write(f"Starting HoodScript Web Playground at {url}\n")
|
|
331
|
+
sys.stdout.write("Press Ctrl+C to stop.\n")
|
|
332
|
+
sys.stdout.flush()
|
|
333
|
+
|
|
334
|
+
if not pargs.no_browser:
|
|
335
|
+
import contextlib
|
|
336
|
+
|
|
337
|
+
with contextlib.suppress(Exception):
|
|
338
|
+
webbrowser.open(url)
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
with socketserver.TCPServer(("", port), handler) as httpd:
|
|
342
|
+
httpd.serve_forever()
|
|
343
|
+
except KeyboardInterrupt:
|
|
344
|
+
sys.stdout.write("\nPlayground stopped.\n")
|
|
345
|
+
return 0
|
|
346
|
+
except Exception as exc:
|
|
347
|
+
sys.stderr.write(f"Playground server error: {exc}\n")
|
|
348
|
+
return 1
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def main(argv=None) -> int:
|
|
352
|
+
if argv is None:
|
|
353
|
+
argv = sys.argv[1:]
|
|
354
|
+
_utf8_pipes()
|
|
355
|
+
|
|
356
|
+
# If no arguments are passed, show the tour (Sprint 7 item 1)
|
|
357
|
+
if not argv:
|
|
358
|
+
sys.stdout.write(TOUR_TEXT)
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
cmd = argv[0]
|
|
362
|
+
|
|
363
|
+
if cmd in ("version", "--version", "-v"):
|
|
364
|
+
print(f"HoodScript v{__version__}")
|
|
365
|
+
return 0
|
|
366
|
+
if cmd == "tokens":
|
|
367
|
+
print_tokens()
|
|
368
|
+
return 0
|
|
369
|
+
if cmd == "completion":
|
|
370
|
+
shell = argv[1] if len(argv) > 1 else "bash"
|
|
371
|
+
return print_completion(shell)
|
|
372
|
+
if cmd == "lsp":
|
|
373
|
+
try:
|
|
374
|
+
from hoodscript.lsp import run_lsp
|
|
375
|
+
except ImportError:
|
|
376
|
+
sys.stderr.write(
|
|
377
|
+
"The LSP server requires pygls and jedi.\n"
|
|
378
|
+
"Install with: pip install 'hoodscript[lsp]'\n"
|
|
379
|
+
)
|
|
380
|
+
return 1
|
|
381
|
+
run_lsp()
|
|
382
|
+
return 0
|
|
383
|
+
if cmd == "make-stubs":
|
|
384
|
+
from hoodscript.stubs import make_stubs_main
|
|
385
|
+
return make_stubs_main(argv[1:])
|
|
386
|
+
if cmd == "check":
|
|
387
|
+
return check_command(argv[1:])
|
|
388
|
+
if cmd == "fmt":
|
|
389
|
+
return fmt_command(argv[1:])
|
|
390
|
+
if cmd == "lint":
|
|
391
|
+
return lint_command(argv[1:])
|
|
392
|
+
if cmd == "repl":
|
|
393
|
+
return repl_command(argv[1:])
|
|
394
|
+
if cmd == "learn":
|
|
395
|
+
return learn_command(argv[1:])
|
|
396
|
+
if cmd == "debug":
|
|
397
|
+
return debug_command(argv[1:])
|
|
398
|
+
if cmd == "playground":
|
|
399
|
+
return playground_command(argv[1:])
|
|
400
|
+
if cmd == "build":
|
|
401
|
+
# build <file> transpiles to stdout (alias for -c)
|
|
402
|
+
if len(argv) < 2:
|
|
403
|
+
sys.stderr.write("Usage: hoodscript build <file.hs>\n")
|
|
404
|
+
return 1
|
|
405
|
+
filepath = argv[1]
|
|
406
|
+
with open(filepath, encoding="utf-8") as fh:
|
|
407
|
+
src = fh.read()
|
|
408
|
+
sys.stdout.write(transpile(src, filepath) + "\n")
|
|
409
|
+
return 0
|
|
410
|
+
|
|
411
|
+
# If user ran `hoodscript run ...`
|
|
412
|
+
if cmd == "run":
|
|
413
|
+
argv = argv[1:]
|
|
414
|
+
|
|
415
|
+
# Fallback to standard execution parser (supports `hoodscript <file>`, `-c`, `--sandbox`, etc.)
|
|
416
|
+
parser = argparse.ArgumentParser(prog="hoodscript")
|
|
417
|
+
parser.add_argument("file", nargs="?")
|
|
418
|
+
parser.add_argument("-c", "--compile", action="store_true")
|
|
419
|
+
parser.add_argument(
|
|
420
|
+
"--python-traceback",
|
|
421
|
+
action="store_true",
|
|
422
|
+
help="show raw Python tracebacks (for debugging the compiler)",
|
|
423
|
+
)
|
|
424
|
+
sb = parser.add_argument_group(
|
|
425
|
+
"sandbox",
|
|
426
|
+
"run with capabilities closed and budgets on (see SECURITY.md)",
|
|
427
|
+
)
|
|
428
|
+
sb.add_argument("--sandbox", action="store_true", help="enable the sandbox (also HOODSCRIPT_SANDBOX=1)")
|
|
429
|
+
sb.add_argument("--allow-fs", action="store_true", help="grant filesystem access inside the sandbox")
|
|
430
|
+
sb.add_argument("--allow-net", action="store_true", help="grant network access inside the sandbox")
|
|
431
|
+
sb.add_argument("--allow-clock", action="store_true", help="grant clock access inside the sandbox")
|
|
432
|
+
sb.add_argument("--max-seconds", type=float, default=None, help="wall-clock budget (default 10)")
|
|
433
|
+
sb.add_argument("--max-output", type=int, default=None, help="output budget in bytes (default 1,000,000)")
|
|
434
|
+
sb.add_argument("--max-depth", type=int, default=None, help="recursion budget (default 256)")
|
|
435
|
+
args = parser.parse_args(argv)
|
|
436
|
+
|
|
437
|
+
if args.python_traceback:
|
|
438
|
+
os.environ[traceback_handler.ENV_FLAG] = "1"
|
|
439
|
+
if not args.file:
|
|
440
|
+
sys.stdout.write(TOUR_TEXT)
|
|
441
|
+
return 0
|
|
442
|
+
|
|
443
|
+
with open(args.file, encoding="utf-8") as fh:
|
|
444
|
+
src = fh.read()
|
|
445
|
+
if args.compile:
|
|
446
|
+
sys.stdout.write(transpile(src, args.file) + "\n")
|
|
447
|
+
return 0
|
|
448
|
+
|
|
449
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(args.file)))
|
|
450
|
+
importer.install()
|
|
451
|
+
traceback_handler.install()
|
|
452
|
+
if args.sandbox or os.environ.get(sandbox.ENV_FLAG) or sandbox.SANDBOX_BY_DEFAULT:
|
|
453
|
+
caps = sandbox.Capabilities(filesystem=args.allow_fs, network=args.allow_net, clock=args.allow_clock)
|
|
454
|
+
defaults = sandbox.Budgets()
|
|
455
|
+
budgets = sandbox.Budgets(
|
|
456
|
+
max_recursion_depth=args.max_depth or defaults.max_recursion_depth,
|
|
457
|
+
max_wall_seconds=args.max_seconds or defaults.max_wall_seconds,
|
|
458
|
+
max_output_bytes=args.max_output or defaults.max_output_bytes,
|
|
459
|
+
)
|
|
460
|
+
sandbox.activate(sandbox.Sandbox(caps, budgets))
|
|
461
|
+
|
|
462
|
+
scope = {"__file__": os.path.abspath(args.file), "__name__": "__main__"}
|
|
463
|
+
exec(compile(transpile(src, args.file), args.file, "exec"), scope)
|
|
464
|
+
return 0
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
if __name__ == "__main__":
|
|
468
|
+
sys.exit(main())
|
hoodscript/constants.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Keyword tables — the runtime truth. Specified by GRAMMAR.md v1.1; every entry
|
|
2
|
+
has a row in hoodscript/docs/linguistics.md. Do not add a word here without
|
|
3
|
+
the 7-step procedure in hoodscript/docs/lexical-sourcing.md §5.
|
|
4
|
+
|
|
5
|
+
Tier A: grammar (Yale GDP page each). Tier B: lexical (two dictionaries each;
|
|
6
|
+
`trip` on one, owner-approved). Tier C: plain English kept deliberately.
|
|
7
|
+
Multi-token forms (steady:, no cap, it's, ain't, BIN, a..b, else if, holla
|
|
8
|
+
"x") are the Sprint 3 pattern layer and are not in this table yet.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import builtins as _b
|
|
12
|
+
|
|
13
|
+
TIER_A = {
|
|
14
|
+
"be": "for", # habitual be -> ast.For
|
|
15
|
+
"finna": "async", # fixin' to -> ast.AsyncFunctionDef (with bet)
|
|
16
|
+
"done": "await", # perfective done -> ast.Await
|
|
17
|
+
"tryna": "try", # tryna -> ast.Try
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
TIER_B = {
|
|
21
|
+
"bet": "def", # commitment -> ast.FunctionDef
|
|
22
|
+
"fam": "class", # close group -> ast.ClassDef
|
|
23
|
+
"holla": "print", # call out -> ast.Call
|
|
24
|
+
"dip": "return", # leave -> ast.Return
|
|
25
|
+
"chill": "pass", # do nothing -> ast.Pass
|
|
26
|
+
"cap": "False", # a lie -> ast.Constant
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
TIER_C = {
|
|
30
|
+
"skip": "continue",
|
|
31
|
+
"catch": "except",
|
|
32
|
+
"regardless": "finally",
|
|
33
|
+
"throw": "raise",
|
|
34
|
+
"ask": "input",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# The *Trip family: Python exceptions under HoodScript names. Token-level, so
|
|
38
|
+
# generated Python reads `except ValueError` and py2hood gives `catch BadValueTrip`.
|
|
39
|
+
HOOD_EXCEPTION_NAMES = {
|
|
40
|
+
"Problem": "Exception",
|
|
41
|
+
"BaseProblem": "BaseException",
|
|
42
|
+
"BadValueTrip": "ValueError",
|
|
43
|
+
"WrongKindTrip": "TypeError",
|
|
44
|
+
"MissingKeyTrip": "KeyError",
|
|
45
|
+
"OutOfBoundsTrip": "IndexError",
|
|
46
|
+
"ZeroSplitTrip": "ZeroDivisionError",
|
|
47
|
+
"MissingFileTrip": "FileNotFoundError",
|
|
48
|
+
"AssertTrip": "AssertionError",
|
|
49
|
+
"UnknownNameTrip": "NameError",
|
|
50
|
+
"MissingAttrTrip": "AttributeError",
|
|
51
|
+
"BadSyntaxTrip": "SyntaxError",
|
|
52
|
+
"InfiniteLoopTrip": "RecursionError",
|
|
53
|
+
"StopRunTrip": "StopIteration",
|
|
54
|
+
"MissingModuleTrip": "ModuleNotFoundError",
|
|
55
|
+
"BadImportTrip": "ImportError",
|
|
56
|
+
"NoAccessTrip": "PermissionError",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
HOOD_KEYWORD_MAP = {**TIER_A, **TIER_B, **TIER_C, **HOOD_EXCEPTION_NAMES}
|
|
60
|
+
|
|
61
|
+
# Python exception class -> HoodScript name, for crash reports.
|
|
62
|
+
HOOD_EXCEPTIONS = {getattr(_b, py): hood for hood, py in HOOD_EXCEPTION_NAMES.items()}
|
|
63
|
+
|
|
64
|
+
# Reserved: a HoodScript program cannot use these as variable names, exactly as
|
|
65
|
+
# Python cannot use `pass` or `return`. Attribute names (`future.done()`) are
|
|
66
|
+
# exempt — the transpiler never rewrites a NAME that follows a dot.
|
|
67
|
+
RESERVED = frozenset(HOOD_KEYWORD_MAP)
|
|
68
|
+
|
|
69
|
+
SUPPORTED_EXTENSIONS = (".hs", ".hood")
|