inscript-lang 1.6.0__tar.gz → 1.7.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.6.0 → inscript_lang-1.7.1}/PKG-INFO +1 -1
  2. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript.py +50 -1
  3. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/PKG-INFO +1 -1
  4. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/interpreter.py +49 -11
  5. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/lexer.py +25 -13
  6. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/parser.py +13 -7
  7. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/pyproject.toml +1 -1
  8. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/repl.py +1 -1
  9. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/setup.py +1 -1
  10. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/README.md +0 -0
  11. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/analyzer.py +0 -0
  12. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/ast_nodes.py +0 -0
  13. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/compiler.py +0 -0
  14. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/environment.py +0 -0
  15. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/errors.py +0 -0
  16. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_fmt.py +0 -0
  17. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/SOURCES.txt +0 -0
  18. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/dependency_links.txt +0 -0
  19. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/entry_points.txt +0 -0
  20. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/requires.txt +0 -0
  21. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/top_level.txt +0 -0
  22. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_test.py +0 -0
  23. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/pygame_backend.py +0 -0
  24. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/setup.cfg +0 -0
  25. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib.py +0 -0
  26. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib_extended.py +0 -0
  27. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib_extended_2.py +0 -0
  28. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib_game.py +0 -0
  29. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib_values.py +0 -0
  30. {inscript_lang-1.6.0 → inscript_lang-1.7.1}/vm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.6.0
3
+ Version: 1.7.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
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
24
24
  SemanticError, InScriptRuntimeError,
25
25
  MultiError, InScriptWarning)
26
26
 
27
- VERSION = "1.6.0"
27
+ VERSION = "1.7.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"
@@ -245,6 +245,51 @@ def _check_all_files(directory: str, strict: bool = False) -> int:
245
245
  return 1 if errors else 0
246
246
 
247
247
 
248
+ def _migrate_files(path: str) -> int:
249
+ """v1.7.4: Auto-migrate deprecated InScript syntax in-place."""
250
+ import re, os
251
+ target = os.path.abspath(path)
252
+ files = []
253
+ if os.path.isfile(target):
254
+ files = [target]
255
+ elif os.path.isdir(target):
256
+ files = list(_find_ins_files(target))
257
+ else:
258
+ print(f"[InScript migrate] Not found: '{path}'", file=sys.stderr)
259
+ return 1
260
+
261
+ changed = errors = 0
262
+ for fpath in files:
263
+ try:
264
+ with open(fpath, encoding="utf-8") as f:
265
+ original = f.read()
266
+ src = original
267
+ # null → nil
268
+ src = re.sub(r'\bnull\b', 'nil', src)
269
+ # x div y → x // y (only bare `div` between expressions)
270
+ src = re.sub(r'\bdiv\b', '//', src)
271
+ # bare [] type annotation → array (e.g. `: []` → `: array`)
272
+ src = re.sub(r':\s*\[\]', ': array', src)
273
+ if src != original:
274
+ with open(fpath, "w", encoding="utf-8") as f:
275
+ f.write(src)
276
+ print(f" MIGRATED {fpath}")
277
+ changed += 1
278
+ else:
279
+ print(f" OK {fpath}")
280
+ except Exception as e:
281
+ errors += 1
282
+ print(f" ERR {fpath}: {e}", file=sys.stderr)
283
+
284
+ sep = "-" * 50
285
+ print(f"\n{sep}")
286
+ print(f" Migrated {changed} file{'s' if changed != 1 else ''}, "
287
+ f"{len(files)-changed-errors} already clean, "
288
+ f"{errors} error{'s' if errors != 1 else ''}")
289
+ print(sep)
290
+ return 1 if errors else 0
291
+
292
+
248
293
  def _fmt_all_files(directory: str) -> int:
249
294
  """v1.6.0: Format all .ins files in directory in-place. Returns exit code."""
250
295
  files = list(_find_ins_files(directory))
@@ -568,6 +613,8 @@ Examples:
568
613
  parser.add_argument("--check", action="store_true", help="Type-check only, don't run")
569
614
  parser.add_argument("--check-all", metavar="DIR",
570
615
  help="v1.6.0: Check all .ins files in DIR recursively, exit 1 if any errors")
616
+ parser.add_argument("--migrate", metavar="DIR_OR_FILE",
617
+ help="v1.7.4: Auto-migrate deprecated syntax (null→nil, div→//)")
571
618
  parser.add_argument("--strict", action="store_true",
572
619
  help="v1.6.0: Strict mode — all warnings become errors, no implicit any")
573
620
  parser.add_argument("--tokens", action="store_true", help="Print lexer token stream")
@@ -721,6 +768,8 @@ Examples:
721
768
  strict=getattr(args, 'strict', False))
722
769
  if getattr(args, 'fmt_all', None):
723
770
  return _fmt_all_files(args.fmt_all)
771
+ if getattr(args, 'migrate', None):
772
+ return _migrate_files(args.migrate)
724
773
 
725
774
  if not args.file:
726
775
  parser.print_help()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.6.0
3
+ Version: 1.7.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
@@ -1487,12 +1487,14 @@ class Interpreter(Visitor):
1487
1487
  def visit_FloatLiteralExpr(self, n): return n.value
1488
1488
  def visit_StringLiteralExpr(self,n): return n.value
1489
1489
  def visit_BoolLiteralExpr(self, n): return n.value
1490
- def visit_NullLiteralExpr(self, n):
1491
- # DESIGN-06: 'null' is a deprecated alias for 'nil' warn once per session
1492
- if not getattr(self, '_null_warned', False):
1493
- import sys
1494
- print("\033[33m[InScript] Warning: 'null' is deprecated — use 'nil' instead\033[0m", file=sys.stderr)
1495
- self._null_warned = True
1490
+ def visit_NullLiteralExpr(self, n):
1491
+ # v1.7.4: 'null' keyword is a hard error; 'nil' is fine
1492
+ if getattr(n, '_is_null_keyword', False):
1493
+ self._error(
1494
+ "'null' was removed in v1.7.4 — use 'nil' instead. "
1495
+ "Run: inscript migrate <file> to auto-fix.",
1496
+ getattr(n, 'line', 0)
1497
+ )
1496
1498
  return None
1497
1499
 
1498
1500
  def visit_IdentExpr(self, node: IdentExpr) -> Any:
@@ -3292,6 +3294,46 @@ def _maybe_copy_value(val):
3292
3294
  return val
3293
3295
 
3294
3296
 
3297
+ def _format_float(val: float) -> str:
3298
+ """v1.7.1: Clean float display.
3299
+ 0.30000000000000004 → '0.3', 1.0 → '1.0',
3300
+ 1/3 → '0.3333333333333333', sqrt(2) → '1.4142135623730951'
3301
+ """
3302
+ if math.isinf(val): return "Infinity" if val > 0 else "-Infinity"
3303
+ if math.isnan(val): return "NaN"
3304
+ if val == int(val) and abs(val) < 1e15:
3305
+ return f"{int(val)}.0"
3306
+
3307
+ # Get the definitive repr (always round-trips correctly)
3308
+ s_repr = repr(val)
3309
+
3310
+ # Try 15 sig figs — may produce a shorter/cleaner string for float-noise values
3311
+ s15 = f"{val:.15g}"
3312
+ try:
3313
+ v15 = float(s15)
3314
+ # Only use 15g result if it's strictly shorter than repr AND round-trips close enough
3315
+ # "Shorter" means fewer significant digits → it genuinely simplified the value
3316
+ denom = abs(val) if val != 0 else 1.0
3317
+ rel_err = abs(v15 - val) / denom
3318
+ if rel_err < 2e-15 and len(s15.rstrip('0').rstrip('.')) <= len(s_repr) - 2:
3319
+ s = s15
3320
+ if '.' in s and 'e' not in s and 'E' not in s:
3321
+ s = s.rstrip('0')
3322
+ if s.endswith('.'):
3323
+ s += '0'
3324
+ return s
3325
+ except Exception:
3326
+ pass
3327
+
3328
+ # Use repr — strip redundant trailing zeros but keep all significant digits
3329
+ s = s_repr
3330
+ if '.' in s and 'e' not in s and 'E' not in s:
3331
+ s = s.rstrip('0')
3332
+ if s.endswith('.'):
3333
+ s += '0'
3334
+ return s
3335
+
3336
+
3295
3337
  def _inscript_str(val) -> str:
3296
3338
  if val is None: return "nil"
3297
3339
  if val is True: return "true"
@@ -3299,11 +3341,7 @@ def _inscript_str(val) -> str:
3299
3341
  if isinstance(val, list):
3300
3342
  return "[" + ", ".join(_inscript_repr(v) for v in val) + "]"
3301
3343
  if isinstance(val, float):
3302
- if math.isinf(val): return "Infinity" if val > 0 else "-Infinity"
3303
- if math.isnan(val): return "NaN"
3304
- if val == int(val):
3305
- return f"{int(val)}.0"
3306
- return str(val)
3344
+ return _format_float(val)
3307
3345
  if isinstance(val, str):
3308
3346
  return val # bare string — no quotes when printing top-level
3309
3347
  # InScript struct instance — show data fields only, no methods
@@ -15,7 +15,8 @@ class TT(Enum):
15
15
  FLOAT = auto()
16
16
  STRING = auto()
17
17
  BOOL = auto()
18
- NULL = auto()
18
+ NULL = auto() # null (removed — hard error)
19
+ NIL = auto() # nil (the correct nil literal)
19
20
 
20
21
  # ── Identifiers & Keywords ────────────────────
21
22
  IDENT = auto()
@@ -175,7 +176,7 @@ KEYWORDS: dict = {
175
176
  "true": TT.BOOL,
176
177
  "false": TT.BOOL,
177
178
  "null": TT.NULL,
178
- "nil": TT.NULL, # alias for null
179
+ "nil": TT.NIL,
179
180
  "import": TT.IMPORT,
180
181
  "from": TT.FROM,
181
182
  "as": TT.AS,
@@ -291,16 +292,26 @@ class Lexer:
291
292
  if ch in (" ", "\t", "\r", "\n"):
292
293
  return
293
294
 
294
- # // — floor-division or line comment
295
- # Floor division: `10 // 3`, `x // 2`, `(a+b) // c`
296
- # Comment: `let x = 5 // this is a comment`
297
- # Key rule: look at what comes AFTER the second /
298
- # - digit or (always floor division
299
- # - letter/word always comment
300
- # Phase 1.2: // is now ALWAYS a line comment. Floor division uses 'div' keyword.
295
+ # // — floor-division operator or line comment
296
+ # v1.7.1: `//` is now the integer division operator (10 // 3 → 3)
297
+ # Line comment: `let x = 5 // this is a comment` — space before //
298
+ # Floor division: `10 // 3`, `x//2`, `(a+b) // c`
299
+ # Rule: if the character BEFORE // was a digit, ), ], or identifier char → floor div
300
+ # otherwiseline comment
301
301
  if ch == "/" and self.current == "/":
302
302
  self.advance() # consume second /
303
- self._skip_line_comment()
303
+ # Determine: floor-div or line comment?
304
+ # Rule: the character IMMEDIATELY before first '/' (no whitespace allowed)
305
+ # must be digit/identifier/closing bracket to be floor-div.
306
+ # Any space/tab before the // → line comment.
307
+ first_slash_pos = self.pos - 2
308
+ prev = self.source[first_slash_pos - 1] if first_slash_pos > 0 else ''
309
+ if prev and (prev.isdigit() or prev.isalpha() or prev in ')]}'):
310
+ # `10//3`, `x//2`, `(a+b)//c` — no space → floor division
311
+ self._emit(TT.SLASH_SLASH, "//", sl, sc)
312
+ else:
313
+ # `10 // 3` with spaces, `5 // comment`, `// standalone` → comment
314
+ self._skip_line_comment()
304
315
  return
305
316
  if ch == "/" and self.current == "*":
306
317
  self._skip_block_comment(sl, sc); return
@@ -497,7 +508,8 @@ class Lexer:
497
508
  text = "".join(chars)
498
509
  tt = KEYWORDS.get(text, TT.IDENT)
499
510
  if tt == TT.BOOL: value = (text == "true")
500
- elif tt == TT.NULL: value = None
511
+ elif tt == TT.NIL: value = None # nil literal → Python None
512
+ elif tt == TT.NULL: value = None # null (removed keyword — still lex it so error can fire)
501
513
  else: value = text
502
514
  self._emit(tt, value, sl, sc)
503
515
 
@@ -522,8 +534,8 @@ class Lexer:
522
534
  elif self.match("="): emit(TT.STAR_EQ, "*=")
523
535
  else: emit(TT.STAR, "*")
524
536
  elif ch == "/":
525
- if self.match("="): emit(TT.SLASH_EQ, "/=")
526
- else: emit(TT.SLASH, "/")
537
+ if self.match("="): emit(TT.SLASH_EQ, "/=")
538
+ else: emit(TT.SLASH, "/")
527
539
  elif ch == "%":
528
540
  if self.match("="): emit(TT.PERCENT_EQ, "%=")
529
541
  else: emit(TT.PERCENT, "%")
@@ -887,7 +887,7 @@ class Parser:
887
887
  _type_starts = (TT.IDENT, TT.INT_TYPE, TT.FLOAT_TYPE, TT.BOOL_TYPE,
888
888
  TT.STRING_TYPE, TT.VOID_TYPE, TT.LBRACE, TT.LPAREN)
889
889
  # Tokens that unambiguously start a literal default (not a type name)
890
- _default_starts = (TT.NULL, TT.BOOL, TT.INT, TT.FLOAT, TT.STRING, TT.MINUS)
890
+ _default_starts = (TT.NIL, TT.NULL, TT.BOOL, TT.INT, TT.FLOAT, TT.STRING, TT.MINUS)
891
891
 
892
892
  if self.check(TT.LBRACKET):
893
893
  if self.peek.type != TT.RBRACKET:
@@ -1623,7 +1623,7 @@ class Parser:
1623
1623
  attr_tok = self.current
1624
1624
  # Accept any identifier OR keyword as attribute name (e.g. regex.match, uuid.nil)
1625
1625
  _KEYWORD_TYPES = {
1626
- TT.NULL, TT.MATCH, TT.IN, TT.FOR, TT.IF, TT.ELSE, TT.WHILE,
1626
+ TT.NIL, TT.NULL, TT.MATCH, TT.IN, TT.FOR, TT.IF, TT.ELSE, TT.WHILE,
1627
1627
  TT.RETURN, TT.BREAK, TT.CONTINUE, TT.LET, TT.CONST, TT.FN,
1628
1628
  TT.STRUCT, TT.ENUM, TT.IMPORT, TT.EXPORT, TT.FROM, TT.ASYNC,
1629
1629
  TT.AWAIT, TT.ABSTRACT, TT.SELECT, TT.CASE, TT.TRY, TT.CATCH,
@@ -1634,7 +1634,7 @@ class Parser:
1634
1634
  }
1635
1635
  if attr_tok.type == TT.IDENT or attr_tok.type in _KEYWORD_TYPES:
1636
1636
  # For NULL token use "nil"; for BOOL use the literal text ("true"/"false")
1637
- if attr_tok.type == TT.NULL:
1637
+ if attr_tok.type in (TT.NIL, TT.NULL):
1638
1638
  attr = "nil"
1639
1639
  elif attr_tok.type == TT.BOOL:
1640
1640
  attr = "true" if attr_tok.value else "false"
@@ -1694,7 +1694,7 @@ class Parser:
1694
1694
  # Propagate ? is followed by newline, }, ), ,, ;, EOF
1695
1695
  elif self.check(TT.QUESTION):
1696
1696
  _EXPR_START = {
1697
- TT.STRING, TT.INT, TT.FLOAT, TT.BOOL, TT.NULL,
1697
+ TT.STRING, TT.INT, TT.FLOAT, TT.BOOL, TT.NIL, TT.NULL,
1698
1698
  TT.IDENT, TT.LPAREN, TT.LBRACKET, TT.LBRACE,
1699
1699
  TT.BIT_OR, TT.OR, TT.NOT, TT.MINUS, TT.FSTRING,
1700
1700
  TT.INT_TYPE, TT.FLOAT_TYPE, TT.STRING_TYPE, TT.BOOL_TYPE,
@@ -1735,7 +1735,7 @@ class Parser:
1735
1735
  _CHECK_TYPES = {
1736
1736
  TT.INT_TYPE: "int", TT.FLOAT_TYPE: "float",
1737
1737
  TT.STRING_TYPE: "string", TT.BOOL_TYPE: "bool",
1738
- TT.VOID_TYPE: "void", TT.NULL: "nil",
1738
+ TT.VOID_TYPE: "void", TT.NULL: "nil", TT.NIL: "nil",
1739
1739
  TT.IDENT: None,
1740
1740
  }
1741
1741
  if check_tok.type in _CHECK_TYPES:
@@ -1813,10 +1813,16 @@ class Parser:
1813
1813
  self.advance()
1814
1814
  return BoolLiteralExpr(value=tok.value, line=line, col=col)
1815
1815
 
1816
- # Null
1817
- if tok.type == TT.NULL:
1816
+ # nil / null
1817
+ if tok.type == TT.NIL:
1818
1818
  self.advance()
1819
1819
  return NullLiteralExpr(line=line, col=col)
1820
+ if tok.type == TT.NULL:
1821
+ self.advance()
1822
+ # Keep the node but tag it as the removed 'null' keyword
1823
+ node = NullLiteralExpr(line=line, col=col)
1824
+ node._is_null_keyword = True # interpreter will hard-error on this
1825
+ return node
1820
1826
 
1821
1827
  # Grouped expression: (expr) OR Tuple: (expr, expr, ...)
1822
1828
  if tok.type == TT.LPAREN:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "inscript-lang"
7
- version = "1.6.0"
7
+ version = "1.7.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.6.0"
43
+ VERSION = "1.7.1"
44
44
 
45
45
  # ── ANSI colours ──────────────────────────────────────────────────────────────
46
46
  def _c(code, text):
@@ -11,7 +11,7 @@ from setuptools import setup, find_packages
11
11
 
12
12
  setup(
13
13
  name = "inscript-lang",
14
- version = "1.6.0",
14
+ version = "1.7.1",
15
15
  author = "Shreyasi Sarkar",
16
16
  description = "InScript — a game-focused scripting language for 2D games",
17
17
  long_description = open("README.md", encoding="utf-8").read(),
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes