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.
Files changed (30) hide show
  1. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/PKG-INFO +1 -1
  2. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/analyzer.py +115 -3
  3. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript.py +1 -1
  4. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/PKG-INFO +1 -1
  5. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/pyproject.toml +1 -1
  6. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/repl.py +1 -1
  7. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/README.md +0 -0
  8. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/ast_nodes.py +0 -0
  9. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/compiler.py +0 -0
  10. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/environment.py +0 -0
  11. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/errors.py +0 -0
  12. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_fmt.py +0 -0
  13. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/SOURCES.txt +0 -0
  14. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/dependency_links.txt +0 -0
  15. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/entry_points.txt +0 -0
  16. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/requires.txt +0 -0
  17. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_lang.egg-info/top_level.txt +0 -0
  18. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/inscript_test.py +0 -0
  19. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/interpreter.py +0 -0
  20. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/lexer.py +0 -0
  21. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/parser.py +0 -0
  22. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/pygame_backend.py +0 -0
  23. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/setup.cfg +0 -0
  24. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/setup.py +0 -0
  25. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib.py +0 -0
  26. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib_extended.py +0 -0
  27. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib_extended_2.py +0 -0
  28. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib_game.py +0 -0
  29. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/stdlib_values.py +0 -0
  30. {inscript_lang-1.7.4 → inscript_lang-1.8.1}/vm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.7.4
3
+ Version: 1.8.1
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
@@ -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 type
108
- if got == T_NULL: return True
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
- self.visit(node.then_branch)
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.7.4"
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.7.4
3
+ Version: 1.8.1
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.7.4"
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.7.4"
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