inscript-lang 1.9.6__tar.gz → 1.9.8__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.9.6 → inscript_lang-1.9.8}/PKG-INFO +1 -1
  2. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/analyzer.py +41 -16
  3. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript.py +80 -1
  4. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript_lang.egg-info/PKG-INFO +1 -1
  5. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/interpreter.py +50 -3
  6. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/pyproject.toml +1 -1
  7. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/repl.py +1 -1
  8. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/stdlib_values.py +17 -1
  9. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/README.md +0 -0
  10. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/ast_nodes.py +0 -0
  11. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/compiler.py +0 -0
  12. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/environment.py +0 -0
  13. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/errors.py +0 -0
  14. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript_fmt.py +0 -0
  15. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript_lang.egg-info/SOURCES.txt +0 -0
  16. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript_lang.egg-info/dependency_links.txt +0 -0
  17. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript_lang.egg-info/entry_points.txt +0 -0
  18. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript_lang.egg-info/requires.txt +0 -0
  19. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript_lang.egg-info/top_level.txt +0 -0
  20. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/inscript_test.py +0 -0
  21. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/lexer.py +0 -0
  22. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/parser.py +0 -0
  23. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/pygame_backend.py +0 -0
  24. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/setup.cfg +0 -0
  25. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/setup.py +0 -0
  26. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/stdlib.py +0 -0
  27. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/stdlib_extended.py +0 -0
  28. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/stdlib_extended_2.py +0 -0
  29. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/stdlib_game.py +0 -0
  30. {inscript_lang-1.9.6 → inscript_lang-1.9.8}/vm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.9.6
3
+ Version: 1.9.8
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
@@ -65,12 +65,14 @@ T_TRANSFORM3D = InScriptType("Transform3D")
65
65
  T_TEXTURE = InScriptType("Texture")
66
66
 
67
67
  T_NEVER = InScriptType("never") # v1.8.3: bottom type — function never returns normally
68
+ T_PROMISE = InScriptType("Promise") # v1.9.7: async fn return type
68
69
 
69
70
  BUILTIN_TYPES: Dict[str, InScriptType] = {
70
71
  # canonical names
71
72
  "int": T_INT, "float": T_FLOAT, "bool": T_BOOL,
72
73
  "string": T_STRING, "void": T_VOID, "null": T_NULL, "any": T_ANY,
73
74
  "never": T_NEVER, # v1.8.3
75
+ "Promise": T_PROMISE, # v1.9.7: async fn return type
74
76
  # common aliases
75
77
  "str": T_STRING, "boolean": T_BOOL, "number": T_FLOAT,
76
78
  "nil": T_NULL, "object": T_ANY, "auto": T_ANY,
@@ -648,21 +650,36 @@ class Analyzer(Visitor):
648
650
  def visit_FunctionDecl(self, node: FunctionDecl) -> InScriptType:
649
651
  ret_type = self._resolve_type_ann(node.return_type)
650
652
 
653
+ # v1.9.7: async fn — external return type is Promise<T>, but the body
654
+ # is checked against the inner type T (what the fn actually returns).
655
+ # E.g.: `async fn f() -> string` → body checked vs `string`, external type = Promise<string>
656
+ is_async = getattr(node, 'is_async', False)
657
+ if is_async:
658
+ inner_ret_type = ret_type if ret_type not in (T_ANY, T_VOID) else T_ANY
659
+ external_ret_type = InScriptType("Promise", [inner_ret_type])
660
+ else:
661
+ inner_ret_type = ret_type
662
+ external_ret_type = ret_type
663
+
651
664
  # v1.8.4: return type inference — if no annotation and body has a single
652
- # unambiguous return type, infer it automatically
653
- if node.return_type is None and ret_type == T_ANY and node.body:
665
+ # unambiguous return type, infer it automatically (use inner type for async)
666
+ if node.return_type is None and inner_ret_type == T_ANY and node.body:
654
667
  inferred = self._infer_fn_return_type(node)
655
668
  if inferred not in (T_ANY, T_VOID, T_NULL):
656
- ret_type = inferred
669
+ inner_ret_type = inferred
670
+ if is_async:
671
+ external_ret_type = InScriptType("Promise", [inner_ret_type])
672
+ else:
673
+ external_ret_type = inner_ret_type
657
674
  # Update the hoisted symbol's type to the inferred one
658
675
  existing = self._scope.lookup(node.name)
659
676
  if existing and existing.kind == "fn":
660
- existing.type_ = ret_type
677
+ existing.type_ = external_ret_type
661
678
 
662
- # Register in current scope (if not already hoisted)
679
+ # Register in current scope using external type (Promise<T> for async)
663
680
  if not self._scope.lookup_local(node.name):
664
681
  self._define(Symbol(
665
- node.name, ret_type, kind="fn",
682
+ node.name, external_ret_type, kind="fn",
666
683
  fn_node=node, line=node.line, col=node.col
667
684
  ))
668
685
 
@@ -676,10 +693,10 @@ class Analyzer(Visitor):
676
693
 
677
694
  # Warn if function declares a non-void return type but body may not return
678
695
  if (node.return_type is not None and
679
- ret_type.name not in ("void", "nil", "any", "") and
696
+ inner_ret_type.name not in ("void", "nil", "any", "") and
680
697
  not node.is_native if hasattr(node, 'is_native') else True):
681
698
  if node.body and not self._body_always_returns(node.body):
682
- if ret_type == T_NEVER:
699
+ if inner_ret_type == T_NEVER:
683
700
  # v1.8.3: `-> never` functions MUST always throw / diverge
684
701
  self._error(
685
702
  f"Function '{node.name}' is declared '-> never' "
@@ -689,15 +706,15 @@ class Analyzer(Visitor):
689
706
  else:
690
707
  self._warn(
691
708
  "missing-return",
692
- f"Function '{node.name}' declares return type '{ret_type.name}' "
709
+ f"Function '{node.name}' declares return type '{inner_ret_type.name}' "
693
710
  f"but not all code paths return a value",
694
711
  node.line
695
712
  )
696
713
 
697
- # Analyze body in a new scope
714
+ # Analyze body in a new scope — body return type is inner (not Promise<T>)
698
715
  self._push_scope("fn")
699
716
  prev_ret = self._current_fn_return_type
700
- self._current_fn_return_type = ret_type
717
+ self._current_fn_return_type = inner_ret_type
701
718
 
702
719
  # Register parameters — mark used=True so we never warn about unused params
703
720
  for param in node.params:
@@ -714,7 +731,7 @@ class Analyzer(Visitor):
714
731
 
715
732
  self._current_fn_return_type = prev_ret
716
733
  self._pop_scope()
717
- return ret_type
734
+ return external_ret_type
718
735
 
719
736
  def _body_always_returns(self, block) -> bool:
720
737
  """Return True if every code path through block ends with a return/throw."""
@@ -1134,13 +1151,21 @@ class Analyzer(Visitor):
1134
1151
  def visit_ArrayLiteralExpr(self, node: ArrayLiteralExpr) -> InScriptType:
1135
1152
  if not node.elements:
1136
1153
  return array_type(T_ANY)
1154
+ # v1.9.8: infer element type; fall back to Array<any> on mixed types
1137
1155
  first_type = self.visit(node.elements[0])
1156
+ # Normalise literal string types to T_STRING for array inference
1157
+ if is_literal_type(first_type):
1158
+ first_type = T_STRING
1159
+ mixed = False
1138
1160
  for elem in node.elements[1:]:
1139
1161
  et = self.visit(elem)
1140
- if not types_compatible(first_type, et):
1141
- self._warn(f"Mixed types in array literal: '{first_type}' and '{et}'",
1142
- node.line)
1143
- return array_type(first_type)
1162
+ if is_literal_type(et):
1163
+ et = T_STRING
1164
+ if not types_compatible(first_type, et) and not types_compatible(et, first_type):
1165
+ mixed = True
1166
+ if mixed:
1167
+ return array_type(T_ANY) # [1, "a", true] → Array<any>
1168
+ return array_type(first_type) # [1, 2, 3] → Array<int>
1144
1169
 
1145
1170
  def visit_DictLiteralExpr(self, node: DictLiteralExpr) -> InScriptType:
1146
1171
  if not node.pairs:
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
24
24
  SemanticError, InScriptRuntimeError,
25
25
  MultiError, InScriptWarning)
26
26
 
27
- VERSION = "1.9.6"
27
+ VERSION = "1.9.8"
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"
@@ -315,6 +315,81 @@ def _compat_files(path: str) -> int:
315
315
  return 1
316
316
 
317
317
 
318
+ def _infer_types_file(path: str) -> int:
319
+ """
320
+ v1.9.8: `inscript --infer-types FILE`
321
+ Parse and type-check FILE, then print the inferred type for every
322
+ let/const declaration. Useful for understanding what the analyzer infers.
323
+ Returns 0 on success, 1 if file not found or parse error.
324
+ """
325
+ import os
326
+ if not os.path.isfile(path):
327
+ print(f"[InScript infer-types] File not found: '{path}'", file=sys.stderr)
328
+ return 1
329
+ try:
330
+ with open(path, encoding="utf-8") as f:
331
+ src = f.read()
332
+ except OSError as e:
333
+ print(f"[InScript infer-types] Cannot read '{path}': {e}", file=sys.stderr)
334
+ return 1
335
+
336
+ from lexer import Lexer
337
+ from parser import Parser
338
+ from analyzer import Analyzer
339
+ from ast_nodes import VarDecl
340
+
341
+ try:
342
+ tokens = Lexer(src).tokenize()
343
+ tree = Parser(tokens).parse()
344
+ except Exception as e:
345
+ print(f"[InScript infer-types] Parse error: {e}", file=sys.stderr)
346
+ return 1
347
+
348
+ analyzer = Analyzer()
349
+ try:
350
+ analyzer.analyze(tree)
351
+ except Exception:
352
+ pass # best-effort — show what we have even if there are errors
353
+
354
+ # Walk all VarDecl / ConstDecl nodes and report inferred types
355
+ results = []
356
+ def _walk(node):
357
+ if node is None:
358
+ return
359
+ if isinstance(node, (VarDecl,)):
360
+ sym = analyzer._scope.lookup(node.name) if hasattr(analyzer, '_scope') else None
361
+ typ = sym.type_ if sym else None
362
+ type_str = str(typ) if typ else "any"
363
+ results.append((node.line, "let" if not node.is_const else "const",
364
+ node.name, type_str))
365
+ # Recurse into body of blocks, fns etc.
366
+ for attr in ("body", "then_branch", "else_branch", "value",
367
+ "initializer", "statements"):
368
+ child = getattr(node, attr, None)
369
+ if child is None:
370
+ continue
371
+ if hasattr(child, "__iter__") and not isinstance(child, str):
372
+ for c in child:
373
+ if hasattr(c, "__class__") and hasattr(c, "line"):
374
+ _walk(c)
375
+ elif hasattr(child, "line"):
376
+ _walk(child)
377
+
378
+ if hasattr(tree, "body"):
379
+ for stmt in tree.body:
380
+ _walk(stmt)
381
+
382
+ if not results:
383
+ print(f"[InScript infer-types] No let/const declarations found in '{path}'")
384
+ return 0
385
+
386
+ print(f"[InScript infer-types] Inferred types in '{path}':\n")
387
+ for line, kw, name, typ in results:
388
+ print(f" Line {line:3d}: {kw} {name:<20s} → {typ}")
389
+ print()
390
+ return 0
391
+
392
+
318
393
  def _migrate_files(path: str) -> int:
319
394
  """v1.7.4: Auto-migrate deprecated InScript syntax in-place."""
320
395
  import re, os
@@ -1573,6 +1648,8 @@ Examples:
1573
1648
  parser.add_argument("--check", action="store_true", help="Type-check only, don't run")
1574
1649
  parser.add_argument("--check-all", metavar="DIR",
1575
1650
  help="v1.6.0: Check all .ins files in DIR recursively, exit 1 if any errors")
1651
+ parser.add_argument("--infer-types", metavar="FILE",
1652
+ help="v1.9.8: Print inferred type for every let/const declaration in FILE")
1576
1653
  parser.add_argument("--migrate", metavar="DIR_OR_FILE",
1577
1654
  help="v1.7.4: Auto-migrate deprecated syntax (null→nil, div→//)")
1578
1655
  parser.add_argument("--compat", metavar="DIR_OR_FILE",
@@ -1754,6 +1831,8 @@ Examples:
1754
1831
  return _fmt_all_files(args.fmt_all)
1755
1832
  if getattr(args, 'migrate', None):
1756
1833
  return _migrate_files(args.migrate)
1834
+ if getattr(args, 'infer_types', None):
1835
+ return _infer_types_file(args.infer_types)
1757
1836
  if getattr(args, 'compat', None):
1758
1837
  return _compat_files(args.compat)
1759
1838
  if getattr(args, 'init', None) is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.9.6
3
+ Version: 1.9.8
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
@@ -5,14 +5,15 @@
5
5
  # After Lexer → Parser → Analyzer pass, this is what runs InScript programs.
6
6
 
7
7
  from __future__ import annotations
8
- import math, random, time
8
+ import math, random, time, asyncio
9
9
  from typing import Any, Dict, List, Optional
10
10
 
11
11
  from ast_nodes import *
12
12
  from environment import Environment
13
13
  from stdlib_values import (
14
14
  Vec2, Vec3, Color, Rect,
15
- InScriptFunction, InScriptInstance, InScriptRange, InScriptGenerator
15
+ InScriptFunction, InScriptInstance, InScriptRange, InScriptGenerator,
16
+ InScriptCoroutine,
16
17
  )
17
18
  import stdlib as _stdlib # loads all built-in modules
18
19
  from errors import (
@@ -526,6 +527,8 @@ class Interpreter(Visitor):
526
527
  # v1.4.0: preserve generic metadata for constraint checking at call time
527
528
  fn.type_params = getattr(node, 'type_params', [])
528
529
  fn.constraints = getattr(node, 'constraints', {})
530
+ # v1.9.7: mark async functions
531
+ fn.is_async = getattr(node, 'is_async', False)
529
532
  self._env.define(node.name, fn)
530
533
  return fn
531
534
 
@@ -2319,6 +2322,33 @@ class Interpreter(Visitor):
2319
2322
 
2320
2323
  _bind_args(call_env, arg_vals, arg_names)
2321
2324
 
2325
+ # v1.9.7: async fn — wrap body execution in a Python coroutine
2326
+ if getattr(fn, 'is_async', False):
2327
+ async def _async_body():
2328
+ nonlocal result
2329
+ prev_fn2 = self._current_fn
2330
+ prev_def2 = getattr(self, '_deferred', None)
2331
+ self._deferred = []
2332
+ self._current_fn = fn
2333
+ try:
2334
+ for stmt in fn.body.body:
2335
+ self.visit(stmt)
2336
+ except ReturnSignal as r:
2337
+ result = r.value
2338
+ except PropagateSignal as sig:
2339
+ result = sig.err_val
2340
+ finally:
2341
+ for deferred_expr in reversed(self._deferred):
2342
+ try: self.visit(deferred_expr)
2343
+ except Exception: pass
2344
+ self._deferred = prev_def2 if prev_def2 is not None else []
2345
+ self._current_fn = prev_fn2
2346
+ self._env = prev_env
2347
+ self._call_depth -= 1
2348
+ self._call_stack.pop()
2349
+ return result
2350
+ return InScriptCoroutine(_async_body(), fn_name=fn.name or "<async>")
2351
+
2322
2352
  # v1.3.0: TCO trampoline — self-recursive tail calls loop instead of recurse
2323
2353
  result = None
2324
2354
  prev_fn = self._current_fn
@@ -2443,7 +2473,24 @@ class Interpreter(Visitor):
2443
2473
  return InScriptRange(start, end, inclusive=node.inclusive)
2444
2474
 
2445
2475
  def visit_AwaitExpr(self, node: AwaitExpr) -> Any:
2446
- return self.visit(node.expr) # synchronous in Phase 4
2476
+ """
2477
+ v1.9.7: True async/await.
2478
+ - InScriptCoroutine → drive via Python coroutine protocol (sync driver).
2479
+ - Plain value → passthrough (backwards compatible).
2480
+
2481
+ We use a synchronous driver (coro.send(None)) rather than asyncio.run()
2482
+ because InScript coroutine bodies contain no real Python await points —
2483
+ they are synchronous interpreter execution wrapped in `async def`.
2484
+ This also safely handles nested async calls without event-loop conflicts.
2485
+ """
2486
+ val = self.visit(node.expr)
2487
+ if isinstance(val, InScriptCoroutine):
2488
+ try:
2489
+ val.coro.send(None)
2490
+ return None # coroutine yielded unexpectedly — shouldn't happen
2491
+ except StopIteration as e:
2492
+ return e.value
2493
+ return val # plain value: passthrough
2447
2494
 
2448
2495
  def visit_SpawnExpr(self, node: SpawnExpr) -> Any:
2449
2496
  return None # Phase 6 (ECS)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "inscript-lang"
7
- version = "1.9.6"
7
+ version = "1.9.8"
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.9.6"
43
+ VERSION = "1.9.8"
44
44
 
45
45
  # ── ANSI colours ──────────────────────────────────────────────────────────────
46
46
  def _c(code, text):
@@ -220,7 +220,7 @@ class Rect:
220
220
 
221
221
  class InScriptFunction:
222
222
  """A user-defined function or lambda at runtime."""
223
- def __init__(self, name, params, body, closure, is_native=False, native_fn=None, return_type=None):
223
+ def __init__(self, name, params, body, closure, is_native=False, native_fn=None, return_type=None, is_async=False):
224
224
  self.name = name
225
225
  self.params = params # List[Param] AST nodes
226
226
  self.body = body # BlockStmt AST node
@@ -228,6 +228,7 @@ class InScriptFunction:
228
228
  self.is_native = is_native
229
229
  self.native_fn = native_fn # Python callable for native fns
230
230
  self.return_type = return_type # Optional type annotation
231
+ self.is_async = is_async # v1.9.7: true for `async fn`
231
232
  self._interp = None # Set by interpreter so stdlib can call us
232
233
 
233
234
  def __repr__(self): return f"<fn {self.name}>"
@@ -239,6 +240,21 @@ class InScriptFunction:
239
240
  raise TypeError(f"InScriptFunction '{self.name}' cannot be called without an interpreter context")
240
241
 
241
242
 
243
+ class InScriptCoroutine:
244
+ """
245
+ v1.9.7: Wraps the result of calling an `async fn`.
246
+ Holds a Python coroutine that runs the function body.
247
+ Driven to completion by `await` via asyncio.
248
+ """
249
+ def __init__(self, coro, fn_name: str = "<async>"):
250
+ self.coro = coro # Python coroutine object
251
+ self.fn_name = fn_name # for repr / error messages
252
+
253
+ def __repr__(self):
254
+ return f"<coroutine {self.fn_name}>"
255
+
256
+
257
+
242
258
  class InScriptInstance:
243
259
  """An instance of a user-defined struct at runtime."""
244
260
  def __init__(self, struct_name: str, fields: Dict[str, Any]):
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