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.
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/PKG-INFO +1 -1
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript.py +50 -1
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/PKG-INFO +1 -1
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/interpreter.py +49 -11
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/lexer.py +25 -13
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/parser.py +13 -7
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/pyproject.toml +1 -1
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/repl.py +1 -1
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/setup.py +1 -1
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/README.md +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/analyzer.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/ast_nodes.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/compiler.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/environment.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/errors.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_fmt.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/SOURCES.txt +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/dependency_links.txt +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/entry_points.txt +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/requires.txt +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_lang.egg-info/top_level.txt +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/inscript_test.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/pygame_backend.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/setup.cfg +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib_extended.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib_extended_2.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib_game.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/stdlib_values.py +0 -0
- {inscript_lang-1.6.0 → inscript_lang-1.7.1}/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 = "1.
|
|
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()
|
|
@@ -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,
|
|
1491
|
-
#
|
|
1492
|
-
if
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
#
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
#
|
|
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
|
+
# otherwise → line comment
|
|
301
301
|
if ch == "/" and self.current == "/":
|
|
302
302
|
self.advance() # consume second /
|
|
303
|
-
|
|
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.
|
|
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("="):
|
|
526
|
-
else:
|
|
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
|
|
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
|
-
#
|
|
1817
|
-
if tok.type == TT.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|