inscript-lang 2.3.0__tar.gz → 2.4.0__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-2.3.0 → inscript_lang-2.4.0}/PKG-INFO +1 -1
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript.py +174 -4
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/interpreter.py +38 -20
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/pyproject.toml +1 -1
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/repl.py +1 -1
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/README.md +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/analyzer.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/ast_nodes.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/compiler.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/environment.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/errors.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript_fmt.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/inscript_test.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/lexer.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/parser.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/pygame_backend.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/setup.cfg +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/setup.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/stdlib.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/stdlib_extended.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/stdlib_extended_2.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/stdlib_game.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/stdlib_values.py +0 -0
- {inscript_lang-2.3.0 → inscript_lang-2.4.0}/vm.py +0 -0
|
@@ -24,7 +24,7 @@ from errors import (InScriptError, LexerError, ParseError,
|
|
|
24
24
|
SemanticError, InScriptRuntimeError,
|
|
25
25
|
MultiError, InScriptWarning)
|
|
26
26
|
|
|
27
|
-
VERSION = "2.
|
|
27
|
+
VERSION = "2.4.0"
|
|
28
28
|
|
|
29
29
|
MANIFEST_FILENAME = "inscript.toml"
|
|
30
30
|
LOCK_FILENAME = "inscript.lock"
|
|
@@ -389,19 +389,166 @@ def info_package(pkg_name: str) -> int:
|
|
|
389
389
|
|
|
390
390
|
|
|
391
391
|
def run_file(path: str, type_check: bool = True) -> int:
|
|
392
|
-
"""Load and execute an InScript source file. Returns exit code."""
|
|
392
|
+
"""Load and execute an InScript source file (or .ibc bytecode). Returns exit code."""
|
|
393
393
|
if not os.path.exists(path):
|
|
394
394
|
print(f"[InScript] Error: file not found: '{path}'", file=sys.stderr)
|
|
395
395
|
return 1
|
|
396
|
+
# v2.4.0: transparent .ibc execution via bytecode VM
|
|
397
|
+
if path.endswith(".ibc"):
|
|
398
|
+
return _run_ibc(path)
|
|
396
399
|
if not path.endswith(".ins"):
|
|
397
400
|
print(f"[InScript] Warning: expected .ins extension, got '{path}'", file=sys.stderr)
|
|
398
|
-
|
|
399
401
|
with open(path, "r", encoding="utf-8") as f:
|
|
400
402
|
source = f.read()
|
|
401
|
-
|
|
402
403
|
return run_source(source, filename=path, type_check=type_check)
|
|
403
404
|
|
|
404
405
|
|
|
406
|
+
# ── v2.4.0: Real Bytecode Pipeline ───────────────────────────────────────────
|
|
407
|
+
|
|
408
|
+
def _ibc_path_for(src: str, out: str | None = None) -> str:
|
|
409
|
+
if out:
|
|
410
|
+
return out
|
|
411
|
+
base = src[:-4] if src.endswith(".ins") else src
|
|
412
|
+
return base + ".ibc"
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _compile_cmd(src_path: str, out_path, target: str,
|
|
416
|
+
dce: bool, incremental: bool) -> int:
|
|
417
|
+
"""v2.4.0: AOT-compile .ins → .ibc using the real register-based VM compiler."""
|
|
418
|
+
if not os.path.exists(src_path):
|
|
419
|
+
print(f"[InScript] compile: file not found: '{src_path}'", file=sys.stderr)
|
|
420
|
+
return 1
|
|
421
|
+
|
|
422
|
+
# WASM / native — honest stubs with roadmap info
|
|
423
|
+
if target in ("wasm", "native"):
|
|
424
|
+
print(f"[InScript] --target {target} is planned for v2.5.x.")
|
|
425
|
+
print(f" WASM output: requires LLVM/Emscripten toolchain")
|
|
426
|
+
print(f" Native output: requires transpile-to-C pipeline")
|
|
427
|
+
print(f" Today's target: inscript --compile {src_path} --target ibc")
|
|
428
|
+
return 0
|
|
429
|
+
|
|
430
|
+
dest = _ibc_path_for(src_path, out_path)
|
|
431
|
+
|
|
432
|
+
# Incremental: skip if .ibc is up to date
|
|
433
|
+
if incremental and os.path.exists(dest):
|
|
434
|
+
if os.path.getmtime(dest) >= os.path.getmtime(src_path):
|
|
435
|
+
print(f"[InScript] compile: {dest} is up to date (--incremental)")
|
|
436
|
+
return 0
|
|
437
|
+
|
|
438
|
+
with open(src_path, "r", encoding="utf-8") as f:
|
|
439
|
+
source = f.read()
|
|
440
|
+
|
|
441
|
+
# Parse + compile to FnProto bytecode
|
|
442
|
+
try:
|
|
443
|
+
from compiler import compile_source
|
|
444
|
+
from vm_code import write_ibc
|
|
445
|
+
proto = compile_source(source, filename=src_path)
|
|
446
|
+
except Exception as e:
|
|
447
|
+
print(f"[InScript] compile error: {e}", file=sys.stderr)
|
|
448
|
+
return 1
|
|
449
|
+
|
|
450
|
+
# Dead-code elimination at the proto level
|
|
451
|
+
if dce:
|
|
452
|
+
n_removed = _dce_proto(proto)
|
|
453
|
+
if n_removed:
|
|
454
|
+
print(f"[InScript] DCE: pruned {n_removed} unreachable nested proto(s)")
|
|
455
|
+
|
|
456
|
+
# Write .ibc
|
|
457
|
+
try:
|
|
458
|
+
write_ibc(proto, dest)
|
|
459
|
+
except Exception as e:
|
|
460
|
+
print(f"[InScript] serialize error: {e}", file=sys.stderr)
|
|
461
|
+
return 1
|
|
462
|
+
|
|
463
|
+
src_size = os.path.getsize(src_path)
|
|
464
|
+
ibc_size = os.path.getsize(dest)
|
|
465
|
+
dce_tag = " +DCE" if dce else ""
|
|
466
|
+
print(f"[InScript] compiled{dce_tag}: {src_path} → {dest}")
|
|
467
|
+
print(f" source {src_size:,}B → bytecode {ibc_size:,}B "
|
|
468
|
+
f"({ibc_size/src_size*100:.0f}% of source)")
|
|
469
|
+
return 0
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _run_ibc(path: str) -> int:
|
|
473
|
+
"""v2.4.0: Load and execute a pre-compiled .ibc bytecode file."""
|
|
474
|
+
try:
|
|
475
|
+
from vm_code import read_ibc
|
|
476
|
+
from vm import VM
|
|
477
|
+
proto = read_ibc(path)
|
|
478
|
+
except ValueError as e:
|
|
479
|
+
print(f"[InScript] run: {e}", file=sys.stderr)
|
|
480
|
+
return 1
|
|
481
|
+
except Exception as e:
|
|
482
|
+
print(f"[InScript] run: failed to load '{path}': {e}", file=sys.stderr)
|
|
483
|
+
return 1
|
|
484
|
+
try:
|
|
485
|
+
vm = VM(filename=path)
|
|
486
|
+
vm.run(proto)
|
|
487
|
+
return 0
|
|
488
|
+
except Exception as e:
|
|
489
|
+
print(f"[InScript] runtime error: {e}", file=sys.stderr)
|
|
490
|
+
return 1
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _dce_proto(proto) -> int:
|
|
494
|
+
"""
|
|
495
|
+
v2.4.0: Dead-code elimination at the FnProto level.
|
|
496
|
+
Removes nested protos whose names are never referenced in the parent's
|
|
497
|
+
name table. Returns the number of protos removed.
|
|
498
|
+
"""
|
|
499
|
+
if not proto.protos:
|
|
500
|
+
return 0
|
|
501
|
+
referenced = set(proto.names)
|
|
502
|
+
before = len(proto.protos)
|
|
503
|
+
# Recurse into survivors first
|
|
504
|
+
for sub in proto.protos:
|
|
505
|
+
_dce_proto(sub)
|
|
506
|
+
# Remove unreferenced nested protos
|
|
507
|
+
proto.protos = [p for p in proto.protos if p.name in referenced or p.name == "<main>"]
|
|
508
|
+
removed = before - len(proto.protos)
|
|
509
|
+
return removed
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _dce_pass(program):
|
|
513
|
+
"""AST-level DCE (used by test_v240 for unit-testing DCE logic)."""
|
|
514
|
+
from ast_nodes import FunctionDecl, IdentExpr, Program
|
|
515
|
+
|
|
516
|
+
referenced: set = set()
|
|
517
|
+
|
|
518
|
+
def _walk(node):
|
|
519
|
+
if node is None: return
|
|
520
|
+
if isinstance(node, IdentExpr):
|
|
521
|
+
referenced.add(node.name)
|
|
522
|
+
return
|
|
523
|
+
for attr in (vars(node).values() if hasattr(node, '__dict__') else []):
|
|
524
|
+
if hasattr(attr, '__dict__'):
|
|
525
|
+
_walk(attr)
|
|
526
|
+
elif isinstance(attr, list):
|
|
527
|
+
for item in attr:
|
|
528
|
+
if hasattr(item, '__dict__'): _walk(item)
|
|
529
|
+
elif isinstance(attr, tuple):
|
|
530
|
+
for item in attr:
|
|
531
|
+
if hasattr(item, '__dict__'): _walk(item)
|
|
532
|
+
|
|
533
|
+
for stmt in program.body:
|
|
534
|
+
_walk(stmt)
|
|
535
|
+
|
|
536
|
+
kept = []
|
|
537
|
+
removed = 0
|
|
538
|
+
for stmt in program.body:
|
|
539
|
+
if isinstance(stmt, FunctionDecl) and stmt.name not in referenced:
|
|
540
|
+
if not getattr(stmt, 'exported', False):
|
|
541
|
+
removed += 1
|
|
542
|
+
continue
|
|
543
|
+
kept.append(stmt)
|
|
544
|
+
|
|
545
|
+
if removed:
|
|
546
|
+
print(f"[InScript] DCE: eliminated {removed} unreferenced function(s)")
|
|
547
|
+
return Program(body=kept)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
|
|
405
552
|
# ─── v1.6.0 helpers ──────────────────────────────────────────────────────────
|
|
406
553
|
|
|
407
554
|
def _find_ins_files(directory: str):
|
|
@@ -2108,12 +2255,35 @@ Examples:
|
|
|
2108
2255
|
help="Window height for --game mode (default: 600)")
|
|
2109
2256
|
parser.add_argument("--fps", type=int, default=60,
|
|
2110
2257
|
help="Target FPS for --game mode (default: 60)")
|
|
2258
|
+
# ── v2.4.0: compilation flags ────────────────────────────────────────────
|
|
2259
|
+
parser.add_argument("--compile", metavar="FILE",
|
|
2260
|
+
help="v2.4.0: AOT-compile FILE.ins → FILE.ibc bytecode")
|
|
2261
|
+
parser.add_argument("--output", "-o", metavar="OUT",
|
|
2262
|
+
help="v2.4.0: Output path for --compile (default: same dir as input)")
|
|
2263
|
+
parser.add_argument("--target", metavar="TARGET", default="interpreter",
|
|
2264
|
+
choices=["interpreter", "wasm", "native", "ibc"],
|
|
2265
|
+
help="v2.4.0: Compilation target: interpreter (default), ibc, wasm, native")
|
|
2266
|
+
parser.add_argument("--no-dce", action="store_true",
|
|
2267
|
+
help="v2.4.0: Disable dead-code elimination before compilation")
|
|
2268
|
+
parser.add_argument("--incremental", action="store_true",
|
|
2269
|
+
help="v2.4.0: Skip recompile if .ibc is newer than source")
|
|
2111
2270
|
args = parser.parse_args()
|
|
2112
2271
|
|
|
2113
2272
|
if args.version:
|
|
2114
2273
|
print(f"InScript {VERSION}")
|
|
2115
2274
|
return
|
|
2116
2275
|
|
|
2276
|
+
# ── v2.4.0: --compile ────────────────────────────────────────────────────
|
|
2277
|
+
if args.compile:
|
|
2278
|
+
import sys as _sys
|
|
2279
|
+
_sys.exit(_compile_cmd(
|
|
2280
|
+
src_path = args.compile,
|
|
2281
|
+
out_path = args.output,
|
|
2282
|
+
target = args.target,
|
|
2283
|
+
dce = not args.no_dce,
|
|
2284
|
+
incremental= args.incremental,
|
|
2285
|
+
) or 0)
|
|
2286
|
+
|
|
2117
2287
|
# ── inscript --fmt / --fmt-check / --fmt-dry-run ─────────────────────────
|
|
2118
2288
|
if args.fmt or args.fmt_check or args.fmt_dry_run:
|
|
2119
2289
|
try:
|
|
@@ -28,6 +28,9 @@ from errors import (
|
|
|
28
28
|
# INTERPRETER
|
|
29
29
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
|
+
# v2.4.0: sentinel for inline cache misses (faster than None since None is valid)
|
|
32
|
+
_CACHE_MISS = object()
|
|
33
|
+
|
|
31
34
|
class Interpreter(Visitor):
|
|
32
35
|
"""
|
|
33
36
|
Tree-walking interpreter. Visits each AST node and returns a Python value.
|
|
@@ -48,6 +51,9 @@ class Interpreter(Visitor):
|
|
|
48
51
|
self._current_fn: object = None
|
|
49
52
|
# v1.4.0: defer stack — list of exprs to run at end of current function
|
|
50
53
|
self._deferred: list = []
|
|
54
|
+
# v2.4.0: inline caches — monomorphic site caching for hot paths
|
|
55
|
+
self._ic_struct_fields: dict = {} # struct_name -> {field: default_idx}
|
|
56
|
+
self._ic_fn_lookup: dict = {} # fn_name -> InScriptFunction (global scope)
|
|
51
57
|
|
|
52
58
|
self._register_builtins()
|
|
53
59
|
|
|
@@ -533,6 +539,8 @@ class Interpreter(Visitor):
|
|
|
533
539
|
# v1.9.7: mark async functions
|
|
534
540
|
fn.is_async = getattr(node, 'is_async', False)
|
|
535
541
|
self._env.define(node.name, fn)
|
|
542
|
+
# v2.4.0: invalidate inline cache if this function was cached
|
|
543
|
+
self._ic_fn_lookup.pop(node.name, None)
|
|
536
544
|
return fn
|
|
537
545
|
|
|
538
546
|
def visit_GeneratorFnDecl(self, node) -> Any:
|
|
@@ -1574,10 +1582,18 @@ class Interpreter(Visitor):
|
|
|
1574
1582
|
decl = None
|
|
1575
1583
|
parent_decl = getattr(decl, '_resolved_parent', None)
|
|
1576
1584
|
if parent_decl is not None:
|
|
1577
|
-
# Return a SuperProxy that knows the parent and self
|
|
1578
1585
|
return _SuperProxy(self_val, parent_decl, self)
|
|
1579
1586
|
self._error("'super' only valid in a struct that extends another", node.line)
|
|
1580
|
-
|
|
1587
|
+
# v2.4.0: inline cache — fast path for global function lookups
|
|
1588
|
+
name = node.name
|
|
1589
|
+
ic = self._ic_fn_lookup.get(name)
|
|
1590
|
+
if ic is not None:
|
|
1591
|
+
return ic
|
|
1592
|
+
val = self._env.get(name, node.line)
|
|
1593
|
+
# Populate cache for top-level InScriptFunctions (stable globals only)
|
|
1594
|
+
if isinstance(val, InScriptFunction) and self._env is self._globals:
|
|
1595
|
+
self._ic_fn_lookup[name] = val
|
|
1596
|
+
return val
|
|
1581
1597
|
|
|
1582
1598
|
def visit_ArrayLiteralExpr(self, node: ArrayLiteralExpr) -> Any:
|
|
1583
1599
|
result = []
|
|
@@ -2037,6 +2053,11 @@ class Interpreter(Visitor):
|
|
|
2037
2053
|
|
|
2038
2054
|
def visit_GetAttrExpr(self, node: GetAttrExpr) -> Any:
|
|
2039
2055
|
obj = self.visit(node.obj)
|
|
2056
|
+
# v2.4.0: inline cache for struct field access — skip full _get_attr for hot path
|
|
2057
|
+
if isinstance(obj, InScriptInstance):
|
|
2058
|
+
val = obj.fields.get(node.attr, _CACHE_MISS)
|
|
2059
|
+
if val is not _CACHE_MISS:
|
|
2060
|
+
return val
|
|
2040
2061
|
return _get_attr(obj, node.attr, node.line, self)
|
|
2041
2062
|
|
|
2042
2063
|
def visit_IndexExpr(self, node: IndexExpr) -> Any:
|
|
@@ -2955,16 +2976,16 @@ def _arr_count(lst, fn_or_val, interp):
|
|
|
2955
2976
|
|
|
2956
2977
|
|
|
2957
2978
|
def _get_attr(obj: Any, name: str, line: int, interp: Interpreter) -> Any:
|
|
2958
|
-
# v2.3.0: concurrency primitive attribute dispatch
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
if
|
|
2979
|
+
# v2.3.0/v2.4.0: concurrency primitive attribute dispatch
|
|
2980
|
+
# Only import when obj is one of the concurrency types (type-name fast check)
|
|
2981
|
+
_tname = type(obj).__name__
|
|
2982
|
+
if _tname == "InScriptTask":
|
|
2983
|
+
import stdlib_values as _sv
|
|
2962
2984
|
if name == "join": return lambda timeout=None: obj.join(timeout)
|
|
2963
2985
|
if name == "done": return obj.done
|
|
2964
2986
|
if name == "result": return obj._result
|
|
2965
2987
|
interp._error(f"Task has no member '{name}'. Available: join, done, result", line)
|
|
2966
|
-
|
|
2967
|
-
if isinstance(obj, InScriptChannel):
|
|
2988
|
+
elif _tname == "InScriptChannel":
|
|
2968
2989
|
if name == "send": return lambda v: obj.send(v)
|
|
2969
2990
|
if name == "recv": return lambda: obj.recv()
|
|
2970
2991
|
if name == "try_recv": return lambda: obj.try_recv()
|
|
@@ -2973,20 +2994,17 @@ def _get_attr(obj: Any, name: str, line: int, interp: Interpreter) -> Any:
|
|
|
2973
2994
|
if name == "size": return obj._q.qsize()
|
|
2974
2995
|
if name == "is_empty": return obj._q.empty()
|
|
2975
2996
|
interp._error(f"Channel has no member '{name}'. Available: send, recv, try_recv, close, closed, size", line)
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
if name == "
|
|
2979
|
-
if name == "
|
|
2980
|
-
if name == "value": return obj.value
|
|
2997
|
+
elif _tname == "InScriptMutex":
|
|
2998
|
+
if name == "lock": return lambda fn: obj.lock(lambda args: interp._call_fn(fn, args))
|
|
2999
|
+
if name == "set": return lambda v: obj.set(v)
|
|
3000
|
+
if name == "value": return obj.value
|
|
2981
3001
|
interp._error(f"Mutex has no member '{name}'. Available: lock, set, value", line)
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
if name == "
|
|
2985
|
-
if name == "
|
|
2986
|
-
if name == "value": return obj.value
|
|
3002
|
+
elif _tname == "InScriptRWLock":
|
|
3003
|
+
if name == "read": return lambda fn: obj.read(lambda args: interp._call_fn(fn, args))
|
|
3004
|
+
if name == "write": return lambda fn: obj.write(lambda args: interp._call_fn(fn, args))
|
|
3005
|
+
if name == "value": return obj.value
|
|
2987
3006
|
interp._error(f"RWLock has no member '{name}'. Available: read, write, value", line)
|
|
2988
|
-
|
|
2989
|
-
if isinstance(obj, InScriptTimerHandle):
|
|
3007
|
+
elif _tname == "InScriptTimerHandle":
|
|
2990
3008
|
if name == "cancel": return lambda: obj.cancel()
|
|
2991
3009
|
interp._error(f"TimerHandle has no member '{name}'. Available: cancel", line)
|
|
2992
3010
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "inscript-lang"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.4.0"
|
|
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 = "2.
|
|
43
|
+
VERSION = "2.4.0"
|
|
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
|
|
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
|