inscript-lang 1.8.3__tar.gz → 1.8.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.
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/PKG-INFO +1 -1
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/analyzer.py +291 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript.py +1 -1
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/pyproject.toml +1 -1
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/repl.py +1 -1
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/README.md +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/ast_nodes.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/compiler.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/environment.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/errors.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript_fmt.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/inscript_test.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/interpreter.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/lexer.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/parser.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/pygame_backend.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/setup.cfg +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/setup.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/stdlib.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/stdlib_extended.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/stdlib_extended_2.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/stdlib_game.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/stdlib_values.py +0 -0
- {inscript_lang-1.8.3 → inscript_lang-1.8.4}/vm.py +0 -0
|
@@ -635,6 +635,17 @@ class Analyzer(Visitor):
|
|
|
635
635
|
def visit_FunctionDecl(self, node: FunctionDecl) -> InScriptType:
|
|
636
636
|
ret_type = self._resolve_type_ann(node.return_type)
|
|
637
637
|
|
|
638
|
+
# v1.8.4: return type inference — if no annotation and body has a single
|
|
639
|
+
# unambiguous return type, infer it automatically
|
|
640
|
+
if node.return_type is None and ret_type == T_ANY and node.body:
|
|
641
|
+
inferred = self._infer_fn_return_type(node)
|
|
642
|
+
if inferred not in (T_ANY, T_VOID, T_NULL):
|
|
643
|
+
ret_type = inferred
|
|
644
|
+
# Update the hoisted symbol's type to the inferred one
|
|
645
|
+
existing = self._scope.lookup(node.name)
|
|
646
|
+
if existing and existing.kind == "fn":
|
|
647
|
+
existing.type_ = ret_type
|
|
648
|
+
|
|
638
649
|
# Register in current scope (if not already hoisted)
|
|
639
650
|
if not self._scope.lookup_local(node.name):
|
|
640
651
|
self._define(Symbol(
|
|
@@ -1290,8 +1301,288 @@ class Analyzer(Visitor):
|
|
|
1290
1301
|
return sym.type_
|
|
1291
1302
|
|
|
1292
1303
|
if isinstance(node.callee, GetAttrExpr):
|
|
1304
|
+
# v1.8.4: infer return types of array method chains
|
|
1305
|
+
obj_type = self.visit(node.callee.obj)
|
|
1306
|
+
# v1.8.2 literal_type("hello") → dispatch as T_STRING
|
|
1307
|
+
if is_literal_type(obj_type):
|
|
1308
|
+
obj_type = T_STRING
|
|
1309
|
+
method = node.callee.attr
|
|
1310
|
+
return self._infer_method_call_type(obj_type, method, node)
|
|
1311
|
+
|
|
1312
|
+
return T_ANY
|
|
1313
|
+
|
|
1314
|
+
def _infer_method_call_type(self, obj_type: InScriptType,
|
|
1315
|
+
method: str, call_node) -> InScriptType:
|
|
1316
|
+
"""
|
|
1317
|
+
v1.8.4: Infer the return type of a method call based on the receiver's type.
|
|
1318
|
+
|
|
1319
|
+
Array methods:
|
|
1320
|
+
.map(fn) → Array<T> where T = inferred return type of fn
|
|
1321
|
+
.filter(fn) → Array<elem> (same element type, just fewer items)
|
|
1322
|
+
.flatMap(fn) → Array<T>
|
|
1323
|
+
.take(n) → Array<elem>
|
|
1324
|
+
.skip(n) → Array<elem>
|
|
1325
|
+
.slice(a,b) → Array<elem>
|
|
1326
|
+
.sorted() → Array<elem>
|
|
1327
|
+
.reversed() → Array<elem>
|
|
1328
|
+
.unique() → Array<elem>
|
|
1329
|
+
.len() → int
|
|
1330
|
+
.count() → int
|
|
1331
|
+
.sum() → int | float
|
|
1332
|
+
.first() → elem? (optional)
|
|
1333
|
+
.last() → elem?
|
|
1334
|
+
.find(fn) → elem?
|
|
1335
|
+
.any(fn) → bool
|
|
1336
|
+
.all(fn) → bool
|
|
1337
|
+
.reduce(fn,i) → any (can't infer accumulator type without annotation)
|
|
1338
|
+
.join(sep) → string
|
|
1339
|
+
.push(v) → void
|
|
1340
|
+
.pop() → elem?
|
|
1341
|
+
.contains(v) → bool
|
|
1342
|
+
.index_of(v) → int?
|
|
1343
|
+
|
|
1344
|
+
String methods:
|
|
1345
|
+
.split(sep) → Array<string>
|
|
1346
|
+
.trim() → string
|
|
1347
|
+
.upper() → string
|
|
1348
|
+
.lower() → string
|
|
1349
|
+
.replace(...) → string
|
|
1350
|
+
.starts_with()→ bool
|
|
1351
|
+
.ends_with() → bool
|
|
1352
|
+
.contains() → bool
|
|
1353
|
+
.len() → int
|
|
1354
|
+
.chars() → Array<string>
|
|
1355
|
+
"""
|
|
1356
|
+
# ── Array method inference ────────────────────────────────────────────
|
|
1357
|
+
if obj_type.name == "Array":
|
|
1358
|
+
elem = obj_type.params[0] if obj_type.params else T_ANY
|
|
1359
|
+
|
|
1360
|
+
# Methods that return Array<same-elem>
|
|
1361
|
+
if method in ("filter", "take", "skip", "slice", "sorted",
|
|
1362
|
+
"reversed", "unique", "shuffle", "concat"):
|
|
1363
|
+
return array_type(elem)
|
|
1364
|
+
|
|
1365
|
+
# .map(fn) / .flatMap(fn) → infer fn return type
|
|
1366
|
+
if method in ("map", "flatMap"):
|
|
1367
|
+
fn_ret = self._infer_fn_arg_return(call_node, arg_index=0, param_type=elem)
|
|
1368
|
+
inner = fn_ret if fn_ret != T_ANY else T_ANY
|
|
1369
|
+
if method == "flatMap" and inner.name == "Array":
|
|
1370
|
+
inner = inner.params[0] if inner.params else T_ANY
|
|
1371
|
+
return array_type(inner)
|
|
1372
|
+
|
|
1373
|
+
# Scalar reductions
|
|
1374
|
+
if method in ("len", "count", "index_of"):
|
|
1375
|
+
return T_INT
|
|
1376
|
+
if method == "sum":
|
|
1377
|
+
return elem if elem in (T_INT, T_FLOAT) else T_INT
|
|
1378
|
+
if method in ("any", "all", "contains", "is_empty"):
|
|
1379
|
+
return T_BOOL
|
|
1380
|
+
if method == "join":
|
|
1381
|
+
return T_STRING
|
|
1382
|
+
if method in ("push", "append", "extend", "clear", "insert",
|
|
1383
|
+
"remove", "remove_at"):
|
|
1384
|
+
return T_VOID
|
|
1385
|
+
|
|
1386
|
+
# Optional-returning methods
|
|
1387
|
+
if method in ("first", "last", "pop", "find", "get"):
|
|
1388
|
+
return optional_type(elem)
|
|
1389
|
+
|
|
1390
|
+
# .reduce → T_ANY (accumulator type unknown without annotation)
|
|
1391
|
+
if method == "reduce":
|
|
1392
|
+
return T_ANY
|
|
1393
|
+
|
|
1394
|
+
# .each(fn) → void (side-effect traversal)
|
|
1395
|
+
if method in ("each", "for_each"):
|
|
1396
|
+
return T_VOID
|
|
1397
|
+
|
|
1398
|
+
# ── String method inference ───────────────────────────────────────────
|
|
1399
|
+
if obj_type == T_STRING:
|
|
1400
|
+
if method in ("trim", "upper", "lower", "replace", "strip",
|
|
1401
|
+
"lstrip", "rstrip", "pad_left", "pad_right",
|
|
1402
|
+
"repeat", "reverse", "slice"):
|
|
1403
|
+
return T_STRING
|
|
1404
|
+
if method in ("len", "count", "index_of", "find"):
|
|
1405
|
+
return T_INT
|
|
1406
|
+
if method in ("starts_with", "ends_with", "contains", "is_empty",
|
|
1407
|
+
"matches"):
|
|
1408
|
+
return T_BOOL
|
|
1409
|
+
if method == "split":
|
|
1410
|
+
return array_type(T_STRING)
|
|
1411
|
+
if method == "chars":
|
|
1412
|
+
return array_type(T_STRING)
|
|
1413
|
+
if method == "to_int":
|
|
1414
|
+
return optional_type(T_INT)
|
|
1415
|
+
if method == "to_float":
|
|
1416
|
+
return optional_type(T_FLOAT)
|
|
1417
|
+
|
|
1418
|
+
# ── Int / Float methods ───────────────────────────────────────────────
|
|
1419
|
+
if obj_type in (T_INT, T_FLOAT):
|
|
1420
|
+
if method in ("abs", "floor", "ceil", "round", "sqrt",
|
|
1421
|
+
"clamp", "min", "max"):
|
|
1422
|
+
return obj_type
|
|
1423
|
+
if method == "to_string":
|
|
1424
|
+
return T_STRING
|
|
1425
|
+
|
|
1426
|
+
# ── Struct method return type ─────────────────────────────────────────
|
|
1427
|
+
if obj_type.name in self._struct_defs:
|
|
1428
|
+
struct = self._struct_defs[obj_type.name]
|
|
1429
|
+
for m in (struct.methods or []):
|
|
1430
|
+
if m.name == method:
|
|
1431
|
+
return self._resolve_type_ann(m.return_type)
|
|
1432
|
+
|
|
1433
|
+
return T_ANY
|
|
1434
|
+
|
|
1435
|
+
def _infer_fn_return_type(self, node) -> InScriptType:
|
|
1436
|
+
"""
|
|
1437
|
+
v1.8.4: Infer a function's return type from its body when no annotation exists.
|
|
1438
|
+
Only infers when ALL return paths agree on the same concrete type.
|
|
1439
|
+
Falls back to T_ANY if ambiguous.
|
|
1440
|
+
NOTE: errors are suppressed — inference is best-effort, never raises.
|
|
1441
|
+
"""
|
|
1442
|
+
from ast_nodes import ReturnStmt, BlockStmt
|
|
1443
|
+
|
|
1444
|
+
if not node.body:
|
|
1445
|
+
return T_VOID
|
|
1446
|
+
|
|
1447
|
+
# Stash and suppress the real error list — inference must never pollute it
|
|
1448
|
+
saved_errors = self._errors
|
|
1449
|
+
self._errors = []
|
|
1450
|
+
|
|
1451
|
+
try:
|
|
1452
|
+
# Push a temporary scope with params typed per their annotations
|
|
1453
|
+
self._push_scope("infer-fn")
|
|
1454
|
+
for p in (node.params or []):
|
|
1455
|
+
p_type = self._resolve_type_ann(getattr(p, 'type_ann', None))
|
|
1456
|
+
pname = getattr(p, 'name', None)
|
|
1457
|
+
if pname:
|
|
1458
|
+
self._scope.symbols[pname] = Symbol(
|
|
1459
|
+
pname, p_type, kind="var",
|
|
1460
|
+
line=getattr(p, 'line', 0), col=getattr(p, 'col', 0)
|
|
1461
|
+
)
|
|
1462
|
+
self._scope.symbols[pname].used = True
|
|
1463
|
+
|
|
1464
|
+
# Collect return types from first-level stmts + if/else branches
|
|
1465
|
+
types_seen = set()
|
|
1466
|
+
stmts = node.body.body if isinstance(node.body, BlockStmt) else [node.body]
|
|
1467
|
+
for stmt in stmts:
|
|
1468
|
+
if isinstance(stmt, ReturnStmt) and stmt.value is not None:
|
|
1469
|
+
try:
|
|
1470
|
+
t = self.visit(stmt.value)
|
|
1471
|
+
if is_literal_type(t): t = T_STRING
|
|
1472
|
+
types_seen.add(t)
|
|
1473
|
+
except Exception:
|
|
1474
|
+
types_seen.add(T_ANY)
|
|
1475
|
+
for branch_attr in ('then_branch', 'else_branch'):
|
|
1476
|
+
branch = getattr(stmt, branch_attr, None)
|
|
1477
|
+
if branch is None:
|
|
1478
|
+
continue
|
|
1479
|
+
inner = getattr(branch, 'body', [branch])
|
|
1480
|
+
if not isinstance(inner, list):
|
|
1481
|
+
inner = [inner]
|
|
1482
|
+
for s in inner:
|
|
1483
|
+
if isinstance(s, ReturnStmt) and s.value is not None:
|
|
1484
|
+
try:
|
|
1485
|
+
t = self.visit(s.value)
|
|
1486
|
+
if is_literal_type(t): t = T_STRING
|
|
1487
|
+
types_seen.add(t)
|
|
1488
|
+
except Exception:
|
|
1489
|
+
types_seen.add(T_ANY)
|
|
1490
|
+
|
|
1491
|
+
self._pop_scope()
|
|
1492
|
+
|
|
1493
|
+
if not types_seen:
|
|
1494
|
+
return T_VOID
|
|
1495
|
+
non_any = {t for t in types_seen if t != T_ANY}
|
|
1496
|
+
if len(non_any) == 1:
|
|
1497
|
+
return next(iter(non_any))
|
|
1498
|
+
if non_any == {T_INT, T_FLOAT}:
|
|
1499
|
+
return T_FLOAT
|
|
1500
|
+
return T_ANY
|
|
1501
|
+
|
|
1502
|
+
except Exception:
|
|
1503
|
+
return T_ANY
|
|
1504
|
+
finally:
|
|
1505
|
+
# Always restore the real error list
|
|
1506
|
+
self._errors = saved_errors
|
|
1507
|
+
|
|
1508
|
+
def _infer_fn_arg_return(self, call_node, arg_index: int,
|
|
1509
|
+
param_type: InScriptType) -> InScriptType:
|
|
1510
|
+
"""
|
|
1511
|
+
v1.8.4: Given a CallExpr and an argument position that expects a fn,
|
|
1512
|
+
try to infer that fn's return type by visiting its body with a temporary
|
|
1513
|
+
scope that binds the param to param_type.
|
|
1514
|
+
Returns T_ANY if inference fails.
|
|
1515
|
+
"""
|
|
1516
|
+
from ast_nodes import FunctionDecl, LambdaExpr
|
|
1517
|
+
if arg_index >= len(call_node.args):
|
|
1518
|
+
return T_ANY
|
|
1519
|
+
fn_arg = call_node.args[arg_index].value
|
|
1520
|
+
|
|
1521
|
+
# Handle both lambda `fn(x) { return x * 2 }` and named refs
|
|
1522
|
+
params = None; body = None
|
|
1523
|
+
if isinstance(fn_arg, FunctionDecl) or isinstance(fn_arg, LambdaExpr):
|
|
1524
|
+
params = getattr(fn_arg, 'params', [])
|
|
1525
|
+
body = getattr(fn_arg, 'body', None)
|
|
1526
|
+
else:
|
|
1293
1527
|
return T_ANY
|
|
1294
1528
|
|
|
1529
|
+
# Stash and suppress the real error list
|
|
1530
|
+
saved_errors = self._errors
|
|
1531
|
+
self._errors = []
|
|
1532
|
+
|
|
1533
|
+
try:
|
|
1534
|
+
# Push a temporary scope, bind first param to elem type
|
|
1535
|
+
self._push_scope("infer")
|
|
1536
|
+
if params:
|
|
1537
|
+
p0 = params[0]
|
|
1538
|
+
p_name = getattr(p0, 'name', None)
|
|
1539
|
+
if p_name:
|
|
1540
|
+
self._scope.symbols[p_name] = Symbol(
|
|
1541
|
+
p_name, param_type, kind="var",
|
|
1542
|
+
line=getattr(p0, 'line', 0), col=getattr(p0, 'col', 0)
|
|
1543
|
+
)
|
|
1544
|
+
self._scope.symbols[p_name].used = True
|
|
1545
|
+
|
|
1546
|
+
inferred = T_ANY
|
|
1547
|
+
if body is not None:
|
|
1548
|
+
inferred = self._infer_block_return_type(body)
|
|
1549
|
+
|
|
1550
|
+
self._pop_scope()
|
|
1551
|
+
return inferred
|
|
1552
|
+
except Exception:
|
|
1553
|
+
return T_ANY
|
|
1554
|
+
finally:
|
|
1555
|
+
self._errors = saved_errors
|
|
1556
|
+
|
|
1557
|
+
def _infer_block_return_type(self, block) -> InScriptType:
|
|
1558
|
+
"""
|
|
1559
|
+
v1.8.4: Walk a block/expression to find the first return statement's type.
|
|
1560
|
+
Also handles single-expression lambdas.
|
|
1561
|
+
"""
|
|
1562
|
+
from ast_nodes import BlockStmt, ReturnStmt
|
|
1563
|
+
stmts = []
|
|
1564
|
+
if isinstance(block, BlockStmt):
|
|
1565
|
+
stmts = block.body
|
|
1566
|
+
elif hasattr(block, '__iter__'):
|
|
1567
|
+
stmts = list(block)
|
|
1568
|
+
else:
|
|
1569
|
+
# Single expression (arrow lambda)
|
|
1570
|
+
try:
|
|
1571
|
+
return self.visit(block)
|
|
1572
|
+
except Exception:
|
|
1573
|
+
return T_ANY
|
|
1574
|
+
|
|
1575
|
+
for stmt in stmts:
|
|
1576
|
+
if isinstance(stmt, ReturnStmt) and stmt.value is not None:
|
|
1577
|
+
try:
|
|
1578
|
+
return self.visit(stmt.value)
|
|
1579
|
+
except Exception:
|
|
1580
|
+
return T_ANY
|
|
1581
|
+
# Recurse into if/while bodies for early returns
|
|
1582
|
+
if hasattr(stmt, 'then_branch'):
|
|
1583
|
+
t = self._infer_block_return_type(stmt.then_branch)
|
|
1584
|
+
if t != T_ANY:
|
|
1585
|
+
return t
|
|
1295
1586
|
return T_ANY
|
|
1296
1587
|
|
|
1297
1588
|
def visit_NamespaceAccessExpr(self, node: NamespaceAccessExpr) -> InScriptType:
|
|
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
|
|
|
24
24
|
SemanticError, InScriptRuntimeError,
|
|
25
25
|
MultiError, InScriptWarning)
|
|
26
26
|
|
|
27
|
-
VERSION = "1.8.
|
|
27
|
+
VERSION = "1.8.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"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "inscript-lang"
|
|
7
|
-
version = "1.8.
|
|
7
|
+
version = "1.8.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.8.
|
|
43
|
+
VERSION = "1.8.4"
|
|
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
|
|
File without changes
|