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.
Files changed (30) hide show
  1. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/PKG-INFO +1 -1
  2. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/analyzer.py +85 -8
  3. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/ast_nodes.py +9 -3
  4. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript.py +1 -1
  5. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/PKG-INFO +1 -1
  6. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/interpreter.py +7 -3
  7. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/parser.py +40 -12
  8. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/pyproject.toml +1 -1
  9. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/repl.py +1 -1
  10. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/README.md +0 -0
  11. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/compiler.py +0 -0
  12. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/environment.py +0 -0
  13. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/errors.py +0 -0
  14. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_fmt.py +0 -0
  15. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/SOURCES.txt +0 -0
  16. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/dependency_links.txt +0 -0
  17. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/entry_points.txt +0 -0
  18. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/requires.txt +0 -0
  19. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_lang.egg-info/top_level.txt +0 -0
  20. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/inscript_test.py +0 -0
  21. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/lexer.py +0 -0
  22. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/pygame_backend.py +0 -0
  23. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/setup.cfg +0 -0
  24. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/setup.py +0 -0
  25. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib.py +0 -0
  26. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib_extended.py +0 -0
  27. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib_extended_2.py +0 -0
  28. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib_game.py +0 -0
  29. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/stdlib_values.py +0 -0
  30. {inscript_lang-1.8.1 → inscript_lang-1.8.2}/vm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: InScript — a game-focused scripting language with 59 game modules and a bytecode VM
5
5
  Author: Shreyasi Sarkar
6
6
  License: MIT
@@ -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, parsed as TypeAnnotation(name="Optional",
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, parsed as TypeAnnotation(name="Union",
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, and top-level functions before checking bodies."""
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, FunctionDecl):
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, # store EnumDecl for exhaustiveness checks
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: return T_STRING
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 type alias (annotation only at runtime)."""
721
- name: str
722
- target: str
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.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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: InScript — a game-focused scripting language with 59 game modules and a bytecode VM
5
5
  Author: Shreyasi Sarkar
6
6
  License: MIT
@@ -937,9 +937,13 @@ class Interpreter(Visitor):
937
937
  return iface
938
938
 
939
939
  def visit_TypeAliasDecl(self, node) -> Any:
940
- """type ID = ExistingType store alias so type annotations can reference it."""
941
- # Runtime effect: register the alias name so it can be used in is/as checks
942
- self._env.define(node.name, {"__type_alias__": True, "__target__": node.target})
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 = ExistingTypetype alias declaration (annotation only)."""
219
+ """type ID = TypeAnnotationsupports 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
- # Consume the type expression (could be int, string, ident, etc.)
225
- target_tok = self.current
226
- target_name = target_tok.value or target_tok.type.name
227
- self.advance()
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=target_name, line=line, col=col)
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.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.8.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