inscript-lang 2.0.1__tar.gz → 2.0.3__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.
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/PKG-INFO +1 -1
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript.py +1 -1
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/interpreter.py +90 -39
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/parser.py +21 -3
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/pyproject.toml +1 -1
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/repl.py +1 -1
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/README.md +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/analyzer.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/ast_nodes.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/compiler.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/environment.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/errors.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript_fmt.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/inscript_test.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/lexer.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/pygame_backend.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/setup.cfg +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/setup.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/stdlib.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/stdlib_extended.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/stdlib_extended_2.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/stdlib_game.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/stdlib_values.py +0 -0
- {inscript_lang-2.0.1 → inscript_lang-2.0.3}/vm.py +0 -0
|
@@ -993,7 +993,17 @@ class Interpreter(Visitor):
|
|
|
993
993
|
return self.visit(node.expr)
|
|
994
994
|
|
|
995
995
|
def visit_PrintStmt(self, node: PrintStmt) -> Any:
|
|
996
|
-
|
|
996
|
+
# v2.0.3: spread args in print(...arr)
|
|
997
|
+
parts = []
|
|
998
|
+
for a in node.args:
|
|
999
|
+
if isinstance(a, SpreadExpr):
|
|
1000
|
+
val = self.visit(a.expr)
|
|
1001
|
+
if isinstance(val, list):
|
|
1002
|
+
parts.extend(_inscript_str(v) for v in val)
|
|
1003
|
+
else:
|
|
1004
|
+
parts.append(_inscript_str(val))
|
|
1005
|
+
else:
|
|
1006
|
+
parts.append(_inscript_str(self.visit(a)))
|
|
997
1007
|
print(" ".join(parts))
|
|
998
1008
|
return None
|
|
999
1009
|
|
|
@@ -2425,10 +2435,22 @@ class Interpreter(Visitor):
|
|
|
2425
2435
|
fields[field.name] = self.visit(field.default) if field.default else None
|
|
2426
2436
|
|
|
2427
2437
|
# Then overrides from initializer
|
|
2438
|
+
# v2.0.3: spread syntax — P{...defaults, x: 5}
|
|
2428
2439
|
provided_names = set()
|
|
2429
2440
|
for name, value_node in node.fields:
|
|
2430
|
-
|
|
2431
|
-
|
|
2441
|
+
if name == "..." and isinstance(value_node, SpreadExpr):
|
|
2442
|
+
# Spread a dict, struct instance, or dict-like object
|
|
2443
|
+
spread_val = self.visit(value_node.expr)
|
|
2444
|
+
if isinstance(spread_val, InScriptInstance):
|
|
2445
|
+
for k, v in spread_val.fields.items():
|
|
2446
|
+
fields[k] = v; provided_names.add(k)
|
|
2447
|
+
elif isinstance(spread_val, dict):
|
|
2448
|
+
for k, v in spread_val.items():
|
|
2449
|
+
if not str(k).startswith("_"):
|
|
2450
|
+
fields[k] = v; provided_names.add(k)
|
|
2451
|
+
else:
|
|
2452
|
+
fields[name] = self.visit(value_node)
|
|
2453
|
+
provided_names.add(name)
|
|
2432
2454
|
|
|
2433
2455
|
# BUG-16 fix: warn about required fields (no default) that were not provided
|
|
2434
2456
|
missing_required = [
|
|
@@ -2516,41 +2538,76 @@ class Interpreter(Visitor):
|
|
|
2516
2538
|
cond = self.visit(node.condition)
|
|
2517
2539
|
return self.visit(node.then_expr) if cond else self.visit(node.else_expr)
|
|
2518
2540
|
|
|
2541
|
+
@staticmethod
|
|
2542
|
+
def _fstring_segments(template: str):
|
|
2543
|
+
"""v2.0.2: brace-depth-aware f-string segment splitter.
|
|
2544
|
+
Returns list of (kind, text) where kind is "lit" or "expr".
|
|
2545
|
+
Handles nested braces (match, for, object literals) correctly.
|
|
2546
|
+
{{ and }} in source are stored as sentinel \x00{ and }\x00.
|
|
2547
|
+
"""
|
|
2548
|
+
segments = []
|
|
2549
|
+
i = 0
|
|
2550
|
+
n = len(template)
|
|
2551
|
+
lit_buf = []
|
|
2552
|
+
while i < n:
|
|
2553
|
+
ch = template[i]
|
|
2554
|
+
# Sentinel escaped braces: \x00{ = literal {, }\x00 = literal }
|
|
2555
|
+
if ch == "\x00" and i + 1 < n and template[i + 1] == "{":
|
|
2556
|
+
lit_buf.append("{"); i += 2
|
|
2557
|
+
elif ch == "}" and i + 1 < n and template[i + 1] == "\x00":
|
|
2558
|
+
lit_buf.append("}"); i += 2
|
|
2559
|
+
elif ch == "{":
|
|
2560
|
+
# Start of an expression — find matching } with depth tracking
|
|
2561
|
+
if lit_buf:
|
|
2562
|
+
segments.append(("lit", "".join(lit_buf))); lit_buf = []
|
|
2563
|
+
depth = 1; j = i + 1; in_str = None
|
|
2564
|
+
while j < n and depth > 0:
|
|
2565
|
+
c2 = template[j]
|
|
2566
|
+
if in_str:
|
|
2567
|
+
if c2 == "\\" and j + 1 < n: j += 2; continue
|
|
2568
|
+
if c2 == in_str: in_str = None
|
|
2569
|
+
elif c2 in ('"', "\'"):
|
|
2570
|
+
in_str = c2
|
|
2571
|
+
elif c2 == "{":
|
|
2572
|
+
depth += 1
|
|
2573
|
+
elif c2 == "}":
|
|
2574
|
+
depth -= 1
|
|
2575
|
+
if depth == 0: break
|
|
2576
|
+
j += 1
|
|
2577
|
+
segments.append(("expr", template[i + 1:j]))
|
|
2578
|
+
i = j + 1
|
|
2579
|
+
else:
|
|
2580
|
+
lit_buf.append(ch); i += 1
|
|
2581
|
+
if lit_buf:
|
|
2582
|
+
segments.append(("lit", "".join(lit_buf)))
|
|
2583
|
+
return segments
|
|
2584
|
+
|
|
2519
2585
|
def visit_FStringExpr(self, node: FStringExpr) -> Any:
|
|
2520
2586
|
"""f"Hello {name}, score={score:.1f}" — evaluate {expr} segments at runtime.
|
|
2587
|
+
v2.0.2: brace-depth-aware splitter handles match/for/object literals inside {}.
|
|
2521
2588
|
Supports Python-style format specs: {x:.2f}, {n:06d}, {s:>20} etc.
|
|
2522
|
-
{{ and }} in source produce literal braces
|
|
2523
|
-
import re
|
|
2589
|
+
{{ and }} in source produce literal braces."""
|
|
2524
2590
|
result = []
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
inner =
|
|
2530
|
-
|
|
2531
|
-
# Split off format spec: {expr:spec}
|
|
2532
|
-
# Key: only split on ':' that is NOT inside brackets AND NOT after '?'
|
|
2533
|
-
# (ternary x ? a : b uses ':' but it's not a format spec)
|
|
2591
|
+
for kind, text in self._fstring_segments(node.template):
|
|
2592
|
+
if kind == "lit":
|
|
2593
|
+
result.append(text)
|
|
2594
|
+
continue
|
|
2595
|
+
inner = text.strip()
|
|
2596
|
+
# Split off format spec {expr:spec}, respecting brackets and ternary
|
|
2534
2597
|
fmt_spec = None
|
|
2535
|
-
depth = 0
|
|
2536
|
-
ternary_depth = 0
|
|
2537
|
-
split_at = -1
|
|
2598
|
+
depth = 0; ternary_depth = 0; split_at = -1
|
|
2538
2599
|
for i, ch in enumerate(inner):
|
|
2539
|
-
if ch in
|
|
2540
|
-
elif ch in
|
|
2541
|
-
elif ch ==
|
|
2542
|
-
elif ch ==
|
|
2543
|
-
if ternary_depth > 0:
|
|
2544
|
-
|
|
2545
|
-
else:
|
|
2546
|
-
split_at = i
|
|
2547
|
-
break
|
|
2600
|
+
if ch in "([{": depth += 1
|
|
2601
|
+
elif ch in ")]}": depth -= 1
|
|
2602
|
+
elif ch == "?" and depth == 0: ternary_depth += 1
|
|
2603
|
+
elif ch == ":" and depth == 0:
|
|
2604
|
+
if ternary_depth > 0: ternary_depth -= 1
|
|
2605
|
+
else: split_at = i; break
|
|
2548
2606
|
if split_at > 0:
|
|
2549
|
-
expr_src
|
|
2550
|
-
fmt_spec
|
|
2607
|
+
expr_src = inner[:split_at].strip()
|
|
2608
|
+
fmt_spec = inner[split_at + 1:].strip()
|
|
2551
2609
|
else:
|
|
2552
2610
|
expr_src = inner
|
|
2553
|
-
|
|
2554
2611
|
try:
|
|
2555
2612
|
from parser import parse
|
|
2556
2613
|
prog = parse(expr_src)
|
|
@@ -2558,19 +2615,13 @@ class Interpreter(Visitor):
|
|
|
2558
2615
|
for stmt in prog.body:
|
|
2559
2616
|
val = self.visit(stmt)
|
|
2560
2617
|
if fmt_spec:
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
result.append(format(val, fmt_spec))
|
|
2564
|
-
except Exception:
|
|
2565
|
-
result.append(_inscript_str(val))
|
|
2618
|
+
try: result.append(format(val, fmt_spec))
|
|
2619
|
+
except: result.append(_inscript_str(val))
|
|
2566
2620
|
else:
|
|
2567
2621
|
result.append(_inscript_str(val))
|
|
2568
2622
|
except Exception:
|
|
2569
|
-
result.append(
|
|
2570
|
-
|
|
2571
|
-
result.append(template[pos:])
|
|
2572
|
-
final = "".join(result).replace("\x00{", "{").replace("}\x00", "}")
|
|
2573
|
-
return final
|
|
2623
|
+
result.append("{" + inner + "}")
|
|
2624
|
+
return "".join(result)
|
|
2574
2625
|
|
|
2575
2626
|
def visit_DestructureDecl(self, node: DestructureDecl) -> Any:
|
|
2576
2627
|
"""let [a, b, c] = arr | let {x, y} = point | let [[a,b],[c,d]] = nested"""
|
|
@@ -1369,12 +1369,19 @@ class Parser:
|
|
|
1369
1369
|
|
|
1370
1370
|
self.expect(TT.LPAREN, "Expected '(' after 'print'")
|
|
1371
1371
|
args = []
|
|
1372
|
+
def _parse_print_arg():
|
|
1373
|
+
# v2.0.3: allow spread in print(...args)
|
|
1374
|
+
if self.check(TT.ELLIPSIS):
|
|
1375
|
+
sl2, sc2 = self._pos()
|
|
1376
|
+
self.advance()
|
|
1377
|
+
return SpreadExpr(expr=self.parse_expr(), line=sl2, col=sc2)
|
|
1378
|
+
return self.parse_expr()
|
|
1372
1379
|
if not self.check(TT.RPAREN):
|
|
1373
|
-
args.append(
|
|
1380
|
+
args.append(_parse_print_arg())
|
|
1374
1381
|
while self.match(TT.COMMA):
|
|
1375
1382
|
if self.check(TT.RPAREN):
|
|
1376
1383
|
break
|
|
1377
|
-
args.append(
|
|
1384
|
+
args.append(_parse_print_arg())
|
|
1378
1385
|
self.expect(TT.RPAREN, "Expected ')' after print arguments")
|
|
1379
1386
|
self.match(TT.SEMICOLON)
|
|
1380
1387
|
return PrintStmt(args=args, line=line, col=col)
|
|
@@ -2072,18 +2079,29 @@ class Parser:
|
|
|
2072
2079
|
result = (
|
|
2073
2080
|
self.check(TT.RBRACE) # empty Counter {}
|
|
2074
2081
|
or (self.current.type == TT.IDENT and self.peek.type == TT.COLON)
|
|
2082
|
+
or self.current.type == TT.ELLIPSIS # v2.0.3: spread P{...d}
|
|
2075
2083
|
)
|
|
2076
2084
|
self.pos = saved
|
|
2077
2085
|
return result
|
|
2078
2086
|
|
|
2079
2087
|
def parse_struct_init(self, name: str, line: int, col: int) -> StructInitExpr:
|
|
2080
|
-
"""Player { pos: Vec2(0,0), health: 100 }
|
|
2088
|
+
"""Player { pos: Vec2(0,0), health: 100 }
|
|
2089
|
+
v2.0.3: spread syntax: Player { ...defaults, health: 100 }
|
|
2090
|
+
"""
|
|
2081
2091
|
self.advance() # consume '{'
|
|
2082
2092
|
fields = []
|
|
2083
2093
|
|
|
2084
2094
|
while not self.check(TT.RBRACE) and not self.is_at_end():
|
|
2085
2095
|
if self.match(TT.SEMICOLON) or self.match(TT.COMMA):
|
|
2086
2096
|
continue
|
|
2097
|
+
# v2.0.3: spread field — P{...defaults, x: 5}
|
|
2098
|
+
if self.check(TT.ELLIPSIS):
|
|
2099
|
+
sl2, sc2 = self._pos()
|
|
2100
|
+
self.advance() # consume '...'
|
|
2101
|
+
spread_expr = self.parse_expr()
|
|
2102
|
+
fields.append(("...", SpreadExpr(expr=spread_expr, line=sl2, col=sc2)))
|
|
2103
|
+
self.match(TT.COMMA)
|
|
2104
|
+
continue
|
|
2087
2105
|
field_name = self.expect_ident("Expected field name in struct initializer")
|
|
2088
2106
|
self.expect(TT.COLON, f"Expected ':' after field name '{field_name}'")
|
|
2089
2107
|
value = self.parse_expr()
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "inscript-lang"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.3"
|
|
8
8
|
description = "InScript — a game-focused scripting language with 59 game modules and a bytecode VM"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -40,7 +40,7 @@ sys.path.insert(0, str(Path(__file__).parent))
|
|
|
40
40
|
|
|
41
41
|
HISTORY_FILE = Path.home() / ".inscript" / "history"
|
|
42
42
|
HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
-
VERSION = "2.0.
|
|
43
|
+
VERSION = "2.0.3"
|
|
44
44
|
|
|
45
45
|
# ── ANSI colours ──────────────────────────────────────────────────────────────
|
|
46
46
|
def _c(code, text):
|
|
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
|