inscript-lang 1.8.2__tar.gz → 1.8.3__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.2 → inscript_lang-1.8.3}/PKG-INFO +1 -1
  2. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/analyzer.py +96 -26
  3. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript.py +1 -1
  4. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript_lang.egg-info/PKG-INFO +1 -1
  5. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/pyproject.toml +1 -1
  6. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/repl.py +1 -1
  7. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/README.md +0 -0
  8. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/ast_nodes.py +0 -0
  9. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/compiler.py +0 -0
  10. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/environment.py +0 -0
  11. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/errors.py +0 -0
  12. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript_fmt.py +0 -0
  13. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript_lang.egg-info/SOURCES.txt +0 -0
  14. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript_lang.egg-info/dependency_links.txt +0 -0
  15. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript_lang.egg-info/entry_points.txt +0 -0
  16. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript_lang.egg-info/requires.txt +0 -0
  17. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript_lang.egg-info/top_level.txt +0 -0
  18. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/inscript_test.py +0 -0
  19. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/interpreter.py +0 -0
  20. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/lexer.py +0 -0
  21. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/parser.py +0 -0
  22. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/pygame_backend.py +0 -0
  23. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/setup.cfg +0 -0
  24. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/setup.py +0 -0
  25. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/stdlib.py +0 -0
  26. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/stdlib_extended.py +0 -0
  27. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/stdlib_extended_2.py +0 -0
  28. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/stdlib_game.py +0 -0
  29. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/stdlib_values.py +0 -0
  30. {inscript_lang-1.8.2 → inscript_lang-1.8.3}/vm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.8.2
3
+ Version: 1.8.3
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
@@ -64,10 +64,13 @@ T_TRANSFORM2D = InScriptType("Transform2D")
64
64
  T_TRANSFORM3D = InScriptType("Transform3D")
65
65
  T_TEXTURE = InScriptType("Texture")
66
66
 
67
+ T_NEVER = InScriptType("never") # v1.8.3: bottom type — function never returns normally
68
+
67
69
  BUILTIN_TYPES: Dict[str, InScriptType] = {
68
70
  # canonical names
69
71
  "int": T_INT, "float": T_FLOAT, "bool": T_BOOL,
70
72
  "string": T_STRING, "void": T_VOID, "null": T_NULL, "any": T_ANY,
73
+ "never": T_NEVER, # v1.8.3
71
74
  # common aliases
72
75
  "str": T_STRING, "boolean": T_BOOL, "number": T_FLOAT,
73
76
  "nil": T_NULL, "object": T_ANY, "auto": T_ANY,
@@ -141,6 +144,8 @@ def types_compatible(expected: InScriptType, got: InScriptType) -> bool:
141
144
  """True if `got` can be used where `expected` is required."""
142
145
  if expected == T_ANY or got == T_ANY: return True
143
146
  if expected == got: return True
147
+ # v1.8.3: `never` is the bottom type — compatible with any expected type
148
+ if got == T_NEVER: return True
144
149
  # int can widen to float
145
150
  if expected == T_FLOAT and got == T_INT: return True
146
151
  # null/nil can be assigned to any nullable or union containing nil
@@ -266,7 +271,8 @@ class Analyzer(Visitor):
266
271
  self._in_scene: bool = False
267
272
  self._in_match_arm: bool = False
268
273
  self._struct_defs: Dict[str, StructDecl] = {}
269
- self._type_aliases: Dict[str, "InScriptType"] = {} # v1.8.2: registered aliases
274
+ self._type_aliases: Dict[str, "InScriptType"] = {} # v1.8.2
275
+ self._interfaces: Dict[str, dict] = {} # v1.8.3: name → {method: (params, ret)}
270
276
 
271
277
  # Pre-register built-in global functions
272
278
  self._register_builtins()
@@ -521,8 +527,8 @@ class Analyzer(Visitor):
521
527
 
522
528
  def _hoist_top_level(self, program: Program):
523
529
  """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)
530
+ from ast_nodes import TypeAliasDecl, InterfaceDecl
531
+ # First sub-pass: register type aliases, structs, and interfaces
526
532
  for node in program.body:
527
533
  if isinstance(node, StructDecl):
528
534
  self._struct_defs[node.name] = node
@@ -532,7 +538,6 @@ class Analyzer(Visitor):
532
538
  line=node.line, col=node.col
533
539
  )
534
540
  elif isinstance(node, TypeAliasDecl):
535
- # v1.8.2: register alias immediately so functions can use it
536
541
  type_ann = getattr(node, 'type_ann', None)
537
542
  if type_ann is not None:
538
543
  resolved = self._resolve_type_ann(type_ann)
@@ -543,6 +548,19 @@ class Analyzer(Visitor):
543
548
  node.name, resolved, kind="type",
544
549
  line=node.line, col=node.col
545
550
  )
551
+ elif isinstance(node, InterfaceDecl):
552
+ # v1.8.3: register interface so structs can reference it
553
+ iface_methods = {}
554
+ for m in getattr(node, 'methods', []):
555
+ ret = self._resolve_type_ann(getattr(m, 'return_type', None))
556
+ params = [self._resolve_type_ann(getattr(p, 'type_ann', None))
557
+ for p in getattr(m, 'params', [])]
558
+ iface_methods[m.name] = (params, ret)
559
+ self._interfaces[node.name] = iface_methods
560
+ self._scope.symbols[node.name] = Symbol(
561
+ node.name, T_ANY, kind="interface",
562
+ line=node.line, col=node.col
563
+ )
546
564
  # Second sub-pass: hoist functions, scenes, enums (can now reference aliases)
547
565
  for node in program.body:
548
566
  if isinstance(node, FunctionDecl):
@@ -637,12 +655,20 @@ class Analyzer(Visitor):
637
655
  ret_type.name not in ("void", "nil", "any", "") and
638
656
  not node.is_native if hasattr(node, 'is_native') else True):
639
657
  if node.body and not self._body_always_returns(node.body):
640
- self._warn(
641
- "missing-return",
642
- f"Function '{node.name}' declares return type '{ret_type.name}' "
643
- f"but not all code paths return a value",
644
- node.line
645
- )
658
+ if ret_type == T_NEVER:
659
+ # v1.8.3: `-> never` functions MUST always throw / diverge
660
+ self._error(
661
+ f"Function '{node.name}' is declared '-> never' "
662
+ f"but not all code paths throw or diverge",
663
+ node.line
664
+ )
665
+ else:
666
+ self._warn(
667
+ "missing-return",
668
+ f"Function '{node.name}' declares return type '{ret_type.name}' "
669
+ f"but not all code paths return a value",
670
+ node.line
671
+ )
646
672
 
647
673
  # Analyze body in a new scope
648
674
  self._push_scope("fn")
@@ -713,6 +739,26 @@ class Analyzer(Visitor):
713
739
  for method in node.methods:
714
740
  self.visit_FunctionDecl(method)
715
741
 
742
+ # v1.8.3: interface enforcement — check every declared interface is satisfied
743
+ struct_method_names = {m.name for m in node.methods}
744
+ # Also include methods from static_methods
745
+ struct_method_names |= {m.name for m in (node.static_methods or [])}
746
+ for iface_name in (node.interfaces or []):
747
+ required = self._interfaces.get(iface_name)
748
+ if required is None:
749
+ self._error(
750
+ f"Struct '{node.name}' implements unknown interface '{iface_name}'",
751
+ node.line
752
+ )
753
+ continue
754
+ missing_methods = sorted(required.keys() - struct_method_names)
755
+ if missing_methods:
756
+ self._error(
757
+ f"Struct '{node.name}' implements '{iface_name}' "
758
+ f"but is missing method(s): {missing_methods}",
759
+ node.line
760
+ )
761
+
716
762
  self._pop_scope()
717
763
  return struct_type
718
764
 
@@ -757,6 +803,21 @@ class Analyzer(Visitor):
757
803
  )
758
804
  return enum_type
759
805
 
806
+ def visit_InterfaceDecl(self, node) -> InScriptType:
807
+ """v1.8.3: Register interface method signatures for implementation checking."""
808
+ iface_methods = {}
809
+ for m in getattr(node, 'methods', []):
810
+ ret = self._resolve_type_ann(getattr(m, 'return_type', None))
811
+ params = [self._resolve_type_ann(getattr(p, 'type_ann', None))
812
+ for p in getattr(m, 'params', [])]
813
+ iface_methods[m.name] = (params, ret)
814
+ self._interfaces[node.name] = iface_methods
815
+ self._scope.symbols[node.name] = Symbol(
816
+ node.name, T_ANY, kind="interface",
817
+ line=node.line, col=node.col
818
+ )
819
+ return T_VOID
820
+
760
821
  def visit_TypeAliasDecl(self, node) -> InScriptType:
761
822
  """v1.8.2: Register a type alias so annotations can reference it by name."""
762
823
  if hasattr(node, 'type_ann') and node.type_ann is not None:
@@ -965,47 +1026,56 @@ class Analyzer(Visitor):
965
1026
 
966
1027
  def _check_match_exhaustiveness(self, node: MatchStmt) -> None:
967
1028
  """
968
- If the match subject is a typed variable whose type is a known enum,
969
- warn when not all variants are covered and there is no wildcard arm.
1029
+ v1.8.3: Enum exhaustiveness is now a SemanticError (not a warning).
1030
+ If the match subject is a variable typed as a known enum and no wildcard
1031
+ is present, every variant must be explicitly covered.
970
1032
  """
971
- subject = node.subject
1033
+ subject = node.subject
972
1034
  enum_name = None
973
1035
 
974
1036
  if isinstance(subject, IdentExpr):
975
1037
  sym = self._scope.lookup(subject.name)
976
- if sym and sym.type_ and sym.type_.name in self._scope.symbols:
977
- candidate = self._scope.symbols[sym.type_.name]
978
- if candidate.kind == "enum":
979
- enum_name = sym.type_.name
1038
+ if sym and sym.type_:
1039
+ t = sym.type_
1040
+ # Resolve via the full scope chain, not just current scope
1041
+ cand = self._scope.lookup(t.name)
1042
+ if cand and cand.kind == "enum":
1043
+ enum_name = t.name
980
1044
 
981
1045
  if enum_name is None:
982
- return # can't determine enum — skip
1046
+ return # subject is not a typed enum variable — skip
983
1047
 
984
- enum_sym = self._scope.symbols.get(enum_name)
1048
+ enum_sym = self._scope.lookup(enum_name)
985
1049
  if not enum_sym or enum_sym.fn_node is None:
986
1050
  return
987
- enum_decl = enum_sym.fn_node
1051
+ enum_decl = enum_sym.fn_node
988
1052
  all_variants = {v.name for v in getattr(enum_decl, 'variants', [])}
989
1053
  if not all_variants:
990
1054
  return
991
1055
 
992
- # Collect explicitly covered variants: case EnumName.Variant
1056
+ # Collect explicitly covered variants
1057
+ # `case Dir::Left` → NamespaceAccessExpr(namespace="Dir", member="Left")
1058
+ # `case Dir.Left` → GetAttrExpr(obj=IdentExpr("Dir"), attr="Left") (legacy)
993
1059
  covered = set()
994
1060
  for arm in node.arms:
995
1061
  p = arm.pattern
996
1062
  if p is None:
997
- return # wildcard — exhaustive
998
- if (isinstance(p, GetAttrExpr)
1063
+ return # wildcard — exhaustive by definition
1064
+ # Primary form: Dir::Variant
1065
+ if (hasattr(p, 'namespace') and hasattr(p, 'member')
1066
+ and p.namespace == enum_name):
1067
+ covered.add(p.member)
1068
+ # Legacy form: Dir.Variant
1069
+ elif (isinstance(p, GetAttrExpr)
999
1070
  and isinstance(p.obj, IdentExpr)
1000
1071
  and p.obj.name == enum_name):
1001
1072
  covered.add(p.attr)
1002
1073
 
1003
1074
  missing = sorted(all_variants - covered)
1004
1075
  if missing:
1005
- self._warn(
1006
- "exhaustive_match",
1076
+ self._error(
1007
1077
  f"Non-exhaustive match on '{enum_name}': "
1008
- f"variants {missing} not covered. Add missing cases or a wildcard 'case _'",
1078
+ f"missing variant(s) {missing}. Add the missing cases or a wildcard arm 'case _'",
1009
1079
  node.line
1010
1080
  )
1011
1081
 
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
24
24
  SemanticError, InScriptRuntimeError,
25
25
  MultiError, InScriptWarning)
26
26
 
27
- VERSION = "1.8.2"
27
+ VERSION = "1.8.3"
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.2
3
+ Version: 1.8.3
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "inscript-lang"
7
- version = "1.8.2"
7
+ version = "1.8.3"
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.2"
43
+ VERSION = "1.8.3"
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