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 ADDED
@@ -0,0 +1,2 @@
1
+ """TechScript — A simple, friendly programming language."""
2
+ __version__ = "1.0.1"
techscript/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Allow running TechScript as `python -m techscript`."""
2
+ from techscript.cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -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))