inscript-lang 1.8.1__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.8.1 → inscript_lang-1.8.2}/PKG-INFO +1 -1
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/analyzer.py +85 -8
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/ast_nodes.py +9 -3
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript.py +1 -1
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/interpreter.py +7 -3
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/parser.py +40 -12
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/pyproject.toml +1 -1
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/repl.py +1 -1
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/README.md +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/compiler.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/environment.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/errors.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_fmt.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_test.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/lexer.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/pygame_backend.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/setup.cfg +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/setup.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib_extended.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib_extended_2.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib_game.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib_values.py +0 -0
- {inscript_lang-1.8.1 → inscript_lang-1.8.2}/vm.py +0 -0
|
@@ -112,6 +112,20 @@ def union_members(t: InScriptType) -> list:
|
|
|
112
112
|
"""v1.8.1: Return list of member types (works on non-unions too)."""
|
|
113
113
|
return t.params if t.name == "Union" else [t]
|
|
114
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
|
+
|
|
115
129
|
def is_numeric(t: InScriptType) -> bool:
|
|
116
130
|
return t in (T_INT, T_FLOAT)
|
|
117
131
|
|
|
@@ -140,6 +154,25 @@ def types_compatible(expected: InScriptType, got: InScriptType) -> bool:
|
|
|
140
154
|
# v1.8.1: if got is a Union, every member must be compatible with expected
|
|
141
155
|
if got.name == "Union":
|
|
142
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
|
|
143
176
|
# Array<X> compatible with Array<any> and vice versa
|
|
144
177
|
if expected.name == "Array" and got.name == "Array": return True
|
|
145
178
|
# Dict<K,V> compatible with Dict<any,any>
|
|
@@ -233,6 +266,7 @@ class Analyzer(Visitor):
|
|
|
233
266
|
self._in_scene: bool = False
|
|
234
267
|
self._in_match_arm: bool = False
|
|
235
268
|
self._struct_defs: Dict[str, StructDecl] = {}
|
|
269
|
+
self._type_aliases: Dict[str, "InScriptType"] = {} # v1.8.2: registered aliases
|
|
236
270
|
|
|
237
271
|
# Pre-register built-in global functions
|
|
238
272
|
self._register_builtins()
|
|
@@ -325,21 +359,33 @@ class Analyzer(Visitor):
|
|
|
325
359
|
v = self._resolve_type_ann(ann.generics[0]) if ann.generics else T_ANY
|
|
326
360
|
return dict_type(k, v)
|
|
327
361
|
|
|
328
|
-
# v1.8.1: `T?` — nullable shorthand
|
|
329
|
-
# is_nullable=True, generics=[T])
|
|
362
|
+
# v1.8.1: `T?` — nullable shorthand
|
|
330
363
|
if ann.is_nullable or ann.name == "Optional":
|
|
331
364
|
inner = self._resolve_type_ann(ann.generics[0]) if ann.generics else T_ANY
|
|
332
365
|
return optional_type(inner)
|
|
333
366
|
|
|
334
|
-
# v1.8.1: `A | B | C` — union type
|
|
335
|
-
# generics=[A, B, C])
|
|
367
|
+
# v1.8.1: `A | B | C` — union type
|
|
336
368
|
if ann.name == "Union":
|
|
337
369
|
members = [self._resolve_type_ann(g) for g in ann.generics]
|
|
338
370
|
return union_type(*members)
|
|
339
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
|
+
|
|
340
382
|
if ann.name in BUILTIN_TYPES:
|
|
341
383
|
return BUILTIN_TYPES[ann.name]
|
|
342
384
|
|
|
385
|
+
# v1.8.2: registered type aliases
|
|
386
|
+
if ann.name in self._type_aliases:
|
|
387
|
+
return self._type_aliases[ann.name]
|
|
388
|
+
|
|
343
389
|
# Check user-defined structs
|
|
344
390
|
if ann.name in self._struct_defs:
|
|
345
391
|
return InScriptType(ann.name)
|
|
@@ -474,7 +520,9 @@ class Analyzer(Visitor):
|
|
|
474
520
|
return self._scope.symbols
|
|
475
521
|
|
|
476
522
|
def _hoist_top_level(self, program: Program):
|
|
477
|
-
"""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)
|
|
478
526
|
for node in program.body:
|
|
479
527
|
if isinstance(node, StructDecl):
|
|
480
528
|
self._struct_defs[node.name] = node
|
|
@@ -483,7 +531,21 @@ class Analyzer(Visitor):
|
|
|
483
531
|
kind="struct", struct_node=node,
|
|
484
532
|
line=node.line, col=node.col
|
|
485
533
|
)
|
|
486
|
-
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):
|
|
487
549
|
ret = self._resolve_type_ann(node.return_type)
|
|
488
550
|
self._scope.symbols[node.name] = Symbol(
|
|
489
551
|
node.name, ret, kind="fn", fn_node=node,
|
|
@@ -497,7 +559,7 @@ class Analyzer(Visitor):
|
|
|
497
559
|
elif isinstance(node, EnumDecl):
|
|
498
560
|
self._scope.symbols[node.name] = Symbol(
|
|
499
561
|
node.name, InScriptType(node.name), kind="enum",
|
|
500
|
-
fn_node=node,
|
|
562
|
+
fn_node=node,
|
|
501
563
|
line=node.line, col=node.col
|
|
502
564
|
)
|
|
503
565
|
|
|
@@ -695,6 +757,19 @@ class Analyzer(Visitor):
|
|
|
695
757
|
)
|
|
696
758
|
return enum_type
|
|
697
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
|
+
|
|
698
773
|
def visit_ImportDecl(self, node: ImportDecl) -> InScriptType:
|
|
699
774
|
"""Register imported symbols so the type checker knows about them."""
|
|
700
775
|
try:
|
|
@@ -952,7 +1027,9 @@ class Analyzer(Visitor):
|
|
|
952
1027
|
|
|
953
1028
|
def visit_IntLiteralExpr(self, node: IntLiteralExpr) -> InScriptType: return T_INT
|
|
954
1029
|
def visit_FloatLiteralExpr(self, node: FloatLiteralExpr) -> InScriptType: return T_FLOAT
|
|
955
|
-
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)
|
|
956
1033
|
def visit_BoolLiteralExpr(self, node: BoolLiteralExpr) -> InScriptType: return T_BOOL
|
|
957
1034
|
def visit_NullLiteralExpr(self, node: NullLiteralExpr) -> InScriptType: return T_NULL
|
|
958
1035
|
|
|
@@ -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.8.
|
|
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.8.
|
|
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.8.
|
|
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
|