inscript-lang 2.0.0__tar.gz → 2.0.2__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.
Files changed (30) hide show
  1. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/PKG-INFO +1 -1
  2. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/analyzer.py +6 -0
  3. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/ast_nodes.py +6 -4
  4. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript.py +1 -1
  5. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript_lang.egg-info/PKG-INFO +1 -1
  6. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/interpreter.py +72 -37
  7. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/parser.py +8 -2
  8. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/pyproject.toml +1 -1
  9. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/repl.py +1 -1
  10. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/stdlib_values.py +13 -4
  11. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/README.md +0 -0
  12. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/compiler.py +0 -0
  13. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/environment.py +0 -0
  14. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/errors.py +0 -0
  15. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript_fmt.py +0 -0
  16. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript_lang.egg-info/SOURCES.txt +0 -0
  17. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript_lang.egg-info/dependency_links.txt +0 -0
  18. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript_lang.egg-info/entry_points.txt +0 -0
  19. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript_lang.egg-info/requires.txt +0 -0
  20. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript_lang.egg-info/top_level.txt +0 -0
  21. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/inscript_test.py +0 -0
  22. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/lexer.py +0 -0
  23. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/pygame_backend.py +0 -0
  24. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/setup.cfg +0 -0
  25. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/setup.py +0 -0
  26. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/stdlib.py +0 -0
  27. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/stdlib_extended.py +0 -0
  28. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/stdlib_extended_2.py +0 -0
  29. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/stdlib_game.py +0 -0
  30. {inscript_lang-2.0.0 → inscript_lang-2.0.2}/vm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: InScript — a game-focused scripting language with 59 game modules and a bytecode VM
5
5
  Author: Shreyasi Sarkar
6
6
  License: MIT
@@ -1772,6 +1772,12 @@ class Analyzer(Visitor):
1772
1772
  if s not in (T_INT, T_ANY) or e not in (T_INT, T_ANY):
1773
1773
  self._error(f"Range bounds must be int, got '{s}' and '{e}'",
1774
1774
  node.line, node.col)
1775
+ # v2.0.1: validate optional step expression
1776
+ if node.step is not None:
1777
+ st = self.visit(node.step)
1778
+ if st not in (T_INT, T_ANY):
1779
+ self._error(f"Range step must be int, got '{st}'",
1780
+ node.line, node.col)
1775
1781
  return InScriptType("Range", [T_INT])
1776
1782
 
1777
1783
  def visit_LambdaExpr(self, node: LambdaExpr) -> InScriptType:
@@ -195,10 +195,11 @@ class LambdaExpr(Node):
195
195
 
196
196
  @dataclass
197
197
  class RangeExpr(Node):
198
- """0..10 (exclusive) | 0..=10 (inclusive)"""
198
+ """0..10 (exclusive) | 0..=10 (inclusive) | 0..10 step 2 (v2.0.1)"""
199
199
  start: Node
200
200
  end: Node
201
- inclusive: bool = False
201
+ inclusive: bool = False
202
+ step: object = None # None = auto (1, or -1 if start > end)
202
203
 
203
204
 
204
205
  # ─────────────────────────────────────────────────────────────────────────────
@@ -560,10 +561,11 @@ class PropertyDecl(Node):
560
561
 
561
562
  @dataclass
562
563
  class RangeExpr(Node):
563
- """0..10 (exclusive) | 0..=10 (inclusive)"""
564
+ """0..10 (exclusive) | 0..=10 (inclusive) | 0..10 step 2 (v2.0.1)"""
564
565
  start: Node
565
566
  end: Node
566
- inclusive: bool = False
567
+ inclusive: bool = False
568
+ step: object = None # None = auto (1 ascending, -1 descending)
567
569
 
568
570
  @dataclass
569
571
  class LambdaExpr(Node):
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
24
24
  SemanticError, InScriptRuntimeError,
25
25
  MultiError, InScriptWarning)
26
26
 
27
- VERSION = "2.0.0"
27
+ VERSION = "2.0.2"
28
28
 
29
29
  MANIFEST_FILENAME = "inscript.toml"
30
30
  LOCK_FILENAME = "inscript.lock"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: InScript — a game-focused scripting language with 59 game modules and a bytecode VM
5
5
  Author: Shreyasi Sarkar
6
6
  License: MIT
@@ -2470,7 +2470,13 @@ class Interpreter(Visitor):
2470
2470
  def visit_RangeExpr(self, node: RangeExpr) -> Any:
2471
2471
  start = self.visit(node.start)
2472
2472
  end = self.visit(node.end)
2473
- return InScriptRange(start, end, inclusive=node.inclusive)
2473
+ # v2.0.1: optional `step` expression
2474
+ if node.step is not None:
2475
+ step = self.visit(node.step)
2476
+ else:
2477
+ # Auto-descend: if start > end, default step to -1
2478
+ step = -1 if int(start) > int(end) else 1
2479
+ return InScriptRange(start, end, step=step, inclusive=node.inclusive)
2474
2480
 
2475
2481
  def visit_AwaitExpr(self, node: AwaitExpr) -> Any:
2476
2482
  """
@@ -2510,41 +2516,76 @@ class Interpreter(Visitor):
2510
2516
  cond = self.visit(node.condition)
2511
2517
  return self.visit(node.then_expr) if cond else self.visit(node.else_expr)
2512
2518
 
2519
+ @staticmethod
2520
+ def _fstring_segments(template: str):
2521
+ """v2.0.2: brace-depth-aware f-string segment splitter.
2522
+ Returns list of (kind, text) where kind is "lit" or "expr".
2523
+ Handles nested braces (match, for, object literals) correctly.
2524
+ {{ and }} in source are stored as sentinel \x00{ and }\x00.
2525
+ """
2526
+ segments = []
2527
+ i = 0
2528
+ n = len(template)
2529
+ lit_buf = []
2530
+ while i < n:
2531
+ ch = template[i]
2532
+ # Sentinel escaped braces: \x00{ = literal {, }\x00 = literal }
2533
+ if ch == "\x00" and i + 1 < n and template[i + 1] == "{":
2534
+ lit_buf.append("{"); i += 2
2535
+ elif ch == "}" and i + 1 < n and template[i + 1] == "\x00":
2536
+ lit_buf.append("}"); i += 2
2537
+ elif ch == "{":
2538
+ # Start of an expression — find matching } with depth tracking
2539
+ if lit_buf:
2540
+ segments.append(("lit", "".join(lit_buf))); lit_buf = []
2541
+ depth = 1; j = i + 1; in_str = None
2542
+ while j < n and depth > 0:
2543
+ c2 = template[j]
2544
+ if in_str:
2545
+ if c2 == "\\" and j + 1 < n: j += 2; continue
2546
+ if c2 == in_str: in_str = None
2547
+ elif c2 in ('"', "\'"):
2548
+ in_str = c2
2549
+ elif c2 == "{":
2550
+ depth += 1
2551
+ elif c2 == "}":
2552
+ depth -= 1
2553
+ if depth == 0: break
2554
+ j += 1
2555
+ segments.append(("expr", template[i + 1:j]))
2556
+ i = j + 1
2557
+ else:
2558
+ lit_buf.append(ch); i += 1
2559
+ if lit_buf:
2560
+ segments.append(("lit", "".join(lit_buf)))
2561
+ return segments
2562
+
2513
2563
  def visit_FStringExpr(self, node: FStringExpr) -> Any:
2514
2564
  """f"Hello {name}, score={score:.1f}" — evaluate {expr} segments at runtime.
2565
+ v2.0.2: brace-depth-aware splitter handles match/for/object literals inside {}.
2515
2566
  Supports Python-style format specs: {x:.2f}, {n:06d}, {s:>20} etc.
2516
- {{ and }} in source produce literal braces (stored as \x00{ and }\x00 sentinels)."""
2517
- import re
2567
+ {{ and }} in source produce literal braces."""
2518
2568
  result = []
2519
- template = node.template
2520
- pos = 0
2521
- for m in re.finditer(r'(?<!\x00)\{([^}\x00][^}]*)\}', template):
2522
- result.append(template[pos:m.start()])
2523
- inner = m.group(1).strip()
2524
-
2525
- # Split off format spec: {expr:spec}
2526
- # Key: only split on ':' that is NOT inside brackets AND NOT after '?'
2527
- # (ternary x ? a : b uses ':' but it's not a format spec)
2569
+ for kind, text in self._fstring_segments(node.template):
2570
+ if kind == "lit":
2571
+ result.append(text)
2572
+ continue
2573
+ inner = text.strip()
2574
+ # Split off format spec {expr:spec}, respecting brackets and ternary
2528
2575
  fmt_spec = None
2529
- depth = 0
2530
- ternary_depth = 0
2531
- split_at = -1
2576
+ depth = 0; ternary_depth = 0; split_at = -1
2532
2577
  for i, ch in enumerate(inner):
2533
- if ch in '([{': depth += 1
2534
- elif ch in ')]}': depth -= 1
2535
- elif ch == '?' and depth == 0: ternary_depth += 1
2536
- elif ch == ':' and depth == 0:
2537
- if ternary_depth > 0:
2538
- ternary_depth -= 1 # this ':' closes the ternary
2539
- else:
2540
- split_at = i
2541
- break
2578
+ if ch in "([{": depth += 1
2579
+ elif ch in ")]}": depth -= 1
2580
+ elif ch == "?" and depth == 0: ternary_depth += 1
2581
+ elif ch == ":" and depth == 0:
2582
+ if ternary_depth > 0: ternary_depth -= 1
2583
+ else: split_at = i; break
2542
2584
  if split_at > 0:
2543
- expr_src = inner[:split_at].strip()
2544
- fmt_spec = inner[split_at+1:].strip()
2585
+ expr_src = inner[:split_at].strip()
2586
+ fmt_spec = inner[split_at + 1:].strip()
2545
2587
  else:
2546
2588
  expr_src = inner
2547
-
2548
2589
  try:
2549
2590
  from parser import parse
2550
2591
  prog = parse(expr_src)
@@ -2552,19 +2593,13 @@ class Interpreter(Visitor):
2552
2593
  for stmt in prog.body:
2553
2594
  val = self.visit(stmt)
2554
2595
  if fmt_spec:
2555
- # Apply Python format spec directly — supports .2f, 06d, >10s etc.
2556
- try:
2557
- result.append(format(val, fmt_spec))
2558
- except Exception:
2559
- result.append(_inscript_str(val))
2596
+ try: result.append(format(val, fmt_spec))
2597
+ except: result.append(_inscript_str(val))
2560
2598
  else:
2561
2599
  result.append(_inscript_str(val))
2562
2600
  except Exception:
2563
- result.append(f"{{{inner}}}")
2564
- pos = m.end()
2565
- result.append(template[pos:])
2566
- final = "".join(result).replace("\x00{", "{").replace("}\x00", "}")
2567
- return final
2601
+ result.append("{" + inner + "}")
2602
+ return "".join(result)
2568
2603
 
2569
2604
  def visit_DestructureDecl(self, node: DestructureDecl) -> Any:
2570
2605
  """let [a, b, c] = arr | let {x, y} = point | let [[a,b],[c,d]] = nested"""
@@ -1555,13 +1555,19 @@ class Parser:
1555
1555
  line, col = self._pos()
1556
1556
  left = self.parse_shift()
1557
1557
 
1558
- # Range: start..end or start..=end
1558
+ # Range: start..end or start..=end or start..end step n (v2.0.1)
1559
1559
  if self.check(TT.DOTDOT, TT.DOTDOT_EQ):
1560
1560
  inclusive = (self.current.type == TT.DOTDOT_EQ)
1561
1561
  self.advance()
1562
1562
  right = self.parse_shift()
1563
+ # Optional `step` suffix: 0..20 step 2
1564
+ step_expr = None
1565
+ if (self.current and self.current.type == TT.IDENT
1566
+ and self.current.value == "step"):
1567
+ self.advance() # consume 'step'
1568
+ step_expr = self.parse_shift()
1563
1569
  return RangeExpr(start=left, end=right, inclusive=inclusive,
1564
- line=line, col=col)
1570
+ step=step_expr, line=line, col=col)
1565
1571
 
1566
1572
  # Membership: x in collection / x not in collection
1567
1573
  if self.check(TT.IN):
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "inscript-lang"
7
- version = "2.0.0"
7
+ version = "2.0.2"
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.0"
43
+ VERSION = "2.0.2"
44
44
 
45
45
  # ── ANSI colours ──────────────────────────────────────────────────────────────
46
46
  def _c(code, text):
@@ -281,16 +281,25 @@ class InScriptRange:
281
281
  self.inclusive = inclusive
282
282
 
283
283
  def __iter__(self):
284
- stop = self.end + 1 if self.inclusive else self.end
284
+ # v2.0.1: support negative step (descending ranges)
285
+ if self.step > 0:
286
+ stop = self.end + 1 if self.inclusive else self.end
287
+ else:
288
+ stop = self.end - 1 if self.inclusive else self.end
285
289
  return iter(range(self.start, stop, self.step))
286
290
 
287
291
  def __len__(self):
288
- stop = self.end + 1 if self.inclusive else self.end
289
- return max(0, (stop - self.start + self.step - 1) // self.step)
292
+ if self.step > 0:
293
+ stop = self.end + 1 if self.inclusive else self.end
294
+ return max(0, (stop - self.start + self.step - 1) // self.step)
295
+ else:
296
+ stop = self.end - 1 if self.inclusive else self.end
297
+ return max(0, (self.start - stop + (-self.step) - 1) // (-self.step))
290
298
 
291
299
  def __repr__(self):
292
300
  op = "..=" if self.inclusive else ".."
293
- return f"{self.start}{op}{self.end}"
301
+ s = f" step {self.step}" if self.step not in (1, -1) else ("" if self.step == 1 else " step -1")
302
+ return f"{self.start}{op}{self.end}{s}"
294
303
 
295
304
 
296
305
  class InScriptGenerator:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes