inscript-lang 1.7.1__tar.gz → 1.7.4__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-1.7.1 → inscript_lang-1.7.4}/PKG-INFO +1 -1
  2. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/environment.py +7 -0
  3. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/errors.py +39 -17
  4. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript.py +1 -1
  5. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript_lang.egg-info/PKG-INFO +1 -1
  6. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/interpreter.py +28 -9
  7. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/parser.py +7 -2
  8. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/pyproject.toml +1 -1
  9. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/repl.py +14 -1
  10. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/setup.py +1 -1
  11. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/README.md +0 -0
  12. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/analyzer.py +0 -0
  13. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/ast_nodes.py +0 -0
  14. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/compiler.py +0 -0
  15. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript_fmt.py +0 -0
  16. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript_lang.egg-info/SOURCES.txt +0 -0
  17. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript_lang.egg-info/dependency_links.txt +0 -0
  18. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript_lang.egg-info/entry_points.txt +0 -0
  19. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript_lang.egg-info/requires.txt +0 -0
  20. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript_lang.egg-info/top_level.txt +0 -0
  21. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/inscript_test.py +0 -0
  22. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/lexer.py +0 -0
  23. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/pygame_backend.py +0 -0
  24. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/setup.cfg +0 -0
  25. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/stdlib.py +0 -0
  26. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/stdlib_extended.py +0 -0
  27. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/stdlib_extended_2.py +0 -0
  28. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/stdlib_game.py +0 -0
  29. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/stdlib_values.py +0 -0
  30. {inscript_lang-1.7.1 → inscript_lang-1.7.4}/vm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.7.1
3
+ Version: 1.7.4
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
@@ -16,9 +16,16 @@ class Environment:
16
16
  self.name: str = name
17
17
  self._store: Dict[str, Any] = {}
18
18
  self._consts: set = set() # names that cannot be reassigned
19
+ # v1.7.4: when True, `let` re-declarations silently re-bind (REPL mode)
20
+ self._repl_mode: bool = False
19
21
 
20
22
  # ── Define ────────────────────────────────────────────────────────────────
21
23
  def define(self, name: str, value: Any, is_const: bool = False) -> None:
24
+ # v1.7.4: in REPL mode, re-declaring an existing non-const `let` just
25
+ # re-binds it. Attempting to re-declare a const is still an error.
26
+ if self._repl_mode and name in self._store and name in self._consts:
27
+ raise InScriptRuntimeError(
28
+ f"Cannot re-declare constant '{name}' in REPL", 0)
22
29
  self._store[name] = value
23
30
  if is_const:
24
31
  self._consts.add(name)
@@ -53,6 +53,9 @@ ERROR_CODES = {
53
53
  "MatchError": "E0047",
54
54
  "PropertyError": "E0048",
55
55
  "NilAccess": "E0049",
56
+
57
+ # Deprecated-keyword hard errors (v1.7.4)
58
+ "NullKeyword": "E0055",
56
59
  }
57
60
 
58
61
  DOCS_BASE = "https://docs.inscript.dev/errors"
@@ -103,11 +106,15 @@ class InScriptError(Exception):
103
106
  if self.hint:
104
107
  parts.append(f" Hint: {self.hint}")
105
108
 
106
- # Call trace (runtime only)
109
+ # Call trace (runtime only) — v1.7.3: entries are (fn, file, line, src) 4-tuples
107
110
  if self.call_trace:
108
111
  parts.append("\nCall stack (most recent last):")
109
- for fn, file, ln in self.call_trace:
112
+ for entry in self.call_trace:
113
+ fn, file, ln = entry[0], entry[1], entry[2]
114
+ src = entry[3] if len(entry) > 3 else ""
110
115
  parts.append(f" File \"{file}\", line {ln}, in {fn}")
116
+ if src:
117
+ parts.append(f" {src}")
111
118
 
112
119
  # Docs link
113
120
  parts.append(f" See: {DOCS_BASE}/{code}")
@@ -222,11 +229,12 @@ class InScriptWarning:
222
229
  # ─────────────────────────────────────────────────────────────────────────────
223
230
 
224
231
  class CallFrame:
225
- __slots__ = ("fn_name", "file", "line")
226
- def __init__(self, fn_name: str, file: str, line: int):
227
- self.fn_name = fn_name
228
- self.file = file
229
- self.line = line
232
+ __slots__ = ("fn_name", "file", "line", "source_line")
233
+ def __init__(self, fn_name: str, file: str, line: int, source_line: str = ""):
234
+ self.fn_name = fn_name
235
+ self.file = file
236
+ self.line = line
237
+ self.source_line = source_line # v1.7.3: source snippet at this frame
230
238
 
231
239
  def as_tuple(self) -> Tuple[str, str, int]:
232
240
  return (self.fn_name, self.file, self.line)
@@ -239,40 +247,54 @@ class InScriptCallStack:
239
247
  """
240
248
  MAX_FRAMES = 200
241
249
 
242
- def __init__(self, filename: str = "<script>"):
250
+ def __init__(self, filename: str = "<script>", src_lines: list = None):
243
251
  self._frames: List[CallFrame] = []
244
252
  self._file = filename
253
+ self._src = src_lines or [] # v1.7.3: source lines for per-frame snippets
254
+
255
+ def _lookup_src(self, line: int) -> str:
256
+ """Return stripped source line, or '' if unavailable."""
257
+ if self._src and 0 < line <= len(self._src):
258
+ return self._src[line - 1].strip()
259
+ return ""
245
260
 
246
261
  def push(self, fn_name: str, line: int):
247
262
  if len(self._frames) < self.MAX_FRAMES:
248
- self._frames.append(CallFrame(fn_name, self._file, line))
263
+ self._frames.append(
264
+ CallFrame(fn_name, self._file, line, self._lookup_src(line))
265
+ )
249
266
 
250
267
  def pop(self):
251
268
  if self._frames:
252
269
  self._frames.pop()
253
270
 
254
271
  def snapshot(self) -> List[Tuple[str, str, int]]:
255
- """Return a copy of current frames as list of (fn, file, line) tuples."""
256
- return [f.as_tuple() for f in self._frames]
272
+ """Return a copy of current frames as (fn, file, line, source_line) 4-tuples.
273
+ Backwards-compatible: consumers that only unpack 3 elements still work."""
274
+ return [(f.fn_name, f.file, f.line, f.source_line) for f in self._frames]
257
275
 
258
276
  def update_top_line(self, line: int):
259
- """Keep top frame's line number current as execution proceeds."""
260
- if self._frames:
261
- self._frames[-1].line = line
277
+ """v1.7.3: Keep top frame's line number AND source snippet current during execution."""
278
+ if self._frames and line:
279
+ f = self._frames[-1]
280
+ f.line = line
281
+ f.source_line = self._lookup_src(line)
262
282
 
263
283
  def format(self) -> str:
264
284
  if not self._frames:
265
285
  return ""
266
- lines = ["Call stack (most recent last):"]
267
- shown = self._frames
286
+ lines = ["Call stack (most recent last):"]
287
+ shown = self._frames
268
288
  truncated = 0
269
289
  if len(shown) > 20:
270
290
  truncated = len(shown) - 20
271
- shown = shown[-20:]
291
+ shown = shown[-20:]
272
292
  if truncated:
273
293
  lines.append(f" ... {truncated} earlier frame(s) ...")
274
294
  for f in shown:
275
295
  lines.append(f' File "{f.file}", line {f.line}, in {f.fn_name}')
296
+ if f.source_line: # v1.7.3: source snippet per frame
297
+ lines.append(f" {f.source_line}")
276
298
  return "\n".join(lines)
277
299
 
278
300
 
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
24
24
  SemanticError, InScriptRuntimeError,
25
25
  MultiError, InScriptWarning)
26
26
 
27
- VERSION = "1.7.1"
27
+ VERSION = "1.7.4"
28
28
  LANG = "InScript"
29
29
  PACKAGES_DIR = os.path.join(os.path.expanduser("~"), ".inscript", "packages")
30
30
  REGISTRY_URL = "https://raw.githubusercontent.com/authorss81/inscript-packages/main/registry.json"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.7.1
3
+ Version: 1.7.4
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
@@ -39,7 +39,7 @@ class Interpreter(Visitor):
39
39
  self._env = self._globals
40
40
  self._call_depth = 0
41
41
  self._MAX_CALL_DEPTH = 500
42
- self._call_stack = InScriptCallStack(filename) # Phase 3.4
42
+ self._call_stack = InScriptCallStack(filename, src_lines=source_lines) # v1.7.3
43
43
 
44
44
  # v1.3.0: dispatch cache — build once, avoids getattr on every node visit
45
45
  self._dispatch: dict = {}
@@ -75,6 +75,22 @@ class Interpreter(Visitor):
75
75
 
76
76
  # ── entry point ───────────────────────────────────────────────────────────
77
77
 
78
+ # v1.7.3: Override Visitor.visit() to track current execution line in the
79
+ # top call-stack frame. This makes stack-trace line numbers point to the
80
+ # exact statement that was executing when an error occurred, rather than
81
+ # the call-site where the function was entered.
82
+ def visit(self, node) -> Any:
83
+ line = getattr(node, 'line', 0)
84
+ if line:
85
+ self._call_stack.update_top_line(line)
86
+ cls = type(node)
87
+ try:
88
+ return self._dispatch[cls](node)
89
+ except KeyError:
90
+ method = getattr(self, f"visit_{cls.__name__}", self.generic_visit)
91
+ self._dispatch[cls] = method
92
+ return method(node)
93
+
78
94
  def run(self, program: Program) -> Any:
79
95
  """Execute a full program."""
80
96
  return self.visit(program)
@@ -1288,16 +1304,18 @@ class Interpreter(Visitor):
1288
1304
 
1289
1305
  def visit_ThrowStmt(self, node) -> Any:
1290
1306
  val = self.visit(node.value)
1291
- # BUG-11 fix: tag error with the InScript type name of the thrown value
1292
- # so that `catch e: string` can match when `throw "hello"` is used
1293
1307
  if isinstance(val, str): etype = 'string'
1294
1308
  elif isinstance(val, bool): etype = 'bool'
1295
1309
  elif isinstance(val, int): etype = 'int'
1296
1310
  elif isinstance(val, float): etype = 'float'
1297
1311
  else: etype = 'Error'
1298
- err = InScriptRuntimeError(str(val), node.line)
1299
- err.error_type = etype
1300
- err.thrown_value = val
1312
+ # v1.7.3: capture full call stack at throw time
1313
+ trace = self._call_stack.snapshot()
1314
+ err = InScriptRuntimeError(str(val), node.line,
1315
+ source_line=self._src_line(node.line),
1316
+ call_trace=trace)
1317
+ err.error_type = etype
1318
+ err.thrown_value = val
1301
1319
  raise err
1302
1320
 
1303
1321
  def visit_TryExpr(self, node) -> Any:
@@ -1488,12 +1506,13 @@ class Interpreter(Visitor):
1488
1506
  def visit_StringLiteralExpr(self,n): return n.value
1489
1507
  def visit_BoolLiteralExpr(self, n): return n.value
1490
1508
  def visit_NullLiteralExpr(self, n):
1491
- # v1.7.4: 'null' keyword is a hard error; 'nil' is fine
1509
+ # v1.7.4: 'null' keyword is a hard error; 'nil' is correct
1492
1510
  if getattr(n, '_is_null_keyword', False):
1493
- self._error(
1511
+ raise InScriptRuntimeError(
1494
1512
  "'null' was removed in v1.7.4 — use 'nil' instead. "
1495
1513
  "Run: inscript migrate <file> to auto-fix.",
1496
- getattr(n, 'line', 0)
1514
+ getattr(n, 'line', 0),
1515
+ code="E0055",
1497
1516
  )
1498
1517
  return None
1499
1518
 
@@ -444,6 +444,9 @@ class Parser:
444
444
  TT.STRING_TYPE, TT.VOID_TYPE, TT.IDENT):
445
445
  name = tok.value
446
446
  self.advance()
447
+ elif tok.type == TT.NIL: # v1.7.2: nil as type annotation
448
+ name = "nil"
449
+ self.advance()
447
450
  else:
448
451
  self._error(f"Expected type name, got '{tok.value}'")
449
452
  ann = TypeAnnotation(name=name, line=line, col=col)
@@ -885,9 +888,11 @@ class Parser:
885
888
  # e.g. items: [] or count: 0 or value: nil
886
889
  type_ann = None
887
890
  _type_starts = (TT.IDENT, TT.INT_TYPE, TT.FLOAT_TYPE, TT.BOOL_TYPE,
888
- TT.STRING_TYPE, TT.VOID_TYPE, TT.LBRACE, TT.LPAREN)
891
+ TT.STRING_TYPE, TT.VOID_TYPE, TT.LBRACE, TT.LPAREN,
892
+ TT.NIL) # v1.7.2: nil is a valid type (nullable/any)
889
893
  # Tokens that unambiguously start a literal default (not a type name)
890
- _default_starts = (TT.NIL, TT.NULL, TT.BOOL, TT.INT, TT.FLOAT, TT.STRING, TT.MINUS)
894
+ # Note: TT.NIL removed could be type OR default; handled by _type_starts
895
+ _default_starts = (TT.NULL, TT.BOOL, TT.INT, TT.FLOAT, TT.STRING, TT.MINUS)
891
896
 
892
897
  if self.check(TT.LBRACKET):
893
898
  if self.peek.type != TT.RBRACKET:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "inscript-lang"
7
- version = "1.7.1"
7
+ version = "1.7.4"
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 = "1.7.1"
43
+ VERSION = "1.7.4"
44
44
 
45
45
  # ── ANSI colours ──────────────────────────────────────────────────────────────
46
46
  def _c(code, text):
@@ -434,6 +434,9 @@ class EnhancedREPL:
434
434
  self._history: List[str] = []
435
435
  self._session: List[str] = []
436
436
  self._last_src: str = ""
437
+ # v1.7.4: mark the root env as REPL-mode so `let` re-declarations
438
+ # silently re-bind instead of erroring ("let x=1" then "let x=2" works)
439
+ self._interp._env._repl_mode = True
437
440
  self._setup_readline()
438
441
 
439
442
  def _setup_readline(self):
@@ -457,6 +460,13 @@ class EnhancedREPL:
457
460
  from parser import Parser
458
461
  from errors import InScriptError
459
462
  t0 = time.perf_counter()
463
+
464
+ # v1.7.4: snapshot the top-level env so a runtime error can't corrupt
465
+ # previously-defined globals. On error we restore the snapshot, which
466
+ # means any partial defines from the failed block are rolled back —
467
+ # already-defined names from prior REPL lines are always preserved.
468
+ env_snapshot = dict(self._interp._env._store)
469
+
460
470
  try:
461
471
  tokens = Lexer(source).tokenize()
462
472
  program = Parser(tokens).parse()
@@ -474,8 +484,11 @@ class EnhancedREPL:
474
484
  result = self._interp.run(program)
475
485
  return result, None, (time.perf_counter() - t0) * 1000
476
486
  except InScriptError as e:
487
+ # v1.7.4: restore globals so surviving names are untouched
488
+ self._interp._env._store = env_snapshot
477
489
  return None, _format_error(str(e), source), (time.perf_counter() - t0) * 1000
478
490
  except Exception as e:
491
+ self._interp._env._store = env_snapshot
479
492
  import traceback as _tb
480
493
  return None, f"Internal error: {e}\n{_tb.format_exc()}", (time.perf_counter() - t0) * 1000
481
494
 
@@ -11,7 +11,7 @@ from setuptools import setup, find_packages
11
11
 
12
12
  setup(
13
13
  name = "inscript-lang",
14
- version = "1.7.1",
14
+ version = "1.7.2",
15
15
  author = "Shreyasi Sarkar",
16
16
  description = "InScript — a game-focused scripting language for 2D games",
17
17
  long_description = open("README.md", encoding="utf-8").read(),
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes