inscript-lang 1.9.2__tar.gz → 1.9.5__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.9.2 → inscript_lang-1.9.5}/PKG-INFO +1 -1
  2. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/analyzer.py +6 -1
  3. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/errors.py +2 -1
  4. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript.py +907 -3
  5. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript_lang.egg-info/PKG-INFO +1 -1
  6. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/parser.py +7 -1
  7. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/pyproject.toml +1 -1
  8. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/repl.py +1 -1
  9. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/setup.py +19 -4
  10. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/README.md +0 -0
  11. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/ast_nodes.py +0 -0
  12. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/compiler.py +0 -0
  13. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/environment.py +0 -0
  14. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript_fmt.py +0 -0
  15. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript_lang.egg-info/SOURCES.txt +0 -0
  16. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript_lang.egg-info/dependency_links.txt +0 -0
  17. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript_lang.egg-info/entry_points.txt +0 -0
  18. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript_lang.egg-info/requires.txt +0 -0
  19. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript_lang.egg-info/top_level.txt +0 -0
  20. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/inscript_test.py +0 -0
  21. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/interpreter.py +0 -0
  22. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/lexer.py +0 -0
  23. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/pygame_backend.py +0 -0
  24. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/setup.cfg +0 -0
  25. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/stdlib.py +0 -0
  26. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/stdlib_extended.py +0 -0
  27. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/stdlib_extended_2.py +0 -0
  28. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/stdlib_game.py +0 -0
  29. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/stdlib_values.py +0 -0
  30. {inscript_lang-1.9.2 → inscript_lang-1.9.5}/vm.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.9.2
3
+ Version: 1.9.5
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
@@ -616,7 +616,8 @@ class Analyzer(Visitor):
616
616
  )
617
617
  # Infer type from initializer if no annotation
618
618
  if declared_type == T_ANY and node.initializer:
619
- declared_type = init_type
619
+ # v1.8.2: literal_type("hello") is a string value — store as T_STRING
620
+ declared_type = T_STRING if is_literal_type(init_type) else init_type
620
621
 
621
622
  self._define(Symbol(
622
623
  node.name, declared_type,
@@ -1155,6 +1156,10 @@ class Analyzer(Visitor):
1155
1156
  right = self.visit(node.right)
1156
1157
  op = node.op
1157
1158
 
1159
+ # v1.8.2: literal string types act as T_STRING in all operations
1160
+ if is_literal_type(left): left = T_STRING
1161
+ if is_literal_type(right): right = T_STRING
1162
+
1158
1163
  # Comparison operators always return bool
1159
1164
  if op in ("==", "!=", "<", ">", "<=", ">="):
1160
1165
  return T_BOOL
@@ -54,8 +54,9 @@ ERROR_CODES = {
54
54
  "PropertyError": "E0048",
55
55
  "NilAccess": "E0049",
56
56
 
57
- # Deprecated-keyword hard errors (v1.7.4)
57
+ # Deprecated-keyword hard errors (v1.7.4+)
58
58
  "NullKeyword": "E0055",
59
+ "DivKeyword": "E0056",
59
60
  }
60
61
 
61
62
  DOCS_BASE = "https://docs.inscript.dev/errors"
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
24
24
  SemanticError, InScriptRuntimeError,
25
25
  MultiError, InScriptWarning)
26
26
 
27
- VERSION = "1.9.2"
27
+ VERSION = "1.9.5"
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"
@@ -256,9 +256,9 @@ def _compat_files(path: str) -> int:
256
256
 
257
257
  CHECKS = [
258
258
  (_re.compile(r'\bnull\b'),
259
- "use of 'null' — removed in v2.0.0, use 'nil'"),
259
+ "use of 'null' — removed in v1.7.4, use 'nil'"),
260
260
  (_re.compile(r'\bdiv\b'),
261
- "use of 'div' operator — removed in v2.0.0, use '//' for floor division"),
261
+ "use of 'div' operator — removed in v1.9.5 (hard error), use '//' for floor division"),
262
262
  (_re.compile(r':\s*\[\]'),
263
263
  "bare ':[]' type annotation — use ':array' or ':[T]' with element type"),
264
264
  (_re.compile(r'\barray\b(?!\s*<)(?!\s*\[)'),
@@ -435,6 +435,883 @@ def _print_inscript_profile(pr, filename: str = "<script>"):
435
435
  print(f" {'':>10} {total_time:>8.3f} 100.0% TOTAL (hotspots)\n")
436
436
 
437
437
 
438
+
439
+
440
+ # ─────────────────────────────────────────────────────────────────────────────
441
+ # v1.9.3 — Documentation Generation
442
+ # ─────────────────────────────────────────────────────────────────────────────
443
+
444
+ import re as _re
445
+
446
+ # ── Doc comment parser ────────────────────────────────────────────────────────
447
+
448
+ def _parse_doc_comments(source: str) -> list:
449
+ """
450
+ v1.9.3: Extract doc-comment blocks from InScript source.
451
+
452
+ A doc block is one or more consecutive `///` lines immediately followed
453
+ by a `fn`, `struct`, `enum`, `interface`, or `type` declaration.
454
+
455
+ Returns a list of dicts:
456
+ {
457
+ "kind": "fn"|"struct"|"enum"|"interface"|"type",
458
+ "name": str,
459
+ "description": str, # first non-tag lines joined
460
+ "params": [{"name":str, "type":str, "desc":str}],
461
+ "returns": {"type":str, "desc":str} | None,
462
+ "examples": [str], # code blocks from @example tags
463
+ "deprecated": str | None, # message from @deprecated
464
+ "since": str | None, # version string from @since
465
+ "raw_tags": [{"tag":str, "text":str}], # all unrecognized tags
466
+ "line": int,
467
+ }
468
+ """
469
+ lines = source.splitlines()
470
+ results = []
471
+ i = 0
472
+
473
+ DECL_RE = _re.compile(
474
+ r'^\s*(?:pub\s+)?(?:async\s+)?'
475
+ r'(fn|struct|enum|interface|type)\s+(\w+)'
476
+ )
477
+
478
+ while i < len(lines):
479
+ # Collect consecutive /// lines
480
+ if not lines[i].lstrip().startswith("///"):
481
+ i += 1
482
+ continue
483
+
484
+ block_start = i
485
+ raw_comment = []
486
+ while i < len(lines) and lines[i].lstrip().startswith("///"):
487
+ stripped = lines[i].lstrip()
488
+ text = stripped[3:].lstrip(" ") # strip leading "/// "
489
+ raw_comment.append(text)
490
+ i += 1
491
+
492
+ # Skip blank lines between doc block and declaration
493
+ j = i
494
+ while j < len(lines) and not lines[j].strip():
495
+ j += 1
496
+
497
+ if j >= len(lines):
498
+ continue
499
+
500
+ m = DECL_RE.match(lines[j])
501
+ if not m:
502
+ continue # doc block not followed by a declaration
503
+
504
+ kind = m.group(1)
505
+ name = m.group(2)
506
+
507
+ # Parse the doc block
508
+ description_lines = []
509
+ params = []
510
+ returns = None
511
+ examples = []
512
+ deprecated = None
513
+ since = None
514
+ raw_tags = []
515
+ example_buf = []
516
+ in_example = False
517
+
518
+ for raw in raw_comment:
519
+ if raw.startswith("@example"):
520
+ in_example = True
521
+ example_buf = []
522
+ continue
523
+ if in_example:
524
+ if raw.startswith("@"):
525
+ examples.append("\n".join(example_buf).strip())
526
+ example_buf = []
527
+ in_example = False
528
+ # fall through to process this @tag
529
+ else:
530
+ example_buf.append(raw)
531
+ continue
532
+ if raw.startswith("@param "):
533
+ rest = raw[7:].strip()
534
+ parts = rest.split(None, 2)
535
+ pname = parts[0] if len(parts) > 0 else ""
536
+ ptype = parts[1] if len(parts) > 1 else ""
537
+ pdesc = parts[2] if len(parts) > 2 else ""
538
+ # strip optional type wrapping like (int) or [int]
539
+ ptype = ptype.strip("()[]")
540
+ params.append({"name": pname, "type": ptype, "desc": pdesc})
541
+ elif raw.startswith("@returns ") or raw.startswith("@return "):
542
+ rest = raw.split(None, 1)[1].strip() if " " in raw else ""
543
+ parts = rest.split(None, 1)
544
+ rtype = parts[0].strip("()[]") if parts else ""
545
+ rdesc = parts[1] if len(parts) > 1 else ""
546
+ returns = {"type": rtype, "desc": rdesc}
547
+ elif raw.startswith("@deprecated"):
548
+ deprecated = raw[11:].strip()
549
+ elif raw.startswith("@since "):
550
+ since = raw[7:].strip()
551
+ elif raw.startswith("@"):
552
+ tag_parts = raw.split(None, 1)
553
+ raw_tags.append({
554
+ "tag": tag_parts[0][1:],
555
+ "text": tag_parts[1] if len(tag_parts) > 1 else ""
556
+ })
557
+ else:
558
+ description_lines.append(raw)
559
+
560
+ if in_example and example_buf:
561
+ examples.append("\n".join(example_buf).strip())
562
+
563
+ results.append({
564
+ "kind": kind,
565
+ "name": name,
566
+ "description": "\n".join(description_lines).strip(),
567
+ "params": params,
568
+ "returns": returns,
569
+ "examples": examples,
570
+ "deprecated": deprecated,
571
+ "since": since,
572
+ "raw_tags": raw_tags,
573
+ "line": block_start + 1,
574
+ })
575
+ i = j + 1
576
+
577
+ return results
578
+
579
+
580
+ # ── Markdown renderer ─────────────────────────────────────────────────────────
581
+
582
+ def _render_doc_markdown(entries: list, source_file: str = "") -> str:
583
+ """
584
+ v1.9.3: Render extracted doc entries as Markdown.
585
+
586
+ Format:
587
+ # API Reference — filename
588
+ ## `fn name`
589
+ Description
590
+ ### Parameters / Returns / Examples / Deprecated
591
+ """
592
+ fname = os.path.basename(source_file) if source_file else "API"
593
+ stem = fname.replace(".ins", "")
594
+
595
+ lines = [
596
+ f"# API Reference — {stem}",
597
+ f"",
598
+ f"> Generated by InScript {VERSION} ",
599
+ f"> Source: `{source_file}`" if source_file else "",
600
+ f"",
601
+ "---",
602
+ "",
603
+ ]
604
+
605
+ for entry in entries:
606
+ kind = entry["kind"]
607
+ name = entry["name"]
608
+
609
+ # Section header
610
+ lines.append(f"## `{kind} {name}`")
611
+ if entry.get("since"):
612
+ lines.append(f"*Since v{entry['since']}* ")
613
+ if entry.get("deprecated"):
614
+ lines.append(f"")
615
+ lines.append(f"> **Deprecated:** {entry['deprecated']}")
616
+ lines.append("")
617
+
618
+ # Description
619
+ if entry["description"]:
620
+ lines.append(entry["description"])
621
+ lines.append("")
622
+
623
+ # Parameters table
624
+ if entry["params"]:
625
+ lines.append("### Parameters")
626
+ lines.append("")
627
+ lines.append("| Name | Type | Description |")
628
+ lines.append("|------|------|-------------|")
629
+ for p in entry["params"]:
630
+ ptype = f"`{p['type']}`" if p["type"] else "—"
631
+ lines.append(f"| `{p['name']}` | {ptype} | {p['desc']} |")
632
+ lines.append("")
633
+
634
+ # Returns
635
+ if entry.get("returns"):
636
+ r = entry["returns"]
637
+ lines.append("### Returns")
638
+ lines.append("")
639
+ rtype = f"`{r['type']}`" if r["type"] else ""
640
+ rdesc = r["desc"]
641
+ if rtype and rdesc:
642
+ lines.append(f"{rtype} — {rdesc}")
643
+ elif rtype:
644
+ lines.append(rtype)
645
+ else:
646
+ lines.append(rdesc)
647
+ lines.append("")
648
+
649
+ # Examples (runnable code blocks)
650
+ if entry["examples"]:
651
+ lines.append("### Example")
652
+ lines.append("")
653
+ for ex in entry["examples"]:
654
+ lines.append("```inscript")
655
+ lines.append(ex)
656
+ lines.append("```")
657
+ lines.append("")
658
+
659
+ lines.append("---")
660
+ lines.append("")
661
+
662
+ return "\n".join(lines)
663
+
664
+
665
+ # ── File / directory driver ───────────────────────────────────────────────────
666
+
667
+ def _doc_file(filepath: str, output_path: str = None) -> tuple:
668
+ """
669
+ v1.9.3: Extract doc comments from a single .ins file and return
670
+ (entries, markdown_str). Writes to output_path if given.
671
+ """
672
+ try:
673
+ with open(filepath, encoding="utf-8") as f:
674
+ source = f.read()
675
+ except OSError as e:
676
+ print(f"[InScript doc] Cannot read '{filepath}': {e}", file=sys.stderr)
677
+ return [], ""
678
+
679
+ entries = _parse_doc_comments(source)
680
+ markdown = _render_doc_markdown(entries, source_file=filepath)
681
+
682
+ if output_path:
683
+ os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True)
684
+ try:
685
+ with open(output_path, "w", encoding="utf-8") as f:
686
+ f.write(markdown)
687
+ except OSError as e:
688
+ print(f"[InScript doc] Cannot write '{output_path}': {e}", file=sys.stderr)
689
+
690
+ return entries, markdown
691
+
692
+
693
+ def _generate_docs(path: str, output_dir: str = None) -> int:
694
+ """
695
+ v1.9.3: `inscript doc FILE|DIR [--out DIR]`
696
+
697
+ • FILE → print Markdown to stdout (or write to --out/filename.md)
698
+ • DIR → walk all .ins files, write docs/api/<name>.md (or --out/<name>.md)
699
+
700
+ Returns 0 on success, 1 on error.
701
+ """
702
+ if os.path.isfile(path):
703
+ entries, markdown = _doc_file(path)
704
+ if output_dir:
705
+ stem = os.path.basename(path).replace(".ins", "")
706
+ out_md = os.path.join(output_dir, stem + ".md")
707
+ _doc_file(path, output_path=out_md)
708
+ n = len(entries)
709
+ print(f"[InScript doc] '{path}' → '{out_md}' ({n} item{'s' if n!=1 else ''})")
710
+ else:
711
+ print(markdown)
712
+ return 0
713
+
714
+ elif os.path.isdir(path):
715
+ out_base = output_dir or os.path.join(path, "docs", "api")
716
+ ins_files = sorted(_find_ins_files(path))
717
+ if not ins_files:
718
+ print(f"[InScript doc] No .ins files found in '{path}'")
719
+ return 0
720
+ total_entries = 0
721
+ for fpath in ins_files:
722
+ rel = os.path.relpath(fpath, path)
723
+ stem = rel.replace(os.sep, "_").replace(".ins", "")
724
+ out_md = os.path.join(out_base, stem + ".md")
725
+ entries, _ = _doc_file(fpath, output_path=out_md)
726
+ total_entries += len(entries)
727
+ n = len(entries)
728
+ print(f"[InScript doc] {rel} → {os.path.relpath(out_md, path)} ({n} item{'s' if n!=1 else ''})")
729
+ print(f"[InScript doc] Done. {len(ins_files)} file(s), {total_entries} documented item(s).")
730
+ return 0
731
+ else:
732
+ print(f"[InScript doc] Not found: '{path}'", file=sys.stderr)
733
+ return 1
734
+
735
+
736
+
737
+ # ─────────────────────────────────────────────────────────────────────────────
738
+ # v1.9.4 — Spec Freeze & Final Polish
739
+ # ─────────────────────────────────────────────────────────────────────────────
740
+
741
+ # ── Error code catalogue ──────────────────────────────────────────────────────
742
+
743
+ ERROR_CATALOGUE = {
744
+ "E0001": ("LexError", "Unexpected character or malformed token"),
745
+ "E0002": ("UnterminatedString", "String literal has no closing quote"),
746
+ "E0003": ("UnterminatedComment", "Block comment /* ... */ never closed"),
747
+ "E0010": ("ParseError", "Unexpected token or malformed syntax"),
748
+ "E0011": ("MissingClosingBrace", "Expected '}' to close block"),
749
+ "E0012": ("MissingClosingParen", "Expected ')' to close expression"),
750
+ "E0013": ("MissingClosingBracket", "Expected ']' to close array or index"),
751
+ "E0014": ("InvalidAssignTarget", "Left-hand side of assignment is not assignable"),
752
+ "E0015": ("MissingReturnType", "Arrow '->' present but return type missing"),
753
+ "E0020": ("SemanticError", "General semantic / type error"),
754
+ "E0021": ("TypeMismatch", "Value type does not match declared type"),
755
+ "E0022": ("UndefinedName", "Name referenced before declaration"),
756
+ "E0023": ("DuplicateDeclaration", "Name already declared in this scope"),
757
+ "E0024": ("ConstReassign", "Cannot reassign a constant"),
758
+ "E0025": ("ReturnTypeMismatch", "Returned value type differs from declared return type"),
759
+ "E0026": ("ReturnOutsideFn", "return statement outside a function"),
760
+ "E0027": ("BreakOutsideLoop", "break statement outside a loop"),
761
+ "E0028": ("ContinueOutsideLoop", "continue statement outside a loop"),
762
+ "E0029": ("UnknownType", "Type name not found in scope"),
763
+ "E0030": ("RuntimeError", "General runtime error"),
764
+ "E0031": ("DivisionByZero", "Integer or float division by zero"),
765
+ "E0032": ("IndexOutOfBounds", "Array index outside valid range"),
766
+ "E0033": ("KeyNotFound", "Dict key does not exist"),
767
+ "E0034": ("StackOverflow", "Call depth exceeded (infinite recursion)"),
768
+ "E0035": ("TypeError", "Operation applied to incompatible types"),
769
+ "E0036": ("ArithmeticError", "Invalid arithmetic operation"),
770
+ "E0037": ("InvalidCast", "Cannot cast value to target type"),
771
+ "E0038": ("NilDereference", "Method or field access on nil"),
772
+ "E0039": ("ThrowSignal", "Unhandled throw (not an error unless uncaught)"),
773
+ "E0040": ("UncaughtThrow", "throw reached top level without a try/catch"),
774
+ "E0041": ("ImportError", "Module or file could not be imported"),
775
+ "E0042": ("ExportError", "Export of undefined name"),
776
+ "E0043": ("NamespaceError", "Invalid namespace access"),
777
+ "E0044": ("ArgumentCount", "Wrong number of arguments to function call"),
778
+ "E0045": ("MissingField", "Struct initializer missing required field"),
779
+ "E0046": ("UnknownField", "Struct initializer references unknown field"),
780
+ "E0047": ("InterfaceNotImpl", "Struct claims to implement interface but methods missing"),
781
+ "E0048": ("EnumNotExhaustive", "match on enum is missing one or more variants"),
782
+ "E0049": ("NilAccess", "Optional field or nil value accessed without guard"),
783
+ "E0050": ("InvalidReturn", "Return value in void function"),
784
+ "E0051": ("UndeclaredInterface", "implements clause references unknown interface"),
785
+ "E0052": ("CircularImport", "Import creates a circular dependency"),
786
+ "E0053": ("ConstInLoop", "const declaration inside a loop"),
787
+ "E0054": ("NeverNotDiverging", "Function declared -> never but has a non-throwing path"),
788
+ "E0055": ("NullKeyword", "'null' keyword removed in v1.7.4 — use 'nil'"),
789
+ "E0056": ("DivKeyword", "'div' keyword removed in v1.9.5 — use '//' for integer division"),
790
+ }
791
+
792
+
793
+ def _print_error_catalogue(filter_prefix: str = None) -> int:
794
+ """
795
+ v1.9.4: Print the full error code catalogue, optionally filtered by prefix.
796
+ """
797
+ print(f"InScript Error Code Catalogue (InScript {VERSION})")
798
+ print(f"{'='*58}")
799
+ count = 0
800
+ for code, (name, desc) in sorted(ERROR_CATALOGUE.items()):
801
+ if filter_prefix and not code.startswith(filter_prefix):
802
+ continue
803
+ print(f" {code} {name:<28} {desc}")
804
+ count += 1
805
+ print(f"{'='*58}")
806
+ print(f" {count} error code(s)")
807
+ return 0
808
+
809
+
810
+ # ── changelog generator ───────────────────────────────────────────────────────
811
+
812
+ def _parse_version_tuple(v: str) -> tuple:
813
+ """Parse 'v1.6.0' or '1.6.0' → (1, 6, 0)."""
814
+ import re
815
+ m = re.match(r"v?(\d+)\.(\d+)\.(\d+)", v.strip())
816
+ if not m:
817
+ return (0, 0, 0)
818
+ return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
819
+
820
+
821
+ def _generate_changelog_range(spec: str, changelog_path: str = None) -> int:
822
+ """
823
+ v1.9.4: `inscript changelog FROM..TO`
824
+
825
+ Reads CHANGELOG.md and extracts entries whose version falls in [FROM, TO].
826
+ Prints filtered changelog to stdout.
827
+
828
+ Examples:
829
+ inscript changelog v1.6.0..v1.9.4
830
+ inscript changelog v1.8.0..
831
+ inscript changelog ..v1.7.4
832
+ """
833
+ import re
834
+
835
+ # Parse the range spec
836
+ if ".." in spec:
837
+ lo_str, hi_str = spec.split("..", 1)
838
+ lo = _parse_version_tuple(lo_str) if lo_str.strip() else (0, 0, 0)
839
+ hi = _parse_version_tuple(hi_str) if hi_str.strip() else (999, 999, 999)
840
+ else:
841
+ # Single version — exact match
842
+ lo = hi = _parse_version_tuple(spec)
843
+ if lo == (0, 0, 0):
844
+ print("[InScript changelog] Invalid range. Use e.g. v1.6.0..v1.9.4 or v1.9.3",
845
+ file=sys.stderr)
846
+ return 1
847
+
848
+ if lo == (0, 0, 0) and hi == (999, 999, 999):
849
+ print("[InScript changelog] Invalid range. Use e.g. v1.6.0..v1.9.4",
850
+ file=sys.stderr)
851
+ return 1
852
+
853
+ # Find CHANGELOG.md
854
+ if changelog_path is None:
855
+ candidates = [
856
+ os.path.join(os.getcwd(), "CHANGELOG.md"),
857
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "CHANGELOG.md"),
858
+ ]
859
+ changelog_path = next((p for p in candidates if os.path.exists(p)), None)
860
+
861
+ if not changelog_path or not os.path.exists(changelog_path):
862
+ print("[InScript changelog] CHANGELOG.md not found. "
863
+ "Run from the project root.", file=sys.stderr)
864
+ return 1
865
+
866
+ with open(changelog_path, encoding="utf-8") as f:
867
+ content = f.read()
868
+
869
+ # Split into per-version sections by "## [X.Y.Z]" headings
870
+ section_re = re.compile(r"^## \[(\d+\.\d+\.\d+)\]", re.MULTILINE)
871
+ positions = [(m.start(), m.group(1)) for m in section_re.finditer(content)]
872
+
873
+ if not positions:
874
+ print("[InScript changelog] No version sections found in CHANGELOG.md",
875
+ file=sys.stderr)
876
+ return 1
877
+
878
+ # Extract sections that fall within [lo, hi]
879
+ selected = []
880
+ for i, (start, ver_str) in enumerate(positions):
881
+ vt = _parse_version_tuple(ver_str)
882
+ if lo <= vt <= hi:
883
+ end = positions[i + 1][0] if i + 1 < len(positions) else len(content)
884
+ selected.append((vt, ver_str, content[start:end].rstrip()))
885
+
886
+ if not selected:
887
+ lo_s = ".".join(str(x) for x in lo)
888
+ hi_s = ".".join(str(x) for x in hi)
889
+ print(f"[InScript changelog] No versions found in range {lo_s}..{hi_s}")
890
+ return 0
891
+
892
+ # Print newest-first
893
+ selected.sort(key=lambda x: x[0], reverse=True)
894
+ lo_s = ".".join(str(x) for x in lo) if lo != (0,0,0) else "start"
895
+ hi_s = ".".join(str(x) for x in hi) if hi != (999,999,999) else "latest"
896
+ print(f"# InScript Changelog {lo_s}..{hi_s}\n")
897
+ for _, _, section_text in selected:
898
+ print(section_text)
899
+ print()
900
+
901
+ print(f"---")
902
+ print(f"{len(selected)} version(s) shown.")
903
+ return 0
904
+
905
+
906
+ # ── performance benchmark suite ───────────────────────────────────────────────
907
+
908
+ BENCHMARK_PROGRAMS = {
909
+ "fib_30": (
910
+ "Fibonacci fib(30) — recursive, tests call overhead",
911
+ """
912
+ fn fib(n) {
913
+ if n <= 1 { return n }
914
+ return fib(n - 1) + fib(n - 2)
915
+ }
916
+ fib(30)
917
+ """
918
+ ),
919
+ "loop_100k": (
920
+ "Counter loop 100,000 iterations — tests loop + arith throughput",
921
+ """
922
+ let i = 0
923
+ let sum = 0
924
+ while i < 100000 {
925
+ sum = sum + i
926
+ i = i + 1
927
+ }
928
+ sum
929
+ """
930
+ ),
931
+ "struct_heavy": (
932
+ "Struct allocation + field access, 10,000 objects",
933
+ """
934
+ struct Vec2 { x: float = 0.0; y: float = 0.0 }
935
+ fn make_vecs(n) {
936
+ let i = 0
937
+ let last_x = 0.0
938
+ while i < n {
939
+ let v = Vec2{x: i * 1.0, y: i * 2.0}
940
+ last_x = v.x + v.y
941
+ i = i + 1
942
+ }
943
+ return last_x
944
+ }
945
+ make_vecs(10000)
946
+ """
947
+ ),
948
+ "string_concat": (
949
+ "String concatenation, 1,000 iterations",
950
+ """
951
+ let s = ""
952
+ let i = 0
953
+ while i < 1000 {
954
+ s = s + "x"
955
+ i = i + 1
956
+ }
957
+ len(s)
958
+ """
959
+ ),
960
+ "array_ops": (
961
+ "Array push + iteration, 5,000 elements",
962
+ """
963
+ let arr = []
964
+ let i = 0
965
+ while i < 5000 {
966
+ arr.push(i)
967
+ i = i + 1
968
+ }
969
+ arr.len()
970
+ """
971
+ ),
972
+ }
973
+
974
+
975
+ def _run_benchmarks(filter_name: str = None, iterations: int = 3) -> int:
976
+ """
977
+ v1.9.4: `inscript benchmark [NAME]` — run the built-in performance suite.
978
+
979
+ Runs each benchmark `iterations` times and reports median time.
980
+ """
981
+ import time, statistics
982
+
983
+ from interpreter import Interpreter
984
+ from lexer import Lexer
985
+ from parser import Parser
986
+
987
+ programs = BENCHMARK_PROGRAMS
988
+ if filter_name:
989
+ programs = {k: v for k, v in programs.items() if filter_name in k}
990
+ if not programs:
991
+ print(f"[InScript benchmark] No benchmark matching '{filter_name}'",
992
+ file=sys.stderr)
993
+ return 1
994
+
995
+ col_w = max(len(k) for k in programs) + 2
996
+ print(f"\nInScript Benchmark Suite (v{VERSION}) [{iterations} runs each]")
997
+ print(f"{'='*(col_w + 52)}")
998
+ print(f" {'Benchmark':<{col_w}} {'Median':>10} {'Min':>10} {'Max':>10} Description")
999
+ print(f" {'-'*(col_w)} {'-'*10} {'-'*10} {'-'*10} {'-'*30}")
1000
+
1001
+ results = {}
1002
+ for name, (desc, src) in programs.items():
1003
+ times = []
1004
+ for _ in range(iterations):
1005
+ try:
1006
+ interp = Interpreter()
1007
+ tokens = Lexer(src).tokenize()
1008
+ program = Parser(tokens).parse()
1009
+ t0 = time.perf_counter()
1010
+ interp.run(program)
1011
+ times.append(time.perf_counter() - t0)
1012
+ except Exception as e:
1013
+ print(f" {'ERROR':<{col_w}} {name}: {e}", file=sys.stderr)
1014
+ times.append(float("inf"))
1015
+
1016
+ median = statistics.median(times)
1017
+ mn = min(times)
1018
+ mx = max(times)
1019
+
1020
+ def fmt(t):
1021
+ if t == float("inf"): return " ERROR "
1022
+ if t < 1: return f"{t*1000:9.1f}ms"
1023
+ return f"{t:9.3f}s "
1024
+
1025
+ results[name] = median
1026
+ print(f" {name:<{col_w}} {fmt(median)} {fmt(mn)} {fmt(mx)} {desc[:40]}")
1027
+
1028
+ print(f"{'='*(col_w + 52)}")
1029
+ fastest = min(results, key=results.get)
1030
+ slowest = max(k for k,v in results.items() if v != float("inf"))
1031
+ print(f" Fastest: {fastest} ({results[fastest]*1000:.1f}ms median)")
1032
+ print()
1033
+ return 0
1034
+
1035
+
1036
+ # ── stdlib documentation ──────────────────────────────────────────────────────
1037
+
1038
+ STDLIB_SIGNATURES = {
1039
+ # Built-in functions
1040
+ "print": ("(value: any) -> void", "Print value to stdout", "print(42)"),
1041
+ "println": ("(value: any) -> void", "Print value with newline", "println('hello')"),
1042
+ "len": ("(x: array|string) -> int", "Length of array or string", "len([1,2,3]) // 3"),
1043
+ "range": ("(n: int) -> [int]", "Generate 0..n-1 integer array", "range(5) // [0,1,2,3,4]"),
1044
+ "typeof": ("(x: any) -> string", "Runtime type name of value", "typeof(42) // 'int'"),
1045
+ "str": ("(x: any) -> string", "Convert value to string", "str(3.14) // '3.14'"),
1046
+ "int": ("(x: any) -> int", "Parse/cast to integer", "int('42') // 42"),
1047
+ "float": ("(x: any) -> float", "Parse/cast to float", "float('3.14') // 3.14"),
1048
+ "bool": ("(x: any) -> bool", "Cast to boolean", "bool(0) // false"),
1049
+ "abs": ("(x: int|float) -> same", "Absolute value", "abs(-5) // 5"),
1050
+ "min": ("(a, b) -> same", "Smaller of two values", "min(3, 7) // 3"),
1051
+ "max": ("(a, b) -> same", "Larger of two values", "max(3, 7) // 7"),
1052
+ "clamp": ("(v, lo, hi) -> same", "Clamp v to [lo, hi]", "clamp(15, 0, 10) // 10"),
1053
+ "floor": ("(x: float) -> int", "Floor to nearest integer", "floor(3.7) // 3"),
1054
+ "ceil": ("(x: float) -> int", "Ceiling to nearest integer", "ceil(3.2) // 4"),
1055
+ "round": ("(x: float) -> int", "Round to nearest integer", "round(3.5) // 4"),
1056
+ "sqrt": ("(x: float) -> float", "Square root", "sqrt(9.0) // 3.0"),
1057
+ "pow": ("(base, exp) -> float", "Raise base to exponent", "pow(2.0, 8.0) // 256.0"),
1058
+ "sin": ("(x: float) -> float", "Sine (radians)", "sin(0.0) // 0.0"),
1059
+ "cos": ("(x: float) -> float", "Cosine (radians)", "cos(0.0) // 1.0"),
1060
+ "random": ("() -> float", "Random float in [0.0, 1.0)", "random()"),
1061
+ "random_int": ("(lo: int, hi: int) -> int","Random int in [lo, hi]", "random_int(1, 6)"),
1062
+ "assert": ("(cond: bool, msg: string = '') -> void",
1063
+ "Assert condition or throw", "assert(x > 0, 'must be positive')"),
1064
+ "panic": ("(msg: string) -> never", "Throw with message, always fails", "panic('unreachable')"),
1065
+ "exit": ("(code: int = 0) -> never","Exit program with status code", "exit(1)"),
1066
+ "input": ("(prompt: string = '') -> string",
1067
+ "Read line from stdin", "let name = input('Name: ')"),
1068
+ # Array methods
1069
+ "Array.push": ("(v: T) -> void", "Append element", "[].push(1)"),
1070
+ "Array.pop": ("() -> T?", "Remove and return last element", "arr.pop()"),
1071
+ "Array.len": ("() -> int", "Number of elements", "arr.len()"),
1072
+ "Array.map": ("(fn: (T)->U) -> [U]", "Transform each element", "arr.map(fn(x){return x*2})"),
1073
+ "Array.filter": ("(fn: (T)->bool) -> [T]", "Keep matching elements", "arr.filter(fn(x){return x>0})"),
1074
+ "Array.find": ("(fn: (T)->bool) -> T?", "First matching element or nil", "arr.find(fn(x){return x>3})"),
1075
+ "Array.any": ("(fn: (T)->bool) -> bool","True if any element matches", "arr.any(fn(x){return x>0})"),
1076
+ "Array.all": ("(fn: (T)->bool) -> bool","True if all elements match", "arr.all(fn(x){return x>0})"),
1077
+ "Array.each": ("(fn: (T)->void) -> void","Iterate (side-effect only)", "arr.each(fn(x){print(x)})"),
1078
+ "Array.reduce": ("(fn: (U,T)->U, init: U) -> U",
1079
+ "Fold array to single value", "arr.reduce(fn(acc,x){return acc+x}, 0)"),
1080
+ "Array.sorted": ("() -> [T]", "Return sorted copy", "arr.sorted()"),
1081
+ "Array.reversed":"() -> [T]",
1082
+ "Array.join": ("(sep: string) -> string","Join elements with separator", "arr.join(', ')"),
1083
+ "Array.contains":"(v: T) -> bool",
1084
+ "Array.take": ("(n: int) -> [T]", "First n elements", "arr.take(3)"),
1085
+ "Array.skip": ("(n: int) -> [T]", "Skip first n elements", "arr.skip(2)"),
1086
+ # String methods
1087
+ "String.len": ("() -> int", "Length in characters", '"hello".len() // 5'),
1088
+ "String.upper": ("() -> string", "Uppercase copy", '"hi".upper() // "HI"'),
1089
+ "String.lower": ("() -> string", "Lowercase copy", '"HI".lower() // "hi"'),
1090
+ "String.trim": ("() -> string", "Strip leading/trailing whitespace",'" x ".trim() // "x"'),
1091
+ "String.split": ('(sep: string) -> [string]', "Split on separator", '"a,b".split(",") // ["a","b"]'),
1092
+ "String.contains": ("(sub: string) -> bool", "True if substring found", '"hello".contains("ell")'),
1093
+ "String.starts_with":("(pre: string) -> bool", "True if starts with prefix", '"hello".starts_with("he")'),
1094
+ "String.ends_with": ("(suf: string) -> bool", "True if ends with suffix", '"hello".ends_with("lo")'),
1095
+ "String.replace": ("(old: string, new: string) -> string",
1096
+ "Replace first occurrence", '"hello".replace("l","r")'),
1097
+ "String.chars": ("() -> [string]", "Split into individual characters","'ab'.chars() // ['a','b']"),
1098
+ "String.to_int": ("() -> int?", "Parse as integer or nil", '"42".to_int() // 42'),
1099
+ "String.to_float": ("() -> float?", "Parse as float or nil", '"3.14".to_float()'),
1100
+ }
1101
+
1102
+
1103
+ def _print_stdlib_docs(filter_str: str = None) -> int:
1104
+ """
1105
+ v1.9.4: `inscript stdlib [FILTER]` — print stdlib function signatures.
1106
+ """
1107
+ entries = STDLIB_SIGNATURES
1108
+ if filter_str:
1109
+ entries = {k: v for k, v in entries.items()
1110
+ if filter_str.lower() in k.lower()}
1111
+ if not entries:
1112
+ print(f"[InScript stdlib] No matches for '{filter_str}'")
1113
+ return 0
1114
+
1115
+ print(f"\nInScript Standard Library (v{VERSION})")
1116
+ print(f"{'='*70}")
1117
+
1118
+ last_group = None
1119
+ for name, info in sorted(entries.items()):
1120
+ group = name.split(".")[0] if "." in name else "Built-ins"
1121
+ if group != last_group:
1122
+ print(f"\n ── {group} ─────")
1123
+ last_group = group
1124
+
1125
+ if isinstance(info, str):
1126
+ # Abbreviated entry (just signature string)
1127
+ print(f" {name:<22} {info}")
1128
+ elif len(info) == 2:
1129
+ sig, desc = info
1130
+ print(f" {name:<22} {sig}")
1131
+ print(f" {'':22} # {desc}")
1132
+ else:
1133
+ sig, desc, example = info
1134
+ print(f" {name:<22} {sig}")
1135
+ print(f" {'':22} # {desc}")
1136
+ print(f" {'':22} {example}")
1137
+
1138
+ print(f"\n{'='*70}")
1139
+ total = len(entries)
1140
+ print(f" {total} stdlib item(s)")
1141
+ return 0
1142
+
1143
+
1144
+ # ── spec document generator ───────────────────────────────────────────────────
1145
+
1146
+ LANGUAGE_SPEC = """# InScript Language Specification
1147
+ ## Version {version}
1148
+
1149
+ > **Spec Freeze**: No new syntax will be added after v1.9.4.
1150
+ > Only bug fixes and performance improvements until v2.0.0.
1151
+
1152
+ ---
1153
+
1154
+ ## 1. Lexical Structure
1155
+
1156
+ ### 1.1 Comments
1157
+ - Line comments: `// text`
1158
+ - Block comments: `/* text */` (nestable)
1159
+ - Doc comments: `/// text` (parsed by `inscript doc`)
1160
+
1161
+ ### 1.2 Keywords
1162
+ ```
1163
+ let const fn return if else while for in match case
1164
+ struct enum interface type impl mixin extends implements
1165
+ true false nil throw try catch break continue defer yield
1166
+ pub async await scene import export with super self is as
1167
+ div not and or
1168
+ ```
1169
+
1170
+ ### 1.3 Literals
1171
+ - **Integer**: `42`, `1_000_000`
1172
+ - **Float**: `3.14`, `1.0e-3`
1173
+ - **String**: `"hello"`, `'world'`, escape sequences `\n \t \\ \"`
1174
+ - **Bool**: `true`, `false`
1175
+ - **Nil**: `nil`
1176
+
1177
+ ---
1178
+
1179
+ ## 2. Types
1180
+
1181
+ ### 2.1 Primitive Types
1182
+ `int` `float` `bool` `string` `nil` `void` `any` `never`
1183
+
1184
+ ### 2.2 Composite Types
1185
+ - **Array**: `[T]` — e.g. `[int]`, `[string]`
1186
+ - **Dict**: `{K: V}` — e.g. `{string: int}`
1187
+ - **Optional**: `T?` — sugar for `T | nil`
1188
+ - **Union**: `A | B | C`
1189
+ - **Function**: `fn(T1, T2) -> R`
1190
+
1191
+ ### 2.3 User-Defined Types
1192
+ - **Struct**: `struct Name {{ fields; methods }}`
1193
+ - **Enum**: `enum Name {{ Variant1; Variant2 }}`
1194
+ - **Interface**: `interface Name {{ fn method() -> R }}`
1195
+ - **Type alias**: `type ID = int`
1196
+
1197
+ ---
1198
+
1199
+ ## 3. Declarations
1200
+
1201
+ ### 3.1 Variables
1202
+ ```inscript
1203
+ let x: int = 42
1204
+ const MAX: int = 100
1205
+ let items: [string] = []
1206
+ ```
1207
+
1208
+ ### 3.2 Functions
1209
+ ```inscript
1210
+ fn add(a: int, b: int) -> int {{
1211
+ return a + b
1212
+ }}
1213
+ fn greet(name: string = "World") {{
1214
+ print("Hello, " + name)
1215
+ }}
1216
+ ```
1217
+
1218
+ ### 3.3 Structs
1219
+ ```inscript
1220
+ struct Player {{
1221
+ name: string = ""
1222
+ health: int = 100
1223
+ fn take_damage(dmg: int) {{
1224
+ self.health = self.health - dmg
1225
+ }}
1226
+ }}
1227
+ ```
1228
+
1229
+ ### 3.4 Enums
1230
+ ```inscript
1231
+ enum Direction {{ North; South; East; West }}
1232
+ ```
1233
+
1234
+ ### 3.5 Interfaces
1235
+ ```inscript
1236
+ interface Drawable {{
1237
+ fn draw() -> void
1238
+ }}
1239
+ struct Sprite implements Drawable {{
1240
+ fn draw() {{ print("drawing") }}
1241
+ }}
1242
+ ```
1243
+
1244
+ ---
1245
+
1246
+ ## 4. Control Flow
1247
+
1248
+ ```inscript
1249
+ if cond {{ }} else if cond {{ }} else {{ }}
1250
+ while cond {{ }}
1251
+ for item in collection {{ }}
1252
+ match value {{
1253
+ case Pattern {{ }}
1254
+ case _ {{ }} // wildcard
1255
+ }}
1256
+ ```
1257
+
1258
+ ---
1259
+
1260
+ ## 5. Error Handling
1261
+
1262
+ ```inscript
1263
+ try {{
1264
+ risky()
1265
+ }} catch e {{
1266
+ print("caught: " + e)
1267
+ }}
1268
+ throw "something went wrong"
1269
+ fn fatal() -> never {{ throw "fatal" }}
1270
+ ```
1271
+
1272
+ ---
1273
+
1274
+ ## 6. Module System
1275
+
1276
+ ```inscript
1277
+ import "path/to/module"
1278
+ export fn public_api() {{ }}
1279
+ ```
1280
+
1281
+ ---
1282
+
1283
+ ## 7. Breaking Changes (v2.0.0 preview)
1284
+
1285
+ - `null` removed — use `nil`
1286
+ - `div` operator removed — use `//`
1287
+ - Bare `array` type without element type is an error
1288
+ - `--no-typecheck` CLI flag removed — use `--unsafe-no-check`
1289
+
1290
+ ---
1291
+
1292
+ *Generated by InScript {version}*
1293
+ """
1294
+
1295
+
1296
+ def _generate_spec(output_path: str = None) -> int:
1297
+ """
1298
+ v1.9.4: `inscript spec [--out FILE]` — print the language spec document.
1299
+ """
1300
+ content = LANGUAGE_SPEC.replace("{version}", VERSION)
1301
+ if output_path:
1302
+ os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
1303
+ try:
1304
+ with open(output_path, "w", encoding="utf-8") as f:
1305
+ f.write(content)
1306
+ print(f"[InScript spec] Spec written to '{output_path}'")
1307
+ return 0
1308
+ except OSError as e:
1309
+ print(f"[InScript spec] Cannot write: {e}", file=sys.stderr)
1310
+ return 1
1311
+ else:
1312
+ print(content)
1313
+ return 0
1314
+
438
1315
  def run_source(source: str, filename: str = "<stdin>",
439
1316
  type_check: bool = True,
440
1317
  no_warn: bool = False,
@@ -694,6 +1571,20 @@ Examples:
694
1571
  help="v1.9.2: Validate inscript.toml (default: current dir)")
695
1572
  parser.add_argument("--lock", metavar="DIR", nargs="?", const=".",
696
1573
  help="v1.9.2: Generate inscript.lock from inscript.toml")
1574
+ parser.add_argument("--doc", metavar="FILE_OR_DIR",
1575
+ help="v1.9.3: Generate Markdown docs from /// doc comments")
1576
+ parser.add_argument("--doc-out", metavar="DIR", default=None,
1577
+ help="v1.9.3: Output directory for --doc (default: stdout / docs/api/)")
1578
+ parser.add_argument("--errors", metavar="PREFIX", nargs="?", const="",
1579
+ help="v1.9.4: Print error code catalogue (optional prefix filter, e.g. E003)")
1580
+ parser.add_argument("--changelog", metavar="RANGE",
1581
+ help="v1.9.4: Print changelog for version range e.g. v1.6.0..v1.9.4")
1582
+ parser.add_argument("--benchmark", metavar="NAME", nargs="?", const="",
1583
+ help="v1.9.4: Run performance benchmark suite (optional name filter)")
1584
+ parser.add_argument("--stdlib", metavar="FILTER", nargs="?", const="",
1585
+ help="v1.9.4: Print stdlib function signatures (optional filter)")
1586
+ parser.add_argument("--spec", metavar="FILE", nargs="?", const=None,
1587
+ help="v1.9.4: Print language spec (optional --spec FILE to write to file)")
697
1588
  parser.add_argument("--strict", action="store_true",
698
1589
  help="v1.6.0: Strict mode — all warnings become errors, no implicit any")
699
1590
  parser.add_argument("--tokens", action="store_true", help="Print lexer token stream")
@@ -859,6 +1750,19 @@ Examples:
859
1750
  return _validate_manifest(args.validate)
860
1751
  if getattr(args, 'lock', None) is not None:
861
1752
  return _generate_lockfile(args.lock)
1753
+ if getattr(args, 'doc', None):
1754
+ return _generate_docs(args.doc, output_dir=getattr(args, 'doc_out', None))
1755
+ if getattr(args, 'errors', None) is not None:
1756
+ return _print_error_catalogue(filter_prefix=args.errors or None)
1757
+ if getattr(args, 'changelog', None):
1758
+ return _generate_changelog_range(args.changelog)
1759
+ if getattr(args, 'benchmark', None) is not None:
1760
+ return _run_benchmarks(filter_name=args.benchmark or None)
1761
+ if getattr(args, 'stdlib', None) is not None:
1762
+ return _print_stdlib_docs(filter_str=args.stdlib or None)
1763
+ if getattr(args, 'spec', None) is not None or '--spec' in sys.argv:
1764
+ out = args.spec if hasattr(args, 'spec') and isinstance(args.spec, str) else None
1765
+ return _generate_spec(output_path=out)
862
1766
 
863
1767
  # v1.9.1: --no-typecheck is deprecated — emit a warning and honour it
864
1768
  if getattr(args, 'no_typecheck', False):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inscript-lang
3
- Version: 1.9.2
3
+ Version: 1.9.5
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
@@ -1610,7 +1610,13 @@ class Parser:
1610
1610
  left = self.parse_unary() # BUG-09 fix: call parse_unary (was parse_power)
1611
1611
  while self.check(TT.STAR, TT.SLASH, TT.SLASH_SLASH, TT.PERCENT) or self.check(TT.DIV):
1612
1612
  op_tok = self.advance()
1613
- op = "//" if op_tok.type == TT.DIV else op_tok.value
1613
+ if op_tok.type == TT.DIV:
1614
+ self._error(
1615
+ "[E0056] 'div' was removed in v1.9.5 — use '//' for integer division. "
1616
+ "Run: inscript migrate <file> to auto-fix.",
1617
+ op_tok
1618
+ )
1619
+ op = op_tok.value
1614
1620
  right = self.parse_unary()
1615
1621
  left = BinaryExpr(left=left, op=op, right=right,
1616
1622
  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.9.2"
7
+ version = "1.9.5"
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.9.2"
43
+ VERSION = "1.9.5"
44
44
 
45
45
  # ── ANSI colours ──────────────────────────────────────────────────────────────
46
46
  def _c(code, text):
@@ -4,14 +4,26 @@ InScript — setup.py
4
4
  pyproject.toml is the canonical config for v1.0.23+
5
5
  This file is kept for compatibility with older pip versions.
6
6
 
7
- Upgrade from v1.0.0: pip install --upgrade inscript-lang
8
- Publish: python -m build && python -m twine upload dist/*
7
+ VERSION is read dynamically from inscript.py so setup.py, pyproject.toml,
8
+ and inscript.py always agree no more manual sync needed.
9
9
  """
10
- from setuptools import setup, find_packages
10
+ import re
11
+ from pathlib import Path
12
+ from setuptools import setup
13
+
14
+ def _read_version():
15
+ src = Path(__file__).parent / "inscript.py"
16
+ m = re.search(r'^VERSION\s*=\s*["\']([^"\']+)["\']',
17
+ src.read_text(encoding="utf-8"), re.M)
18
+ if not m:
19
+ raise RuntimeError("VERSION not found in inscript.py")
20
+ return m.group(1)
21
+
22
+ VERSION = _read_version()
11
23
 
12
24
  setup(
13
25
  name = "inscript-lang",
14
- version = "1.7.2",
26
+ version = VERSION,
15
27
  author = "Shreyasi Sarkar",
16
28
  description = "InScript — a game-focused scripting language for 2D games",
17
29
  long_description = open("README.md", encoding="utf-8").read(),
@@ -24,6 +36,9 @@ setup(
24
36
  "Intended Audience :: Developers",
25
37
  "License :: OSI Approved :: MIT License",
26
38
  "Programming Language :: Python :: 3",
39
+ "Programming Language :: Python :: 3.10",
40
+ "Programming Language :: Python :: 3.11",
41
+ "Programming Language :: Python :: 3.12",
27
42
  "Topic :: Games/Entertainment",
28
43
  "Topic :: Software Development :: Interpreters",
29
44
  ],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes