inscript-lang 1.7.4__tar.gz → 1.8.1__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.7.4 → inscript_lang-1.8.1}/PKG-INFO +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/analyzer.py +115 -3
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript.py +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/pyproject.toml +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/repl.py +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/README.md +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/ast_nodes.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/compiler.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/environment.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/errors.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_fmt.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_test.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/interpreter.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/lexer.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/parser.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/pygame_backend.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/setup.cfg +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/setup.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib_extended.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib_extended_2.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib_game.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib_values.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.1}/vm.py +0 -0
|
@@ -87,6 +87,31 @@ def array_type(elem: InScriptType) -> InScriptType:
|
|
|
87
87
|
def dict_type(key: InScriptType, val: InScriptType) -> InScriptType:
|
|
88
88
|
return InScriptType("Dict", [key, val])
|
|
89
89
|
|
|
90
|
+
def union_type(*members: InScriptType) -> InScriptType:
|
|
91
|
+
"""v1.8.1: Create a Union type from two or more member types.
|
|
92
|
+
Flattens nested unions; deduplicates; single-member unions collapse."""
|
|
93
|
+
flat = []
|
|
94
|
+
seen = set()
|
|
95
|
+
for m in members:
|
|
96
|
+
if m.name == "Union":
|
|
97
|
+
for p in m.params:
|
|
98
|
+
if p not in seen:
|
|
99
|
+
flat.append(p); seen.add(p)
|
|
100
|
+
else:
|
|
101
|
+
if m not in seen:
|
|
102
|
+
flat.append(m); seen.add(m)
|
|
103
|
+
if len(flat) == 1:
|
|
104
|
+
return flat[0]
|
|
105
|
+
return InScriptType("Union", flat)
|
|
106
|
+
|
|
107
|
+
def optional_type(inner: InScriptType) -> InScriptType:
|
|
108
|
+
"""v1.8.1: `T?` is sugar for `T | nil`."""
|
|
109
|
+
return union_type(inner, T_NULL)
|
|
110
|
+
|
|
111
|
+
def union_members(t: InScriptType) -> list:
|
|
112
|
+
"""v1.8.1: Return list of member types (works on non-unions too)."""
|
|
113
|
+
return t.params if t.name == "Union" else [t]
|
|
114
|
+
|
|
90
115
|
def is_numeric(t: InScriptType) -> bool:
|
|
91
116
|
return t in (T_INT, T_FLOAT)
|
|
92
117
|
|
|
@@ -104,8 +129,17 @@ def types_compatible(expected: InScriptType, got: InScriptType) -> bool:
|
|
|
104
129
|
if expected == got: return True
|
|
105
130
|
# int can widen to float
|
|
106
131
|
if expected == T_FLOAT and got == T_INT: return True
|
|
107
|
-
# null can be assigned to any
|
|
108
|
-
if got == T_NULL:
|
|
132
|
+
# null/nil can be assigned to any nullable or union containing nil
|
|
133
|
+
if got == T_NULL:
|
|
134
|
+
if expected == T_NULL: return True
|
|
135
|
+
if expected.name == "Union" and T_NULL in expected.params: return True
|
|
136
|
+
return False
|
|
137
|
+
# v1.8.1: if expected is a Union, got must be a member (or compatible with one)
|
|
138
|
+
if expected.name == "Union":
|
|
139
|
+
return any(types_compatible(m, got) for m in expected.params)
|
|
140
|
+
# v1.8.1: if got is a Union, every member must be compatible with expected
|
|
141
|
+
if got.name == "Union":
|
|
142
|
+
return all(types_compatible(expected, m) for m in got.params)
|
|
109
143
|
# Array<X> compatible with Array<any> and vice versa
|
|
110
144
|
if expected.name == "Array" and got.name == "Array": return True
|
|
111
145
|
# Dict<K,V> compatible with Dict<any,any>
|
|
@@ -291,6 +325,18 @@ class Analyzer(Visitor):
|
|
|
291
325
|
v = self._resolve_type_ann(ann.generics[0]) if ann.generics else T_ANY
|
|
292
326
|
return dict_type(k, v)
|
|
293
327
|
|
|
328
|
+
# v1.8.1: `T?` — nullable shorthand, parsed as TypeAnnotation(name="Optional",
|
|
329
|
+
# is_nullable=True, generics=[T])
|
|
330
|
+
if ann.is_nullable or ann.name == "Optional":
|
|
331
|
+
inner = self._resolve_type_ann(ann.generics[0]) if ann.generics else T_ANY
|
|
332
|
+
return optional_type(inner)
|
|
333
|
+
|
|
334
|
+
# v1.8.1: `A | B | C` — union type, parsed as TypeAnnotation(name="Union",
|
|
335
|
+
# generics=[A, B, C])
|
|
336
|
+
if ann.name == "Union":
|
|
337
|
+
members = [self._resolve_type_ann(g) for g in ann.generics]
|
|
338
|
+
return union_type(*members)
|
|
339
|
+
|
|
294
340
|
if ann.name in BUILTIN_TYPES:
|
|
295
341
|
return BUILTIN_TYPES[ann.name]
|
|
296
342
|
|
|
@@ -727,6 +773,36 @@ class Analyzer(Visitor):
|
|
|
727
773
|
self._error("'continue' outside of loop", node.line, node.col)
|
|
728
774
|
return T_VOID
|
|
729
775
|
|
|
776
|
+
def _extract_typeof_narrowing(self, condition) -> tuple:
|
|
777
|
+
"""v1.8.1: If condition is `typeof(x) == "T"` return (var_name, narrowed_type).
|
|
778
|
+
Returns (None, None) if the condition is not that pattern."""
|
|
779
|
+
if not (hasattr(condition, 'op') and condition.op == "=="):
|
|
780
|
+
return (None, None)
|
|
781
|
+
left, right = condition.left, condition.right
|
|
782
|
+
# Support both sides for the string literal
|
|
783
|
+
if (hasattr(right, 'args') and isinstance(getattr(right, 'callee', None), object)
|
|
784
|
+
and hasattr(left, 'value') and isinstance(left.value, str)):
|
|
785
|
+
left, right = right, left
|
|
786
|
+
# left must be a CallExpr(callee=IdentExpr("typeof"|"type"), args=[IdentExpr])
|
|
787
|
+
if not hasattr(left, 'callee') or not hasattr(left, 'args'):
|
|
788
|
+
return (None, None)
|
|
789
|
+
callee_name = getattr(left.callee, 'name', None)
|
|
790
|
+
if callee_name not in ("typeof", "type"):
|
|
791
|
+
return (None, None)
|
|
792
|
+
if not left.args:
|
|
793
|
+
return (None, None)
|
|
794
|
+
arg0 = left.args[0]
|
|
795
|
+
arg_val = getattr(arg0, 'value', arg0) # Argument node wraps value
|
|
796
|
+
var_name = getattr(arg_val, 'name', None)
|
|
797
|
+
if var_name is None:
|
|
798
|
+
return (None, None)
|
|
799
|
+
# right must be a StringLiteralExpr
|
|
800
|
+
if not hasattr(right, 'value') or not isinstance(right.value, str):
|
|
801
|
+
return (None, None)
|
|
802
|
+
type_name = right.value
|
|
803
|
+
narrow_to = BUILTIN_TYPES.get(type_name, InScriptType(type_name))
|
|
804
|
+
return (var_name, narrow_to)
|
|
805
|
+
|
|
730
806
|
def visit_IfStmt(self, node: IfStmt) -> InScriptType:
|
|
731
807
|
cond_type = self.visit(node.condition)
|
|
732
808
|
if cond_type not in (T_BOOL, T_ANY):
|
|
@@ -735,7 +811,29 @@ class Analyzer(Visitor):
|
|
|
735
811
|
f"non-bool conditions will be truthy/falsy at runtime",
|
|
736
812
|
node.line
|
|
737
813
|
)
|
|
738
|
-
|
|
814
|
+
|
|
815
|
+
# v1.8.1: union narrowing — `if typeof(x) == "int" { }` narrows x to int
|
|
816
|
+
var_name, narrow_to = self._extract_typeof_narrowing(node.condition)
|
|
817
|
+
if var_name and narrow_to:
|
|
818
|
+
# Push a scope, inject narrowed binding, then visit the BODY STATEMENTS
|
|
819
|
+
# directly so the narrowed type is visible for the entire then-branch.
|
|
820
|
+
self._push_scope("block")
|
|
821
|
+
sym = self._scope.parent.lookup(var_name) if self._scope.parent else None
|
|
822
|
+
if sym:
|
|
823
|
+
narrowed_sym = Symbol(var_name, narrow_to, sym.kind,
|
|
824
|
+
line=sym.line, col=sym.col)
|
|
825
|
+
narrowed_sym.used = True
|
|
826
|
+
self._define(narrowed_sym)
|
|
827
|
+
# Visit body stmts without letting visit_BlockStmt push another scope
|
|
828
|
+
from ast_nodes import BlockStmt
|
|
829
|
+
body = node.then_branch
|
|
830
|
+
stmts = body.body if isinstance(body, BlockStmt) else [body]
|
|
831
|
+
for stmt in stmts:
|
|
832
|
+
self.visit(stmt)
|
|
833
|
+
self._pop_scope()
|
|
834
|
+
else:
|
|
835
|
+
self.visit(node.then_branch)
|
|
836
|
+
|
|
739
837
|
if node.else_branch:
|
|
740
838
|
self.visit(node.else_branch)
|
|
741
839
|
return T_VOID
|
|
@@ -1024,6 +1122,20 @@ class Analyzer(Visitor):
|
|
|
1024
1122
|
f"{n_total} arg(s), got {n_args}",
|
|
1025
1123
|
node.line
|
|
1026
1124
|
)
|
|
1125
|
+
# v1.8.1: check argument types against declared param types
|
|
1126
|
+
for i, arg in enumerate(node.args):
|
|
1127
|
+
if i >= len(params):
|
|
1128
|
+
break
|
|
1129
|
+
p_type = self._resolve_type_ann(params[i].type_ann)
|
|
1130
|
+
if p_type == T_ANY:
|
|
1131
|
+
continue
|
|
1132
|
+
arg_type = self.visit(arg.value)
|
|
1133
|
+
if not types_compatible(p_type, arg_type):
|
|
1134
|
+
self._error(
|
|
1135
|
+
f"Argument {i+1} to '{node.callee.name}': "
|
|
1136
|
+
f"expected '{p_type}', got '{arg_type}'",
|
|
1137
|
+
getattr(arg.value, 'line', node.line)
|
|
1138
|
+
)
|
|
1027
1139
|
return self._resolve_type_ann(fn.return_type)
|
|
1028
1140
|
if sym:
|
|
1029
1141
|
if sym.kind == "struct":
|
|
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
|
|
|
24
24
|
SemanticError, InScriptRuntimeError,
|
|
25
25
|
MultiError, InScriptWarning)
|
|
26
26
|
|
|
27
|
-
VERSION = "1.
|
|
27
|
+
VERSION = "1.8.1"
|
|
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.
|
|
7
|
+
version = "1.8.1"
|
|
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.
|
|
43
|
+
VERSION = "1.8.1"
|
|
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
|