inscript-lang 1.7.4__tar.gz → 1.8.2__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.2}/PKG-INFO +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/analyzer.py +196 -7
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/ast_nodes.py +9 -3
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript.py +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/interpreter.py +7 -3
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/parser.py +40 -12
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/pyproject.toml +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/repl.py +1 -1
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/README.md +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/compiler.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/environment.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/errors.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript_fmt.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/inscript_test.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/lexer.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/pygame_backend.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/setup.cfg +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/setup.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/stdlib.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/stdlib_extended.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/stdlib_extended_2.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/stdlib_game.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/stdlib_values.py +0 -0
- {inscript_lang-1.7.4 → inscript_lang-1.8.2}/vm.py +0 -0
|
@@ -87,6 +87,45 @@ 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
|
+
|
|
115
|
+
def literal_type(value: str) -> InScriptType:
|
|
116
|
+
"""v1.8.2: A string-literal type like `"left"` or `"right"`."""
|
|
117
|
+
return InScriptType("__literal__", [value]) # params[0] is the string value
|
|
118
|
+
|
|
119
|
+
def fn_type(param_types: list, return_type: InScriptType) -> InScriptType:
|
|
120
|
+
"""v1.8.2: A function type `fn(int,string)->bool`."""
|
|
121
|
+
return InScriptType("__fn__", param_types + [return_type])
|
|
122
|
+
|
|
123
|
+
def is_literal_type(t: InScriptType) -> bool:
|
|
124
|
+
return t.name == "__literal__"
|
|
125
|
+
|
|
126
|
+
def literal_value(t: InScriptType) -> str:
|
|
127
|
+
return t.params[0] if t.params else ""
|
|
128
|
+
|
|
90
129
|
def is_numeric(t: InScriptType) -> bool:
|
|
91
130
|
return t in (T_INT, T_FLOAT)
|
|
92
131
|
|
|
@@ -104,8 +143,36 @@ def types_compatible(expected: InScriptType, got: InScriptType) -> bool:
|
|
|
104
143
|
if expected == got: return True
|
|
105
144
|
# int can widen to float
|
|
106
145
|
if expected == T_FLOAT and got == T_INT: return True
|
|
107
|
-
# null can be assigned to any
|
|
108
|
-
if got == T_NULL:
|
|
146
|
+
# null/nil can be assigned to any nullable or union containing nil
|
|
147
|
+
if got == T_NULL:
|
|
148
|
+
if expected == T_NULL: return True
|
|
149
|
+
if expected.name == "Union" and T_NULL in expected.params: return True
|
|
150
|
+
return False
|
|
151
|
+
# v1.8.1: if expected is a Union, got must be a member (or compatible with one)
|
|
152
|
+
if expected.name == "Union":
|
|
153
|
+
return any(types_compatible(m, got) for m in expected.params)
|
|
154
|
+
# v1.8.1: if got is a Union, every member must be compatible with expected
|
|
155
|
+
if got.name == "Union":
|
|
156
|
+
return all(types_compatible(expected, m) for m in got.params)
|
|
157
|
+
# v1.8.2: string literal type — `"left"` is compatible with string
|
|
158
|
+
if is_literal_type(expected):
|
|
159
|
+
# Expected a specific literal: got must be the same literal
|
|
160
|
+
if is_literal_type(got):
|
|
161
|
+
return literal_value(expected) == literal_value(got)
|
|
162
|
+
# A plain string variable can fill a literal slot (runtime check)
|
|
163
|
+
return got == T_STRING
|
|
164
|
+
if is_literal_type(got):
|
|
165
|
+
# A literal can always fill a plain string slot
|
|
166
|
+
if expected == T_STRING: return True
|
|
167
|
+
# A literal can fill a Union<string, ...> slot
|
|
168
|
+
if expected.name == "Union":
|
|
169
|
+
return any(types_compatible(m, got) for m in expected.params)
|
|
170
|
+
return False
|
|
171
|
+
# v1.8.2: fn types — compatible if same signature, or got is T_ANY/Function (lambda)
|
|
172
|
+
if expected.name == "__fn__":
|
|
173
|
+
if got == T_ANY: return True
|
|
174
|
+
if got.name in ("Function", "__fn__"): return True
|
|
175
|
+
return False
|
|
109
176
|
# Array<X> compatible with Array<any> and vice versa
|
|
110
177
|
if expected.name == "Array" and got.name == "Array": return True
|
|
111
178
|
# Dict<K,V> compatible with Dict<any,any>
|
|
@@ -199,6 +266,7 @@ class Analyzer(Visitor):
|
|
|
199
266
|
self._in_scene: bool = False
|
|
200
267
|
self._in_match_arm: bool = False
|
|
201
268
|
self._struct_defs: Dict[str, StructDecl] = {}
|
|
269
|
+
self._type_aliases: Dict[str, "InScriptType"] = {} # v1.8.2: registered aliases
|
|
202
270
|
|
|
203
271
|
# Pre-register built-in global functions
|
|
204
272
|
self._register_builtins()
|
|
@@ -291,9 +359,33 @@ class Analyzer(Visitor):
|
|
|
291
359
|
v = self._resolve_type_ann(ann.generics[0]) if ann.generics else T_ANY
|
|
292
360
|
return dict_type(k, v)
|
|
293
361
|
|
|
362
|
+
# v1.8.1: `T?` — nullable shorthand
|
|
363
|
+
if ann.is_nullable or ann.name == "Optional":
|
|
364
|
+
inner = self._resolve_type_ann(ann.generics[0]) if ann.generics else T_ANY
|
|
365
|
+
return optional_type(inner)
|
|
366
|
+
|
|
367
|
+
# v1.8.1: `A | B | C` — union type
|
|
368
|
+
if ann.name == "Union":
|
|
369
|
+
members = [self._resolve_type_ann(g) for g in ann.generics]
|
|
370
|
+
return union_type(*members)
|
|
371
|
+
|
|
372
|
+
# v1.8.2: string literal type — `"left"`
|
|
373
|
+
if ann.name == "__literal__":
|
|
374
|
+
return literal_type(ann.literal_value or "")
|
|
375
|
+
|
|
376
|
+
# v1.8.2: fn type — `fn(int) -> bool`
|
|
377
|
+
if ann.name == "__fn__":
|
|
378
|
+
params = [self._resolve_type_ann(p) for p in (ann.fn_params or [])]
|
|
379
|
+
ret = self._resolve_type_ann(ann.fn_return) if ann.fn_return else T_ANY
|
|
380
|
+
return fn_type(params, ret)
|
|
381
|
+
|
|
294
382
|
if ann.name in BUILTIN_TYPES:
|
|
295
383
|
return BUILTIN_TYPES[ann.name]
|
|
296
384
|
|
|
385
|
+
# v1.8.2: registered type aliases
|
|
386
|
+
if ann.name in self._type_aliases:
|
|
387
|
+
return self._type_aliases[ann.name]
|
|
388
|
+
|
|
297
389
|
# Check user-defined structs
|
|
298
390
|
if ann.name in self._struct_defs:
|
|
299
391
|
return InScriptType(ann.name)
|
|
@@ -428,7 +520,9 @@ class Analyzer(Visitor):
|
|
|
428
520
|
return self._scope.symbols
|
|
429
521
|
|
|
430
522
|
def _hoist_top_level(self, program: Program):
|
|
431
|
-
"""Register structs, scenes,
|
|
523
|
+
"""Register structs, scenes, top-level functions, and type aliases before checking bodies."""
|
|
524
|
+
from ast_nodes import TypeAliasDecl
|
|
525
|
+
# First sub-pass: register type aliases and structs (order-independent)
|
|
432
526
|
for node in program.body:
|
|
433
527
|
if isinstance(node, StructDecl):
|
|
434
528
|
self._struct_defs[node.name] = node
|
|
@@ -437,7 +531,21 @@ class Analyzer(Visitor):
|
|
|
437
531
|
kind="struct", struct_node=node,
|
|
438
532
|
line=node.line, col=node.col
|
|
439
533
|
)
|
|
440
|
-
elif isinstance(node,
|
|
534
|
+
elif isinstance(node, TypeAliasDecl):
|
|
535
|
+
# v1.8.2: register alias immediately so functions can use it
|
|
536
|
+
type_ann = getattr(node, 'type_ann', None)
|
|
537
|
+
if type_ann is not None:
|
|
538
|
+
resolved = self._resolve_type_ann(type_ann)
|
|
539
|
+
else:
|
|
540
|
+
resolved = BUILTIN_TYPES.get(node.target, InScriptType(node.target))
|
|
541
|
+
self._type_aliases[node.name] = resolved
|
|
542
|
+
self._scope.symbols[node.name] = Symbol(
|
|
543
|
+
node.name, resolved, kind="type",
|
|
544
|
+
line=node.line, col=node.col
|
|
545
|
+
)
|
|
546
|
+
# Second sub-pass: hoist functions, scenes, enums (can now reference aliases)
|
|
547
|
+
for node in program.body:
|
|
548
|
+
if isinstance(node, FunctionDecl):
|
|
441
549
|
ret = self._resolve_type_ann(node.return_type)
|
|
442
550
|
self._scope.symbols[node.name] = Symbol(
|
|
443
551
|
node.name, ret, kind="fn", fn_node=node,
|
|
@@ -451,7 +559,7 @@ class Analyzer(Visitor):
|
|
|
451
559
|
elif isinstance(node, EnumDecl):
|
|
452
560
|
self._scope.symbols[node.name] = Symbol(
|
|
453
561
|
node.name, InScriptType(node.name), kind="enum",
|
|
454
|
-
fn_node=node,
|
|
562
|
+
fn_node=node,
|
|
455
563
|
line=node.line, col=node.col
|
|
456
564
|
)
|
|
457
565
|
|
|
@@ -649,6 +757,19 @@ class Analyzer(Visitor):
|
|
|
649
757
|
)
|
|
650
758
|
return enum_type
|
|
651
759
|
|
|
760
|
+
def visit_TypeAliasDecl(self, node) -> InScriptType:
|
|
761
|
+
"""v1.8.2: Register a type alias so annotations can reference it by name."""
|
|
762
|
+
if hasattr(node, 'type_ann') and node.type_ann is not None:
|
|
763
|
+
resolved = self._resolve_type_ann(node.type_ann)
|
|
764
|
+
else:
|
|
765
|
+
# Legacy alias with only a target string
|
|
766
|
+
resolved = BUILTIN_TYPES.get(node.target, InScriptType(node.target))
|
|
767
|
+
self._type_aliases[node.name] = resolved
|
|
768
|
+
# Also register in scope so the name is "used" and not flagged undefined
|
|
769
|
+
self._scope.symbols[node.name] = Symbol(node.name, resolved, kind="type",
|
|
770
|
+
line=node.line, col=node.col)
|
|
771
|
+
return T_VOID
|
|
772
|
+
|
|
652
773
|
def visit_ImportDecl(self, node: ImportDecl) -> InScriptType:
|
|
653
774
|
"""Register imported symbols so the type checker knows about them."""
|
|
654
775
|
try:
|
|
@@ -727,6 +848,36 @@ class Analyzer(Visitor):
|
|
|
727
848
|
self._error("'continue' outside of loop", node.line, node.col)
|
|
728
849
|
return T_VOID
|
|
729
850
|
|
|
851
|
+
def _extract_typeof_narrowing(self, condition) -> tuple:
|
|
852
|
+
"""v1.8.1: If condition is `typeof(x) == "T"` return (var_name, narrowed_type).
|
|
853
|
+
Returns (None, None) if the condition is not that pattern."""
|
|
854
|
+
if not (hasattr(condition, 'op') and condition.op == "=="):
|
|
855
|
+
return (None, None)
|
|
856
|
+
left, right = condition.left, condition.right
|
|
857
|
+
# Support both sides for the string literal
|
|
858
|
+
if (hasattr(right, 'args') and isinstance(getattr(right, 'callee', None), object)
|
|
859
|
+
and hasattr(left, 'value') and isinstance(left.value, str)):
|
|
860
|
+
left, right = right, left
|
|
861
|
+
# left must be a CallExpr(callee=IdentExpr("typeof"|"type"), args=[IdentExpr])
|
|
862
|
+
if not hasattr(left, 'callee') or not hasattr(left, 'args'):
|
|
863
|
+
return (None, None)
|
|
864
|
+
callee_name = getattr(left.callee, 'name', None)
|
|
865
|
+
if callee_name not in ("typeof", "type"):
|
|
866
|
+
return (None, None)
|
|
867
|
+
if not left.args:
|
|
868
|
+
return (None, None)
|
|
869
|
+
arg0 = left.args[0]
|
|
870
|
+
arg_val = getattr(arg0, 'value', arg0) # Argument node wraps value
|
|
871
|
+
var_name = getattr(arg_val, 'name', None)
|
|
872
|
+
if var_name is None:
|
|
873
|
+
return (None, None)
|
|
874
|
+
# right must be a StringLiteralExpr
|
|
875
|
+
if not hasattr(right, 'value') or not isinstance(right.value, str):
|
|
876
|
+
return (None, None)
|
|
877
|
+
type_name = right.value
|
|
878
|
+
narrow_to = BUILTIN_TYPES.get(type_name, InScriptType(type_name))
|
|
879
|
+
return (var_name, narrow_to)
|
|
880
|
+
|
|
730
881
|
def visit_IfStmt(self, node: IfStmt) -> InScriptType:
|
|
731
882
|
cond_type = self.visit(node.condition)
|
|
732
883
|
if cond_type not in (T_BOOL, T_ANY):
|
|
@@ -735,7 +886,29 @@ class Analyzer(Visitor):
|
|
|
735
886
|
f"non-bool conditions will be truthy/falsy at runtime",
|
|
736
887
|
node.line
|
|
737
888
|
)
|
|
738
|
-
|
|
889
|
+
|
|
890
|
+
# v1.8.1: union narrowing — `if typeof(x) == "int" { }` narrows x to int
|
|
891
|
+
var_name, narrow_to = self._extract_typeof_narrowing(node.condition)
|
|
892
|
+
if var_name and narrow_to:
|
|
893
|
+
# Push a scope, inject narrowed binding, then visit the BODY STATEMENTS
|
|
894
|
+
# directly so the narrowed type is visible for the entire then-branch.
|
|
895
|
+
self._push_scope("block")
|
|
896
|
+
sym = self._scope.parent.lookup(var_name) if self._scope.parent else None
|
|
897
|
+
if sym:
|
|
898
|
+
narrowed_sym = Symbol(var_name, narrow_to, sym.kind,
|
|
899
|
+
line=sym.line, col=sym.col)
|
|
900
|
+
narrowed_sym.used = True
|
|
901
|
+
self._define(narrowed_sym)
|
|
902
|
+
# Visit body stmts without letting visit_BlockStmt push another scope
|
|
903
|
+
from ast_nodes import BlockStmt
|
|
904
|
+
body = node.then_branch
|
|
905
|
+
stmts = body.body if isinstance(body, BlockStmt) else [body]
|
|
906
|
+
for stmt in stmts:
|
|
907
|
+
self.visit(stmt)
|
|
908
|
+
self._pop_scope()
|
|
909
|
+
else:
|
|
910
|
+
self.visit(node.then_branch)
|
|
911
|
+
|
|
739
912
|
if node.else_branch:
|
|
740
913
|
self.visit(node.else_branch)
|
|
741
914
|
return T_VOID
|
|
@@ -854,7 +1027,9 @@ class Analyzer(Visitor):
|
|
|
854
1027
|
|
|
855
1028
|
def visit_IntLiteralExpr(self, node: IntLiteralExpr) -> InScriptType: return T_INT
|
|
856
1029
|
def visit_FloatLiteralExpr(self, node: FloatLiteralExpr) -> InScriptType: return T_FLOAT
|
|
857
|
-
def visit_StringLiteralExpr(self, node: StringLiteralExpr) -> InScriptType:
|
|
1030
|
+
def visit_StringLiteralExpr(self, node: StringLiteralExpr) -> InScriptType:
|
|
1031
|
+
# v1.8.2: return a specific literal type so union-of-literals is enforced
|
|
1032
|
+
return literal_type(node.value)
|
|
858
1033
|
def visit_BoolLiteralExpr(self, node: BoolLiteralExpr) -> InScriptType: return T_BOOL
|
|
859
1034
|
def visit_NullLiteralExpr(self, node: NullLiteralExpr) -> InScriptType: return T_NULL
|
|
860
1035
|
|
|
@@ -1024,6 +1199,20 @@ class Analyzer(Visitor):
|
|
|
1024
1199
|
f"{n_total} arg(s), got {n_args}",
|
|
1025
1200
|
node.line
|
|
1026
1201
|
)
|
|
1202
|
+
# v1.8.1: check argument types against declared param types
|
|
1203
|
+
for i, arg in enumerate(node.args):
|
|
1204
|
+
if i >= len(params):
|
|
1205
|
+
break
|
|
1206
|
+
p_type = self._resolve_type_ann(params[i].type_ann)
|
|
1207
|
+
if p_type == T_ANY:
|
|
1208
|
+
continue
|
|
1209
|
+
arg_type = self.visit(arg.value)
|
|
1210
|
+
if not types_compatible(p_type, arg_type):
|
|
1211
|
+
self._error(
|
|
1212
|
+
f"Argument {i+1} to '{node.callee.name}': "
|
|
1213
|
+
f"expected '{p_type}', got '{arg_type}'",
|
|
1214
|
+
getattr(arg.value, 'line', node.line)
|
|
1215
|
+
)
|
|
1027
1216
|
return self._resolve_type_ann(fn.return_type)
|
|
1028
1217
|
if sym:
|
|
1029
1218
|
if sym.kind == "struct":
|
|
@@ -49,6 +49,11 @@ class TypeAnnotation(Node):
|
|
|
49
49
|
is_nullable: bool = False
|
|
50
50
|
key_type: Optional["TypeAnnotation"] = None
|
|
51
51
|
nullable: bool = False
|
|
52
|
+
# v1.8.2: for string literal types `"left"` in unions / params
|
|
53
|
+
literal_value: Optional[str] = None # set when name == "__literal__"
|
|
54
|
+
# v1.8.2: for fn type aliases `fn(int) -> bool`
|
|
55
|
+
fn_params: Optional[list] = None # list of TypeAnnotation
|
|
56
|
+
fn_return: Optional["TypeAnnotation"] = None
|
|
52
57
|
|
|
53
58
|
|
|
54
59
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -717,9 +722,10 @@ class DecoratedDecl(Node):
|
|
|
717
722
|
|
|
718
723
|
@dataclass
|
|
719
724
|
class TypeAliasDecl(Node):
|
|
720
|
-
"""type ID = ExistingType
|
|
721
|
-
name:
|
|
722
|
-
target:
|
|
725
|
+
"""type ID = ExistingType | fn(...)->T | "lit1"|"lit2" — type alias."""
|
|
726
|
+
name: str
|
|
727
|
+
target: str # kept for backwards compat with interpreter
|
|
728
|
+
type_ann: object = None # TypeAnnotation (None for legacy string-only aliases)
|
|
723
729
|
|
|
724
730
|
@dataclass
|
|
725
731
|
class SelectStmt(Node):
|
|
@@ -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.2"
|
|
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"
|
|
@@ -937,9 +937,13 @@ class Interpreter(Visitor):
|
|
|
937
937
|
return iface
|
|
938
938
|
|
|
939
939
|
def visit_TypeAliasDecl(self, node) -> Any:
|
|
940
|
-
"""
|
|
941
|
-
|
|
942
|
-
self._env.define(node.name, {
|
|
940
|
+
"""v1.8.2: Register alias so it can be used in typeof/is checks at runtime."""
|
|
941
|
+
type_ann = getattr(node, 'type_ann', None)
|
|
942
|
+
self._env.define(node.name, {
|
|
943
|
+
"__type_alias__": True,
|
|
944
|
+
"__target__": node.target,
|
|
945
|
+
"__type_ann__": type_ann,
|
|
946
|
+
})
|
|
943
947
|
return None
|
|
944
948
|
|
|
945
949
|
def visit_ImplDecl(self, node) -> Any:
|
|
@@ -216,23 +216,17 @@ class Parser:
|
|
|
216
216
|
# ─────────────────────────────────────────────
|
|
217
217
|
|
|
218
218
|
def parse_type_alias(self):
|
|
219
|
-
"""type ID =
|
|
219
|
+
"""type ID = TypeAnnotation — supports simple, union, literal union, fn types."""
|
|
220
220
|
line, col = self.current.line, self.current.col
|
|
221
221
|
self.advance() # consume 'type'
|
|
222
222
|
name = self.expect(TT.IDENT, "Expected alias name after 'type'").value
|
|
223
223
|
self.expect(TT.ASSIGN, "Expected '=' in type alias")
|
|
224
|
-
#
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
# Handle qualified names like MyModule.MyType
|
|
229
|
-
while self.check(TT.DOT):
|
|
230
|
-
self.advance()
|
|
231
|
-
target_name += "." + (self.current.value or "")
|
|
232
|
-
self.advance()
|
|
233
|
-
# Return a simple VarDecl so the interpreter stores name in env as a type tag
|
|
224
|
+
# v1.8.2: parse a full type annotation (including string literal unions, fn types)
|
|
225
|
+
type_ann = self.parse_type_annotation()
|
|
226
|
+
# target: for backwards compat with interpreter, derive a string name
|
|
227
|
+
target = type_ann.name if type_ann.name not in ("__literal__", "__fn__", "Union") else name
|
|
234
228
|
from ast_nodes import TypeAliasDecl
|
|
235
|
-
return TypeAliasDecl(name=name, target=
|
|
229
|
+
return TypeAliasDecl(name=name, target=target, type_ann=type_ann, line=line, col=col)
|
|
236
230
|
|
|
237
231
|
def parse_import(self) -> ImportDecl:
|
|
238
232
|
"""
|
|
@@ -447,6 +441,40 @@ class Parser:
|
|
|
447
441
|
elif tok.type == TT.NIL: # v1.7.2: nil as type annotation
|
|
448
442
|
name = "nil"
|
|
449
443
|
self.advance()
|
|
444
|
+
# v1.8.2: string literal type — `"left"` in union / param
|
|
445
|
+
elif tok.type == TT.STRING:
|
|
446
|
+
lit = tok.value
|
|
447
|
+
self.advance()
|
|
448
|
+
ann = TypeAnnotation(name="__literal__", literal_value=lit,
|
|
449
|
+
line=line, col=col)
|
|
450
|
+
# Union suffix handled below — return early for nullable/union
|
|
451
|
+
if self.current.type == TT.QUESTION:
|
|
452
|
+
self.advance()
|
|
453
|
+
ann = TypeAnnotation(name="Optional", is_nullable=True,
|
|
454
|
+
generics=[ann], line=line, col=col)
|
|
455
|
+
if self.current.type == TT.BIT_OR:
|
|
456
|
+
union_types = [ann]
|
|
457
|
+
while self.current.type == TT.BIT_OR:
|
|
458
|
+
self.advance()
|
|
459
|
+
union_types.append(self.parse_type_annotation())
|
|
460
|
+
ann = TypeAnnotation(name="Union", generics=union_types, line=line, col=col)
|
|
461
|
+
return ann
|
|
462
|
+
# v1.8.2: fn type alias — `fn(int, string) -> bool`
|
|
463
|
+
elif tok.type == TT.FN:
|
|
464
|
+
self.advance() # consume 'fn'
|
|
465
|
+
self.expect(TT.LPAREN, "Expected '(' in fn type")
|
|
466
|
+
fn_params = []
|
|
467
|
+
if not self.check(TT.RPAREN):
|
|
468
|
+
fn_params.append(self.parse_type_annotation())
|
|
469
|
+
while self.match(TT.COMMA):
|
|
470
|
+
fn_params.append(self.parse_type_annotation())
|
|
471
|
+
self.expect(TT.RPAREN, "Expected ')' in fn type")
|
|
472
|
+
fn_return = None
|
|
473
|
+
if self.match(TT.ARROW):
|
|
474
|
+
fn_return = self.parse_type_annotation()
|
|
475
|
+
ann = TypeAnnotation(name="__fn__", fn_params=fn_params,
|
|
476
|
+
fn_return=fn_return, line=line, col=col)
|
|
477
|
+
return ann
|
|
450
478
|
else:
|
|
451
479
|
self._error(f"Expected type name, got '{tok.value}'")
|
|
452
480
|
ann = TypeAnnotation(name=name, line=line, col=col)
|
|
@@ -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.2"
|
|
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.2"
|
|
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
|