inscript-lang 1.9.6__tar.gz → 1.9.7__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-1.9.6 → inscript_lang-1.9.7}/PKG-INFO +1 -1
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/analyzer.py +29 -12
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript.py +1 -1
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/interpreter.py +50 -3
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/pyproject.toml +1 -1
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/repl.py +1 -1
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/stdlib_values.py +17 -1
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/README.md +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/ast_nodes.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/compiler.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/environment.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/errors.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript_fmt.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/inscript_test.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/lexer.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/parser.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/pygame_backend.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/setup.cfg +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/setup.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/stdlib.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/stdlib_extended.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/stdlib_extended_2.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/stdlib_game.py +0 -0
- {inscript_lang-1.9.6 → inscript_lang-1.9.7}/vm.py +0 -0
|
@@ -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
|
|
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
|
-
|
|
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_ =
|
|
677
|
+
existing.type_ = external_ret_type
|
|
661
678
|
|
|
662
|
-
# Register in current scope (
|
|
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,
|
|
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
|
-
|
|
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
|
|
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 '{
|
|
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 =
|
|
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
|
|
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."""
|
|
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
|
|
|
24
24
|
SemanticError, InScriptRuntimeError,
|
|
25
25
|
MultiError, InScriptWarning)
|
|
26
26
|
|
|
27
|
-
VERSION = "1.9.
|
|
27
|
+
VERSION = "1.9.7"
|
|
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"
|
|
@@ -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
|
-
|
|
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.
|
|
7
|
+
version = "1.9.7"
|
|
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.
|
|
43
|
+
VERSION = "1.9.7"
|
|
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
|
|
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
|