inscript-lang 1.9.14__tar.gz → 2.0.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.
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/PKG-INFO +1 -1
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/analyzer.py +37 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/ast_nodes.py +6 -4
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript.py +12 -10
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/interpreter.py +7 -1
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/lexer.py +7 -1
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/parser.py +8 -2
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/pyproject.toml +1 -1
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/repl.py +1 -1
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/stdlib_values.py +13 -4
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/README.md +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/compiler.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/environment.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/errors.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript_fmt.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/inscript_test.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/pygame_backend.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/setup.cfg +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/setup.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/stdlib.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/stdlib_extended.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/stdlib_extended_2.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/stdlib_game.py +0 -0
- {inscript_lang-1.9.14 → inscript_lang-2.0.1}/vm.py +0 -0
|
@@ -1160,6 +1160,37 @@ class Analyzer(Visitor):
|
|
|
1160
1160
|
def visit_BoolLiteralExpr(self, node: BoolLiteralExpr) -> InScriptType: return T_BOOL
|
|
1161
1161
|
def visit_NullLiteralExpr(self, node: NullLiteralExpr) -> InScriptType: return T_NULL
|
|
1162
1162
|
|
|
1163
|
+
def visit_FStringExpr(self, node) -> InScriptType:
|
|
1164
|
+
"""v1.9.15: $"..." and f"..." interpolated strings always produce T_STRING.
|
|
1165
|
+
Type-check each {expr} segment by re-parsing and visiting it.
|
|
1166
|
+
"""
|
|
1167
|
+
import re
|
|
1168
|
+
template = node.template
|
|
1169
|
+
for m in re.finditer(r'(?<!\x00)\{([^}\x00][^}]*)\}', template):
|
|
1170
|
+
inner = m.group(1).strip()
|
|
1171
|
+
# Strip optional format spec :spec
|
|
1172
|
+
depth = 0; split_at = -1; ternary_depth = 0
|
|
1173
|
+
for i, ch in enumerate(inner):
|
|
1174
|
+
if ch in '([{': depth += 1
|
|
1175
|
+
elif ch in ')]}': depth -= 1
|
|
1176
|
+
elif ch == '?' and depth == 0: ternary_depth += 1
|
|
1177
|
+
elif ch == ':' and depth == 0:
|
|
1178
|
+
if ternary_depth > 0: ternary_depth -= 1
|
|
1179
|
+
else: split_at = i; break
|
|
1180
|
+
expr_src = inner[:split_at].strip() if split_at > 0 else inner
|
|
1181
|
+
try:
|
|
1182
|
+
from parser import parse
|
|
1183
|
+
prog = parse(expr_src)
|
|
1184
|
+
# visit each stmt in a suppressed-error sub-context
|
|
1185
|
+
saved = self._errors; self._errors = []
|
|
1186
|
+
try:
|
|
1187
|
+
for stmt in prog.body: self.visit(stmt)
|
|
1188
|
+
finally:
|
|
1189
|
+
self._errors = saved
|
|
1190
|
+
except Exception:
|
|
1191
|
+
pass # parse errors in sub-expr are caught at runtime
|
|
1192
|
+
return T_STRING
|
|
1193
|
+
|
|
1163
1194
|
def visit_IdentExpr(self, node: IdentExpr) -> InScriptType:
|
|
1164
1195
|
sym = self._lookup(node.name, node.line, node.col)
|
|
1165
1196
|
return sym.type_
|
|
@@ -1741,6 +1772,12 @@ class Analyzer(Visitor):
|
|
|
1741
1772
|
if s not in (T_INT, T_ANY) or e not in (T_INT, T_ANY):
|
|
1742
1773
|
self._error(f"Range bounds must be int, got '{s}' and '{e}'",
|
|
1743
1774
|
node.line, node.col)
|
|
1775
|
+
# v2.0.1: validate optional step expression
|
|
1776
|
+
if node.step is not None:
|
|
1777
|
+
st = self.visit(node.step)
|
|
1778
|
+
if st not in (T_INT, T_ANY):
|
|
1779
|
+
self._error(f"Range step must be int, got '{st}'",
|
|
1780
|
+
node.line, node.col)
|
|
1744
1781
|
return InScriptType("Range", [T_INT])
|
|
1745
1782
|
|
|
1746
1783
|
def visit_LambdaExpr(self, node: LambdaExpr) -> InScriptType:
|
|
@@ -195,10 +195,11 @@ class LambdaExpr(Node):
|
|
|
195
195
|
|
|
196
196
|
@dataclass
|
|
197
197
|
class RangeExpr(Node):
|
|
198
|
-
"""0..10 (exclusive) | 0..=10 (inclusive)"""
|
|
198
|
+
"""0..10 (exclusive) | 0..=10 (inclusive) | 0..10 step 2 (v2.0.1)"""
|
|
199
199
|
start: Node
|
|
200
200
|
end: Node
|
|
201
|
-
inclusive: bool
|
|
201
|
+
inclusive: bool = False
|
|
202
|
+
step: object = None # None = auto (1, or -1 if start > end)
|
|
202
203
|
|
|
203
204
|
|
|
204
205
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -560,10 +561,11 @@ class PropertyDecl(Node):
|
|
|
560
561
|
|
|
561
562
|
@dataclass
|
|
562
563
|
class RangeExpr(Node):
|
|
563
|
-
"""0..10 (exclusive) | 0..=10 (inclusive)"""
|
|
564
|
+
"""0..10 (exclusive) | 0..=10 (inclusive) | 0..10 step 2 (v2.0.1)"""
|
|
564
565
|
start: Node
|
|
565
566
|
end: Node
|
|
566
|
-
inclusive: bool
|
|
567
|
+
inclusive: bool = False
|
|
568
|
+
step: object = None # None = auto (1 ascending, -1 descending)
|
|
567
569
|
|
|
568
570
|
@dataclass
|
|
569
571
|
class LambdaExpr(Node):
|
|
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
|
|
|
24
24
|
SemanticError, InScriptRuntimeError,
|
|
25
25
|
MultiError, InScriptWarning)
|
|
26
26
|
|
|
27
|
-
VERSION = "
|
|
27
|
+
VERSION = "2.0.1"
|
|
28
28
|
|
|
29
29
|
MANIFEST_FILENAME = "inscript.toml"
|
|
30
30
|
LOCK_FILENAME = "inscript.lock"
|
|
@@ -703,9 +703,9 @@ def _check_v2_readiness(project_dir: str = ".") -> int:
|
|
|
703
703
|
for fpath in ins_files:
|
|
704
704
|
try:
|
|
705
705
|
src = open(fpath, encoding="utf-8").read()
|
|
706
|
-
#
|
|
706
|
+
# Only flag STANDALONE // lines — inline // is always floor division in v1.9.6+
|
|
707
707
|
bad = [i+1 for i, l in enumerate(src.splitlines())
|
|
708
|
-
if _re.match(r'^\s*//', l)
|
|
708
|
+
if _re.match(r'^\s*//', l)]
|
|
709
709
|
if bad:
|
|
710
710
|
comment_hits.append((fpath, bad))
|
|
711
711
|
except OSError:
|
|
@@ -785,9 +785,11 @@ def _migrate_files(path: str) -> int:
|
|
|
785
785
|
# v1.9.6: // line comments → # line comments (MUST run before div→// rewrite)
|
|
786
786
|
# Rule 1: standalone comment lines — optional whitespace then //
|
|
787
787
|
src = re.sub(r'^(\s*)//', r'\1#', src, flags=re.MULTILINE)
|
|
788
|
-
# Rule 2: inline
|
|
789
|
-
#
|
|
790
|
-
|
|
788
|
+
# Rule 2: inline trailing comment — `; // word` or `} // word` → `; # word`
|
|
789
|
+
# v1.9.15: ONLY fires after statement terminators (;, {, }),
|
|
790
|
+
# NOT after expression-ending chars like ), ], identifiers, digits.
|
|
791
|
+
# This prevents floor-division like `(a*b) // gcd(a,b)` being mangled.
|
|
792
|
+
src = re.sub(r'([;{}])( *)//(\s+[A-Za-z_])', r'\1\2#\3', src)
|
|
791
793
|
# Rule 3: warn about ambiguous ` // <digit>` patterns (may be comment or floor-div)
|
|
792
794
|
ambiguous = re.findall(r'(\s)//(\s+\d)', src)
|
|
793
795
|
if ambiguous:
|
|
@@ -2335,10 +2337,6 @@ Examples:
|
|
|
2335
2337
|
profile=profile)
|
|
2336
2338
|
|
|
2337
2339
|
|
|
2338
|
-
if __name__ == "__main__":
|
|
2339
|
-
sys.exit(main())
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
2340
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
2343
2341
|
# v1.9.2 — Package Manifest Foundation
|
|
2344
2342
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -2690,3 +2688,7 @@ def _generate_lockfile(directory: str = ".") -> int:
|
|
|
2690
2688
|
except OSError as e:
|
|
2691
2689
|
print(f"[InScript lock] Cannot write lockfile: {e}", file=sys.stderr)
|
|
2692
2690
|
return 1
|
|
2691
|
+
|
|
2692
|
+
|
|
2693
|
+
if __name__ == "__main__":
|
|
2694
|
+
sys.exit(main())
|
|
@@ -2470,7 +2470,13 @@ class Interpreter(Visitor):
|
|
|
2470
2470
|
def visit_RangeExpr(self, node: RangeExpr) -> Any:
|
|
2471
2471
|
start = self.visit(node.start)
|
|
2472
2472
|
end = self.visit(node.end)
|
|
2473
|
-
|
|
2473
|
+
# v2.0.1: optional `step` expression
|
|
2474
|
+
if node.step is not None:
|
|
2475
|
+
step = self.visit(node.step)
|
|
2476
|
+
else:
|
|
2477
|
+
# Auto-descend: if start > end, default step to -1
|
|
2478
|
+
step = -1 if int(start) > int(end) else 1
|
|
2479
|
+
return InScriptRange(start, end, step=step, inclusive=node.inclusive)
|
|
2474
2480
|
|
|
2475
2481
|
def visit_AwaitExpr(self, node: AwaitExpr) -> Any:
|
|
2476
2482
|
"""
|
|
@@ -365,7 +365,7 @@ class Lexer:
|
|
|
365
365
|
self._error("Unterminated triple-quoted string", sl, sc)
|
|
366
366
|
|
|
367
367
|
def _scan_fstring(self, quote: str, sl: int, sc: int):
|
|
368
|
-
"""Scan f"...{expr}..." — store the raw template as FSTRING token.
|
|
368
|
+
"""Scan f"...{expr}..." or $"...{expr}..." — store the raw template as FSTRING token. v1.9.15: $"..." is the new preferred syntax; both prefixes share this scanner.
|
|
369
369
|
{{ and }} are literal brace escapes.
|
|
370
370
|
Inside {}, quote characters are allowed (e.g. d["key"])."""
|
|
371
371
|
chars = []
|
|
@@ -580,6 +580,12 @@ class Lexer:
|
|
|
580
580
|
elif ch == "#":
|
|
581
581
|
# v1.9.6: # is the line-comment character (was: HASH annotation token)
|
|
582
582
|
self._skip_line_comment(); return
|
|
583
|
+
elif ch == "$":
|
|
584
|
+
# v1.9.15: $"..." interpolated string (preferred over f"...")
|
|
585
|
+
if self.current in ('"', "'"):
|
|
586
|
+
self._scan_fstring(self.advance(), sl, sc)
|
|
587
|
+
return
|
|
588
|
+
# bare $ not followed by quote — skip (not used otherwise)
|
|
583
589
|
elif ch == "@": emit(TT.AT, "@")
|
|
584
590
|
elif ch == "(": emit(TT.LPAREN)
|
|
585
591
|
elif ch == ")": emit(TT.RPAREN)
|
|
@@ -1555,13 +1555,19 @@ class Parser:
|
|
|
1555
1555
|
line, col = self._pos()
|
|
1556
1556
|
left = self.parse_shift()
|
|
1557
1557
|
|
|
1558
|
-
# Range: start..end or start..=end
|
|
1558
|
+
# Range: start..end or start..=end or start..end step n (v2.0.1)
|
|
1559
1559
|
if self.check(TT.DOTDOT, TT.DOTDOT_EQ):
|
|
1560
1560
|
inclusive = (self.current.type == TT.DOTDOT_EQ)
|
|
1561
1561
|
self.advance()
|
|
1562
1562
|
right = self.parse_shift()
|
|
1563
|
+
# Optional `step` suffix: 0..20 step 2
|
|
1564
|
+
step_expr = None
|
|
1565
|
+
if (self.current and self.current.type == TT.IDENT
|
|
1566
|
+
and self.current.value == "step"):
|
|
1567
|
+
self.advance() # consume 'step'
|
|
1568
|
+
step_expr = self.parse_shift()
|
|
1563
1569
|
return RangeExpr(start=left, end=right, inclusive=inclusive,
|
|
1564
|
-
line=line, col=col)
|
|
1570
|
+
step=step_expr, line=line, col=col)
|
|
1565
1571
|
|
|
1566
1572
|
# Membership: x in collection / x not in collection
|
|
1567
1573
|
if self.check(TT.IN):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "inscript-lang"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.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 = "
|
|
43
|
+
VERSION = "2.0.1"
|
|
44
44
|
|
|
45
45
|
# ── ANSI colours ──────────────────────────────────────────────────────────────
|
|
46
46
|
def _c(code, text):
|
|
@@ -281,16 +281,25 @@ class InScriptRange:
|
|
|
281
281
|
self.inclusive = inclusive
|
|
282
282
|
|
|
283
283
|
def __iter__(self):
|
|
284
|
-
|
|
284
|
+
# v2.0.1: support negative step (descending ranges)
|
|
285
|
+
if self.step > 0:
|
|
286
|
+
stop = self.end + 1 if self.inclusive else self.end
|
|
287
|
+
else:
|
|
288
|
+
stop = self.end - 1 if self.inclusive else self.end
|
|
285
289
|
return iter(range(self.start, stop, self.step))
|
|
286
290
|
|
|
287
291
|
def __len__(self):
|
|
288
|
-
|
|
289
|
-
|
|
292
|
+
if self.step > 0:
|
|
293
|
+
stop = self.end + 1 if self.inclusive else self.end
|
|
294
|
+
return max(0, (stop - self.start + self.step - 1) // self.step)
|
|
295
|
+
else:
|
|
296
|
+
stop = self.end - 1 if self.inclusive else self.end
|
|
297
|
+
return max(0, (self.start - stop + (-self.step) - 1) // (-self.step))
|
|
290
298
|
|
|
291
299
|
def __repr__(self):
|
|
292
300
|
op = "..=" if self.inclusive else ".."
|
|
293
|
-
|
|
301
|
+
s = f" step {self.step}" if self.step not in (1, -1) else ("" if self.step == 1 else " step -1")
|
|
302
|
+
return f"{self.start}{op}{self.end}{s}"
|
|
294
303
|
|
|
295
304
|
|
|
296
305
|
class InScriptGenerator:
|
|
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
|
|
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
|
|
File without changes
|