techscript 1.0.3__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.
- techscript/__init__.py +2 -0
- techscript/__main__.py +5 -0
- techscript/ast_nodes.py +239 -0
- techscript/builtins.py +298 -0
- techscript/cli.py +190 -0
- techscript/environment.py +75 -0
- techscript/errors.py +153 -0
- techscript/interpreter.py +674 -0
- techscript/lexer.py +336 -0
- techscript/parser.py +637 -0
- techscript/repl.py +86 -0
- techscript/tokens.py +132 -0
- techscript/transpiler.py +290 -0
- techscript/web.py +143 -0
- techscript-1.0.3.dist-info/METADATA +510 -0
- techscript-1.0.3.dist-info/RECORD +20 -0
- techscript-1.0.3.dist-info/WHEEL +5 -0
- techscript-1.0.3.dist-info/entry_points.txt +2 -0
- techscript-1.0.3.dist-info/licenses/LICENSE +21 -0
- techscript-1.0.3.dist-info/top_level.txt +1 -0
techscript/__init__.py
ADDED
techscript/__main__.py
ADDED
techscript/ast_nodes.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""TechScript Abstract Syntax Tree node definitions.
|
|
2
|
+
|
|
3
|
+
Every node is a frozen-ish ``@dataclass``. Statements and expressions are
|
|
4
|
+
plain classes — no common base is needed because we pattern-match on type.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ===================================================================
|
|
14
|
+
# Programme root
|
|
15
|
+
# ===================================================================
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Program:
|
|
19
|
+
body: list[Any]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Param:
|
|
24
|
+
name: str
|
|
25
|
+
default: Any = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ===================================================================
|
|
29
|
+
# Statements
|
|
30
|
+
# ===================================================================
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class SayStmt:
|
|
34
|
+
"""``say <expr>, …``"""
|
|
35
|
+
values: list[Any]
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class SetStmt:
|
|
39
|
+
"""``set <name> = <expr>``"""
|
|
40
|
+
name: str
|
|
41
|
+
value: Any
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class AssignStmt:
|
|
45
|
+
"""``<target> = | += | -= | *= | /= <expr>``"""
|
|
46
|
+
target: Any
|
|
47
|
+
op: str
|
|
48
|
+
value: Any
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class ExpressionStmt:
|
|
52
|
+
"""A bare expression used as a statement (e.g. function call)."""
|
|
53
|
+
expression: Any
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class IfStmt:
|
|
57
|
+
condition: Any
|
|
58
|
+
body: list[Any]
|
|
59
|
+
elif_clauses: list[tuple[Any, list[Any]]] = field(default_factory=list)
|
|
60
|
+
else_body: list[Any] | None = None
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class ForStmt:
|
|
64
|
+
var_name: str
|
|
65
|
+
iterable: Any
|
|
66
|
+
body: list[Any]
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class WhileStmt:
|
|
70
|
+
condition: Any
|
|
71
|
+
body: list[Any]
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class FnStmt:
|
|
75
|
+
name: str
|
|
76
|
+
params: list[Param]
|
|
77
|
+
body: list[Any]
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ClassStmt:
|
|
81
|
+
name: str
|
|
82
|
+
parent: str | None = None
|
|
83
|
+
body: list[Any] = field(default_factory=list)
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class ReturnStmt:
|
|
87
|
+
value: Any = None
|
|
88
|
+
|
|
89
|
+
@dataclass
|
|
90
|
+
class BreakStmt:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class SkipStmt:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class PassStmt:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
@dataclass
|
|
102
|
+
class TryStmt:
|
|
103
|
+
body: list[Any]
|
|
104
|
+
catch_var: str | None = None
|
|
105
|
+
catch_body: list[Any] = field(default_factory=list)
|
|
106
|
+
finally_body: list[Any] | None = None
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class ThrowStmt:
|
|
110
|
+
value: Any
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class MatchStmt:
|
|
114
|
+
subject: Any
|
|
115
|
+
cases: list[tuple[Any, list[Any]]] = field(default_factory=list)
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class ImportStmt:
|
|
119
|
+
module: str
|
|
120
|
+
names: list[str] | None = None
|
|
121
|
+
alias: str | None = None
|
|
122
|
+
|
|
123
|
+
@dataclass
|
|
124
|
+
class FromImportStmt:
|
|
125
|
+
module: str
|
|
126
|
+
names: list[str]
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class DelStmt:
|
|
130
|
+
name: str
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class DeferStmt:
|
|
134
|
+
expression: Any
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class GuardStmt:
|
|
138
|
+
condition: Any
|
|
139
|
+
else_body: list[Any]
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class WithStmt:
|
|
143
|
+
expression: Any
|
|
144
|
+
var_name: str
|
|
145
|
+
body: list[Any]
|
|
146
|
+
|
|
147
|
+
@dataclass
|
|
148
|
+
class ConstStmt:
|
|
149
|
+
name: str
|
|
150
|
+
value: Any
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class ExportStmt:
|
|
154
|
+
declaration: Any
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ===================================================================
|
|
158
|
+
# Expressions
|
|
159
|
+
# ===================================================================
|
|
160
|
+
|
|
161
|
+
@dataclass
|
|
162
|
+
class NumberLit:
|
|
163
|
+
value: int | float
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class StringLit:
|
|
167
|
+
value: str
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class FStringLit:
|
|
171
|
+
"""Stored as raw template string; the interpreter handles ``{…}`` parts."""
|
|
172
|
+
raw: str
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class BoolLit:
|
|
176
|
+
value: bool
|
|
177
|
+
|
|
178
|
+
@dataclass
|
|
179
|
+
class NoneLit:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
@dataclass
|
|
183
|
+
class ListLit:
|
|
184
|
+
elements: list[Any]
|
|
185
|
+
|
|
186
|
+
@dataclass
|
|
187
|
+
class MapLit:
|
|
188
|
+
entries: list[tuple[Any, Any]]
|
|
189
|
+
|
|
190
|
+
@dataclass
|
|
191
|
+
class Identifier:
|
|
192
|
+
name: str
|
|
193
|
+
|
|
194
|
+
@dataclass
|
|
195
|
+
class BinaryOp:
|
|
196
|
+
left: Any
|
|
197
|
+
op: str
|
|
198
|
+
right: Any
|
|
199
|
+
|
|
200
|
+
@dataclass
|
|
201
|
+
class UnaryOp:
|
|
202
|
+
op: str
|
|
203
|
+
operand: Any
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class CallExpr:
|
|
207
|
+
callee: Any
|
|
208
|
+
args: list[Any]
|
|
209
|
+
|
|
210
|
+
@dataclass
|
|
211
|
+
class IndexExpr:
|
|
212
|
+
obj: Any
|
|
213
|
+
index: Any
|
|
214
|
+
|
|
215
|
+
@dataclass
|
|
216
|
+
class MemberExpr:
|
|
217
|
+
obj: Any
|
|
218
|
+
member: str
|
|
219
|
+
|
|
220
|
+
@dataclass
|
|
221
|
+
class LambdaExpr:
|
|
222
|
+
params: list[Param]
|
|
223
|
+
body: Any # single expression
|
|
224
|
+
|
|
225
|
+
@dataclass
|
|
226
|
+
class AskExpr:
|
|
227
|
+
prompt: Any
|
|
228
|
+
|
|
229
|
+
@dataclass
|
|
230
|
+
class TernaryExpr:
|
|
231
|
+
true_val: Any
|
|
232
|
+
condition: Any
|
|
233
|
+
false_val: Any
|
|
234
|
+
|
|
235
|
+
@dataclass
|
|
236
|
+
class RangeExpr:
|
|
237
|
+
start: Any
|
|
238
|
+
end: Any
|
|
239
|
+
inclusive: bool = False
|
techscript/builtins.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""TechScript built-in functions — the first 80 registered into the global env.
|
|
2
|
+
|
|
3
|
+
Each function is a plain Python callable. The ``register_builtins(env)``
|
|
4
|
+
function populates a given ``Environment`` with all of them.
|
|
5
|
+
|
|
6
|
+
Categories (numbered to match TECHSCRIPT_REFERENCE.md):
|
|
7
|
+
1–10 I/O & output
|
|
8
|
+
11–25 Math & numeric
|
|
9
|
+
26–45 String
|
|
10
|
+
46–65 List & map
|
|
11
|
+
66–75 Type conversion & checking
|
|
12
|
+
76–80 System / misc
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import math
|
|
19
|
+
import os
|
|
20
|
+
import random
|
|
21
|
+
import time as _time
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from techscript.environment import Environment
|
|
25
|
+
from techscript.errors import TechScriptError, ValueErr, FileErr
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# Helpers
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
def _ts_print(*args: Any) -> None:
|
|
33
|
+
"""``say`` implementation — called for SayStmt in the interpreter."""
|
|
34
|
+
print(*args)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _ts_write(*args: Any) -> None:
|
|
38
|
+
print(*args, end="")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _ts_debug(val: Any) -> None:
|
|
42
|
+
print(f"[debug] {type(val).__name__}: {val!r}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _ts_log(msg: str) -> None:
|
|
46
|
+
t = _time.strftime("%H:%M:%S")
|
|
47
|
+
print(f"[{t}] {msg}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _ts_warn(msg: str) -> None:
|
|
51
|
+
import sys
|
|
52
|
+
print(f"[warn] {msg}", file=sys.stderr)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _ts_error_print(msg: str) -> None:
|
|
56
|
+
import sys
|
|
57
|
+
print(f"[error] {msg}", file=sys.stderr)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _ts_clear() -> None:
|
|
61
|
+
os.system("cls" if os.name == "nt" else "clear")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _ts_format(template: str, *args: Any) -> str:
|
|
65
|
+
return template.format(*args)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Math helpers
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def _clamp(x: float, lo: float, hi: float) -> float:
|
|
73
|
+
return max(lo, min(x, hi))
|
|
74
|
+
|
|
75
|
+
def _sign(x: float) -> int:
|
|
76
|
+
return (x > 0) - (x < 0)
|
|
77
|
+
|
|
78
|
+
def _is_even(x: int) -> bool:
|
|
79
|
+
return x % 2 == 0
|
|
80
|
+
|
|
81
|
+
def _is_odd(x: int) -> bool:
|
|
82
|
+
return x % 2 != 0
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# File helpers
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
def _read_file(path: str) -> str:
|
|
90
|
+
try:
|
|
91
|
+
with open(path, encoding="utf-8") as f:
|
|
92
|
+
return f.read()
|
|
93
|
+
except FileNotFoundError:
|
|
94
|
+
raise FileErr(f"File not found: '{path}'")
|
|
95
|
+
except PermissionError:
|
|
96
|
+
raise FileErr(f"Permission denied: '{path}'")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _write_file(path: str, data: str) -> None:
|
|
100
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
101
|
+
f.write(data)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _append_file(path: str, data: str) -> None:
|
|
105
|
+
with open(path, "a", encoding="utf-8") as f:
|
|
106
|
+
f.write(data)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _read_lines(path: str) -> list[str]:
|
|
110
|
+
return _read_file(path).splitlines()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _write_lines(path: str, lines: list[str]) -> None:
|
|
114
|
+
_write_file(path, "\n".join(lines))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _read_json(path: str) -> Any:
|
|
118
|
+
return json.loads(_read_file(path))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _write_json(path: str, data: Any) -> None:
|
|
122
|
+
_write_file(path, json.dumps(data, indent=2, ensure_ascii=False))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Type conversion & checking
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
def _to_int(val: Any) -> int:
|
|
130
|
+
try:
|
|
131
|
+
return int(val)
|
|
132
|
+
except (ValueError, TypeError):
|
|
133
|
+
raise ValueErr(f"Cannot convert {val!r} to int")
|
|
134
|
+
|
|
135
|
+
def _to_float(val: Any) -> float:
|
|
136
|
+
try:
|
|
137
|
+
return float(val)
|
|
138
|
+
except (ValueError, TypeError):
|
|
139
|
+
raise ValueErr(f"Cannot convert {val!r} to float")
|
|
140
|
+
|
|
141
|
+
def _to_str(val: Any) -> str:
|
|
142
|
+
if val is None:
|
|
143
|
+
return "none"
|
|
144
|
+
if isinstance(val, bool):
|
|
145
|
+
return "true" if val else "false"
|
|
146
|
+
return str(val)
|
|
147
|
+
|
|
148
|
+
def _to_bool(val: Any) -> bool:
|
|
149
|
+
return bool(val)
|
|
150
|
+
|
|
151
|
+
def _to_list(val: Any) -> list:
|
|
152
|
+
return list(val)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _typeof(val: Any) -> str:
|
|
156
|
+
if val is None:
|
|
157
|
+
return "none"
|
|
158
|
+
if isinstance(val, bool):
|
|
159
|
+
return "bool"
|
|
160
|
+
if isinstance(val, int):
|
|
161
|
+
return "int"
|
|
162
|
+
if isinstance(val, float):
|
|
163
|
+
return "float"
|
|
164
|
+
if isinstance(val, str):
|
|
165
|
+
return "str"
|
|
166
|
+
if isinstance(val, list):
|
|
167
|
+
return "list"
|
|
168
|
+
if isinstance(val, dict):
|
|
169
|
+
return "map"
|
|
170
|
+
return type(val).__name__
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------------
|
|
174
|
+
# Assert
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def _ts_assert(condition: Any, msg: str = "Assertion failed") -> None:
|
|
178
|
+
if not condition:
|
|
179
|
+
raise TechScriptError(msg)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
# Registration
|
|
184
|
+
# ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
def register_builtins(env: Environment) -> None:
|
|
187
|
+
"""Populate *env* with the first 80 built-in functions/values."""
|
|
188
|
+
|
|
189
|
+
# ===== I/O (1-10) =====
|
|
190
|
+
env.set("write", _ts_write) # 1
|
|
191
|
+
env.set("debug", _ts_debug) # 2
|
|
192
|
+
env.set("log", _ts_log) # 3
|
|
193
|
+
env.set("warn", _ts_warn) # 4
|
|
194
|
+
env.set("error", _ts_error_print) # 5
|
|
195
|
+
env.set("clear", _ts_clear) # 6
|
|
196
|
+
env.set("format", _ts_format) # 7
|
|
197
|
+
env.set("read_file", _read_file) # 8
|
|
198
|
+
env.set("write_file", _write_file) # 9
|
|
199
|
+
env.set("append_file", _append_file) # 10
|
|
200
|
+
|
|
201
|
+
# ===== Math (11-25) =====
|
|
202
|
+
env.set("abs", abs) # 11
|
|
203
|
+
env.set("round", round) # 12
|
|
204
|
+
env.set("ceil", math.ceil) # 13
|
|
205
|
+
env.set("floor", math.floor) # 14
|
|
206
|
+
env.set("min", min) # 15
|
|
207
|
+
env.set("max", max) # 16
|
|
208
|
+
env.set("sum", sum) # 17
|
|
209
|
+
env.set("sqrt", math.sqrt) # 18
|
|
210
|
+
env.set("pow", pow) # 19
|
|
211
|
+
env.set("clamp", _clamp) # 20
|
|
212
|
+
env.set("sign", _sign) # 21
|
|
213
|
+
env.set("is_even", _is_even) # 22
|
|
214
|
+
env.set("is_odd", _is_odd) # 23
|
|
215
|
+
env.set("gcd", math.gcd) # 24
|
|
216
|
+
env.set("pi", math.pi) # 25
|
|
217
|
+
|
|
218
|
+
# ===== Random (26-30) =====
|
|
219
|
+
env.set("random", random.random) # 26
|
|
220
|
+
env.set("randint", random.randint) # 27
|
|
221
|
+
env.set("choice", random.choice) # 28
|
|
222
|
+
env.set("shuffle", lambda lst: random.shuffle(lst) or lst) # 29
|
|
223
|
+
env.set("e", math.e) # 30
|
|
224
|
+
|
|
225
|
+
# ===== String helpers (31-45) — most are methods, these are free fns =====
|
|
226
|
+
env.set("upper", lambda s: s.upper()) # 31
|
|
227
|
+
env.set("lower", lambda s: s.lower()) # 32
|
|
228
|
+
env.set("trim", lambda s: s.strip()) # 33
|
|
229
|
+
env.set("split", lambda s, sep=" ": s.split(sep)) # 34
|
|
230
|
+
env.set("join", lambda sep, lst: sep.join(str(x) for x in lst)) # 35
|
|
231
|
+
env.set("replace", lambda s, old, new: s.replace(old, new)) # 36
|
|
232
|
+
env.set("contains", lambda s, sub: sub in s) # 37
|
|
233
|
+
env.set("starts_with", lambda s, p: s.startswith(p)) # 38
|
|
234
|
+
env.set("ends_with", lambda s, p: s.endswith(p)) # 39
|
|
235
|
+
env.set("chars", lambda s: list(s)) # 40
|
|
236
|
+
env.set("repeat", lambda s, n: s * n) # 41
|
|
237
|
+
env.set("capitalize", lambda s: s.capitalize()) # 42
|
|
238
|
+
env.set("title", lambda s: s.title()) # 43
|
|
239
|
+
env.set("index_of", lambda s, sub: s.find(sub)) # 44
|
|
240
|
+
env.set("count", lambda s, sub: s.count(sub)) # 45
|
|
241
|
+
|
|
242
|
+
# ===== List / collection (46-65) =====
|
|
243
|
+
env.set("len", len) # 46
|
|
244
|
+
env.set("size", len) # 47
|
|
245
|
+
env.set("range", lambda *a: list(range(*a))) # 48
|
|
246
|
+
env.set("enumerate", lambda lst: [[i, v] for i, v in enumerate(lst)]) # 49
|
|
247
|
+
env.set("zip", lambda a, b: [[x, y] for x, y in zip(a, b)]) # 50
|
|
248
|
+
env.set("sorted", sorted) # 51
|
|
249
|
+
env.set("reversed", lambda lst: list(reversed(lst))) # 52
|
|
250
|
+
env.set("map", lambda fn, lst: [fn(x) for x in lst]) # 53 (free fn form)
|
|
251
|
+
env.set("filter", lambda fn, lst: [x for x in lst if fn(x)]) # 54
|
|
252
|
+
env.set("flat", lambda lst: [x for sub in lst for x in (sub if isinstance(sub, list) else [sub])]) # 55
|
|
253
|
+
env.set("unique", lambda lst: list(dict.fromkeys(lst))) # 56
|
|
254
|
+
env.set("take", lambda lst, n: lst[:n]) # 57
|
|
255
|
+
env.set("drop", lambda lst, n: lst[n:]) # 58
|
|
256
|
+
env.set("push", lambda lst, v: lst.append(v) or lst) # 59
|
|
257
|
+
env.set("pop", lambda lst: lst.pop()) # 60
|
|
258
|
+
env.set("keys", lambda m: list(m.keys())) # 61
|
|
259
|
+
env.set("values", lambda m: list(m.values())) # 62
|
|
260
|
+
env.set("entries", lambda m: [[k, v] for k, v in m.items()]) # 63
|
|
261
|
+
env.set("merge", lambda a, b: {**a, **b}) # 64
|
|
262
|
+
env.set("has_key", lambda m, k: k in m) # 65
|
|
263
|
+
|
|
264
|
+
# ===== Type conversion & checking (66-75) =====
|
|
265
|
+
env.set("to_int", _to_int) # 66
|
|
266
|
+
env.set("to_float", _to_float) # 67
|
|
267
|
+
env.set("to_str", _to_str) # 68
|
|
268
|
+
env.set("to_bool", _to_bool) # 69
|
|
269
|
+
env.set("to_list", _to_list) # 70
|
|
270
|
+
env.set("typeof", _typeof) # 71
|
|
271
|
+
env.set("is_int", lambda v: isinstance(v, int) and not isinstance(v, bool)) # 72
|
|
272
|
+
env.set("is_float", lambda v: isinstance(v, float)) # 73
|
|
273
|
+
env.set("is_str", lambda v: isinstance(v, str)) # 74
|
|
274
|
+
env.set("is_list", lambda v: isinstance(v, list)) # 75
|
|
275
|
+
|
|
276
|
+
# ===== System (76-80) =====
|
|
277
|
+
env.set("is_map", lambda v: isinstance(v, dict)) # 76
|
|
278
|
+
env.set("is_none", lambda v: v is None) # 77
|
|
279
|
+
env.set("is_bool", lambda v: isinstance(v, bool)) # 78
|
|
280
|
+
env.set("sleep", lambda ms: _time.sleep(ms / 1000)) # 79
|
|
281
|
+
env.set("exit", lambda code=0: exit(code)) # 80
|
|
282
|
+
|
|
283
|
+
# === Bonus extras (commonly used) ===
|
|
284
|
+
env.set("assert", _ts_assert)
|
|
285
|
+
env.set("hash", hash)
|
|
286
|
+
env.set("read_lines", _read_lines)
|
|
287
|
+
env.set("write_lines", _write_lines)
|
|
288
|
+
env.set("read_json", _read_json)
|
|
289
|
+
env.set("write_json", _write_json)
|
|
290
|
+
env.set("file_exists", lambda p: os.path.exists(p))
|
|
291
|
+
env.set("delete_file", lambda p: os.remove(p))
|
|
292
|
+
env.set("list_dir", lambda p=".": os.listdir(p))
|
|
293
|
+
env.set("make_dir", lambda p: os.makedirs(p, exist_ok=True))
|
|
294
|
+
env.set("print_env", lambda: None) # placeholder
|
|
295
|
+
|
|
296
|
+
# Error constructors
|
|
297
|
+
env.set("Error", lambda msg: TechScriptError(msg))
|
|
298
|
+
env.set("ValueError", lambda msg: ValueErr(msg))
|