rapydscript-ns 0.8.0 → 0.8.2
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.
- package/CONTRIBUTORS +3 -2
- package/PYTHON_FEATURE_COVERAGE.md +109 -0
- package/README.md +320 -34
- package/TODO.md +17 -48
- package/hack_demo.pyj +112 -0
- package/package.json +7 -5
- package/src/ast.pyj +30 -6
- package/src/baselib-builtins.pyj +104 -10
- package/src/baselib-containers.pyj +146 -1
- package/src/baselib-errors.pyj +3 -0
- package/src/baselib-internal.pyj +10 -0
- package/src/baselib-str.pyj +34 -0
- package/src/monaco-language-service/analyzer.js +1 -13
- package/src/monaco-language-service/builtins.js +10 -0
- package/src/monaco-language-service/completions.js +54 -2
- package/src/monaco-language-service/diagnostics.js +54 -4
- package/src/monaco-language-service/index.js +9 -5
- package/src/output/codegen.pyj +37 -2
- package/src/output/functions.pyj +31 -9
- package/src/output/loops.pyj +64 -2
- package/src/output/operators.pyj +53 -1
- package/src/output/statements.pyj +7 -3
- package/src/output/stream.pyj +6 -0
- package/src/parse.pyj +77 -13
- package/src/tokenizer.pyj +1 -0
- package/test/python_features.pyj +1184 -0
- package/test/unit/language-service-bundle.js +83 -0
- package/test/unit/language-service-completions.js +109 -0
- package/test/unit/language-service.js +123 -1
- package/test/unit/run-language-service.js +1 -0
- package/tools/lint.js +1 -1
- package/tools/self.js +1 -9
- package/web-repl/language-service.js +129 -26
- package/web-repl/rapydscript.js +3 -3
package/TODO.md
CHANGED
|
@@ -1,62 +1,30 @@
|
|
|
1
1
|
|
|
2
2
|
### libraries
|
|
3
|
-
- allow var etc as property name
|
|
4
|
-
- revert numpy variance method to be var
|
|
5
3
|
|
|
6
|
-
-
|
|
4
|
+
- bitburner - handle missed await / concurrency errors gracefully?
|
|
5
|
+
- two space tabs
|
|
6
|
+
|
|
7
|
+
- fix error from vuln report
|
|
8
|
+
- https://socket.dev/npm/package/rapydscript-ns/alerts/0.8.1?tab=dependencies
|
|
9
|
+
- https://snyk.io/test/github/ficocelliguy/rapydscript-ns
|
|
7
10
|
|
|
8
|
-
-
|
|
11
|
+
- omit_function_metadata breaks imports - it needs to be changed to only affect imported modules, maybe?
|
|
9
12
|
|
|
10
13
|
- vscode plugin based on language service
|
|
11
14
|
|
|
12
|
-
- export language service
|
|
13
|
-
- update url links
|
|
14
|
-
- make npm module
|
|
15
|
+
- export .t.ds for language service
|
|
15
16
|
|
|
17
|
+
- add opt-out for python truthyness
|
|
18
|
+
- add web-repl flag for python truthiness?
|
|
19
|
+
- include python truthiness in compiler call
|
|
16
20
|
|
|
21
|
+
- update changelist for 8.0
|
|
17
22
|
|
|
18
23
|
I would like you to add support for [ Python's extended subscript syntax where commas inside [] implicitly form a tuple ] to rapydscript. It should have the same syntax as the Python implementation, and be transpiled into equivalent javascript. Please ensure with unit tests that it transpiles and the output JS runs correctly, and that the language service correctly handles it in parsed code. Please make sure it works in the web-repl too. Please also update the README to mention this support.
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
|
|
23
|
-
# 1. Placeholder body (like pass)
|
|
24
|
-
def todo():
|
|
25
|
-
...
|
|
26
|
-
|
|
27
|
-
class MyProtocol:
|
|
28
|
-
def method(self) -> int: ...
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# 3. Sentinel / default marker
|
|
32
|
-
_MISSING = ...
|
|
33
|
-
|
|
34
|
-
def greet(name=...):
|
|
35
|
-
if name is ...:
|
|
36
|
-
name = "World"
|
|
37
|
-
print(f"Hello, {name}!")
|
|
38
|
-
|
|
39
|
-
greet() # Hello, World!
|
|
40
|
-
greet("Alice") # Hello, Alice!
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# 4. NumPy-style slice shorthand (all leading dimensions)
|
|
44
|
-
import numpy as np
|
|
45
|
-
arr = np.zeros((2, 3, 4))
|
|
46
|
-
print(arr[..., 0] ) # shape (2, 3) — first element of last axis
|
|
47
|
-
print(arr[1, ...]) # shape (3, 4) — entire second "sheet"
|
|
48
|
-
|
|
49
|
-
# 5. Identity check
|
|
50
|
-
print(... is ...) # True (singleton)
|
|
51
|
-
print(... is Ellipsis) # True
|
|
52
|
-
print(type(...).__name__) # ellipsis
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
28
|
Python Feature Gap Report: RapydScript-NS
|
|
61
29
|
|
|
62
30
|
Summary
|
|
@@ -76,7 +44,7 @@ differences.
|
|
|
76
44
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
77
45
|
│ lambda keyword │ all │ -tested │
|
|
78
46
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
79
|
-
│ Variable type annotations x: int = 1 │ 3.6+ │
|
|
47
|
+
│ Variable type annotations x: int = 1 │ 3.6+ │ -tested │
|
|
80
48
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
81
49
|
│ Positional-only parameters (def f(a, /, b):) │ 3.8+ │ -tested │
|
|
82
50
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
@@ -88,7 +56,7 @@ differences.
|
|
|
88
56
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
89
57
|
│ Parenthesized with (multi-context) │ 3.10+ │ - doesn't make sense in a web context │
|
|
90
58
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
91
|
-
│ Exception chaining raise X from Y │ 3.0+ │
|
|
59
|
+
│ Exception chaining raise X from Y │ 3.0+ │ - done │
|
|
92
60
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
93
61
|
│ except* (exception groups) │ 3.11+ │ No support │
|
|
94
62
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
@@ -96,7 +64,7 @@ differences.
|
|
|
96
64
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
97
65
|
│ Complex number literals 3+4j │ all │ No j suffix │
|
|
98
66
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
99
|
-
│ Ellipsis literal ... as expression │ all │
|
|
67
|
+
│ Ellipsis literal ... as expression │ all │ -tested │
|
|
100
68
|
├───────────────────────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────┤
|
|
101
69
|
│ b'...' bytes literals │ all │ No b prefix; encoding module exists but no native bytes type │
|
|
102
70
|
└───────────────────────────────────────────────┴────────────────┴──────────────────────────────────────────────────────────────┘
|
|
@@ -313,7 +281,8 @@ If prioritizing what to implement next, these have the highest user impact:
|
|
|
313
281
|
|
|
314
282
|
1. super() — required for idiomatic OOP code - done
|
|
315
283
|
2. Operator overloading (__add__, __lt__, etc.) — blocks numerical/scientific code - done
|
|
316
|
-
3. __bool__ / truthiness fix — silent Python compatibility trap
|
|
284
|
+
3. __bool__ / truthiness fix — silent Python compatibility trap - done
|
|
285
|
+
→ frozenset — first medium-priority missing builtin - done
|
|
317
286
|
4. lambda keyword — commonly expected - done
|
|
318
287
|
5. all() / any() — extremely common builtins - done
|
|
319
288
|
6. functools.reduce / partial — core functional programming tools - done
|
package/hack_demo.pyj
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Over-engineered RapydScript hacking script — Python features on parade
|
|
2
|
+
|
|
3
|
+
class HackAction:
|
|
4
|
+
"""Enum-like action namespace."""
|
|
5
|
+
WEAKEN = 'weaken'
|
|
6
|
+
GROW = 'grow'
|
|
7
|
+
HACK = 'hack'
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def all():
|
|
11
|
+
return [HackAction.WEAKEN, HackAction.GROW, HackAction.HACK]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ServerConfig:
|
|
15
|
+
"""Data-class-style target config with property + fluent builder."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, name, money_factor=1.0, sec_buffer=0.0):
|
|
18
|
+
self._name = name
|
|
19
|
+
self.money_factor = money_factor
|
|
20
|
+
self.sec_buffer = sec_buffer
|
|
21
|
+
self._tags = {'auto', 'hack'} # set literal
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def name(self):
|
|
25
|
+
return self._name
|
|
26
|
+
|
|
27
|
+
def __repr__(self):
|
|
28
|
+
return "ServerConfig(name={}, tags={})".format(
|
|
29
|
+
repr(self._name), sorted(list(self._tags))
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def add_tags(self, *tags): # *args + fluent return
|
|
33
|
+
for t in tags:
|
|
34
|
+
self._tags.add(t)
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def decide_action(ns, target, money_thresh, sec_thresh):
|
|
39
|
+
"""next() + generator expression to pick highest-priority action."""
|
|
40
|
+
checks = [
|
|
41
|
+
(ns.getServerSecurityLevel(target) > sec_thresh, HackAction.WEAKEN),
|
|
42
|
+
(ns.getServerMoneyAvailable(target) < money_thresh, HackAction.GROW),
|
|
43
|
+
]
|
|
44
|
+
return next((action for cond, action in checks if cond), HackAction.HACK)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def breach(ns, target, exploits):
|
|
48
|
+
"""Apply available exploits via (exe, fn) tuple list, nuke, return summary."""
|
|
49
|
+
# list of (exe filename, bound ns method) tuples
|
|
50
|
+
exe_methods = [
|
|
51
|
+
('BruteSSH.exe', ns.brutessh),
|
|
52
|
+
('FTPCrack.exe', ns.ftpcrack),
|
|
53
|
+
('relaySMTP.exe', ns.relaysmtp),
|
|
54
|
+
]
|
|
55
|
+
applied = [fn for exe, fn in exe_methods if ns.fileExists(exe, "home")]
|
|
56
|
+
missing = [exe for exe, fn in exe_methods if not ns.fileExists(exe, "home")]
|
|
57
|
+
|
|
58
|
+
if missing:
|
|
59
|
+
ns.tprint("Missing: {}".format(', '.join(missing)))
|
|
60
|
+
|
|
61
|
+
for fn in applied:
|
|
62
|
+
fn(target)
|
|
63
|
+
ns.nuke(target)
|
|
64
|
+
|
|
65
|
+
return {'applied': len(applied), 'total': len(exploits),
|
|
66
|
+
'rooted': ns.hasRootAccess(target)}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def main(ns):
|
|
70
|
+
config = ServerConfig("n00dles").add_tags('target', 'beginner') # fluent
|
|
71
|
+
ns.tprint(repr(config)) # __repr__
|
|
72
|
+
|
|
73
|
+
target = config.name # @property
|
|
74
|
+
money_thresh = ns.getServerMaxMoney(target) * config.money_factor
|
|
75
|
+
sec_thresh = ns.getServerMinSecurityLevel(target) + config.sec_buffer
|
|
76
|
+
|
|
77
|
+
def by_name_len(p): # named key fn (required for
|
|
78
|
+
return len(p[0]) # anonymous fns in call args)
|
|
79
|
+
exploits = sorted([
|
|
80
|
+
('brutessh', 'BruteSSH.exe'),
|
|
81
|
+
('ftpcrack', 'FTPCrack.exe'),
|
|
82
|
+
('relaysmtp', 'relaySMTP.exe'),
|
|
83
|
+
], key=by_name_len)
|
|
84
|
+
|
|
85
|
+
result = await breach(ns, target, exploits)
|
|
86
|
+
ns.tprint("Breach: {applied}/{total} applied, rooted={rooted}".format(**result))
|
|
87
|
+
assert result['rooted'], "Root access denied on {}!".format(target)
|
|
88
|
+
|
|
89
|
+
dispatch = { # dict literal
|
|
90
|
+
'weaken': ns.weaken,
|
|
91
|
+
'grow': ns.grow,
|
|
92
|
+
'hack': ns.hack,
|
|
93
|
+
}
|
|
94
|
+
stats = {a: 0 for a in HackAction.all()} # dict comprehension
|
|
95
|
+
|
|
96
|
+
cycle = 0
|
|
97
|
+
while True:
|
|
98
|
+
cycle += 1
|
|
99
|
+
action = decide_action(ns, target, money_thresh, sec_thresh)
|
|
100
|
+
stats[action] += 1
|
|
101
|
+
|
|
102
|
+
if cycle % 10 is 0: # periodic summary
|
|
103
|
+
counts = [stats[a] for a in HackAction.all()]
|
|
104
|
+
summary = ', '.join(["{}: {}".format(a, v)
|
|
105
|
+
for a, v in zip(HackAction.all(), counts)])
|
|
106
|
+
ns.tprint("Cycle {} | {} any={} all={}".format(
|
|
107
|
+
cycle, summary,
|
|
108
|
+
any([v > 0 for v in counts]), # any()
|
|
109
|
+
all([v > 0 for v in counts]) # all()
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
await dispatch[action](target)
|
package/package.json
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
"compiler"
|
|
10
10
|
],
|
|
11
11
|
"main": "tools/compiler.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./tools/compiler.js",
|
|
14
|
+
"./language-service": "./web-repl/language-service.js"
|
|
15
|
+
},
|
|
12
16
|
"scripts": {
|
|
13
17
|
"test": "node bin/rapydscript self --complete --test && npm run test:unit && npm run test:ls",
|
|
14
18
|
"test:unit": "node test/unit/index.js",
|
|
@@ -16,9 +20,10 @@
|
|
|
16
20
|
"start": "node bin/rapydscript",
|
|
17
21
|
"build-self": "node bin/rapydscript self --complete",
|
|
18
22
|
"build:ls": "node tools/build-language-service.js",
|
|
19
|
-
"build": "rm -rf **/*pyj-cached dev && node bin/rapydscript self --complete && node bin/web-repl-export web-repl && node tools/build-language-service.js"
|
|
23
|
+
"build": "rm -rf **/*pyj-cached dev && node bin/rapydscript self --complete && node bin/web-repl-export web-repl && node tools/build-language-service.js",
|
|
24
|
+
"prepublishOnly": "node tools/build-language-service.js"
|
|
20
25
|
},
|
|
21
|
-
"version": "0.8.
|
|
26
|
+
"version": "0.8.2",
|
|
22
27
|
"license": "BSD-2-Clause",
|
|
23
28
|
"engines": {
|
|
24
29
|
"node": ">=0.12.0"
|
|
@@ -37,9 +42,6 @@
|
|
|
37
42
|
"regenerator": ">= 0.12.1",
|
|
38
43
|
"uglify-js": ">= 3.0.15"
|
|
39
44
|
},
|
|
40
|
-
"optionalDependencies": {
|
|
41
|
-
"v8-profiler": ">= 5.2.9"
|
|
42
|
-
},
|
|
43
45
|
"bin": {
|
|
44
46
|
"rapydscript": "bin/rapydscript"
|
|
45
47
|
}
|
package/src/ast.pyj
CHANGED
|
@@ -159,6 +159,7 @@ class AST_Assert(AST_Statement):
|
|
|
159
159
|
properties = {
|
|
160
160
|
'condition': "[AST_Node] the expression that should be tested",
|
|
161
161
|
'message': "[AST_Node*] the expression that is the error message or None",
|
|
162
|
+
'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
def _walk(self, visitor):
|
|
@@ -213,7 +214,8 @@ class AST_StatementWithBody(AST_Statement):
|
|
|
213
214
|
class AST_DWLoop(AST_StatementWithBody):
|
|
214
215
|
"Base class for do/while statements"
|
|
215
216
|
properties = {
|
|
216
|
-
'condition': "[AST_Node] the loop condition. Should not be instanceof AST_Statement"
|
|
217
|
+
'condition': "[AST_Node] the loop condition. Should not be instanceof AST_Statement",
|
|
218
|
+
'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
def _walk(self, visitor):
|
|
@@ -227,13 +229,25 @@ class AST_Do(AST_DWLoop):
|
|
|
227
229
|
|
|
228
230
|
class AST_While(AST_DWLoop):
|
|
229
231
|
"A `while` statement"
|
|
232
|
+
properties = {
|
|
233
|
+
'belse': "[AST_Else?] the `else` clause, run when loop exits without `break`"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
def _walk(self, visitor):
|
|
237
|
+
return visitor._visit(self, def():
|
|
238
|
+
self.condition._walk(visitor)
|
|
239
|
+
self.body._walk(visitor)
|
|
240
|
+
if self.belse:
|
|
241
|
+
self.belse._walk(visitor)
|
|
242
|
+
)
|
|
230
243
|
|
|
231
244
|
class AST_ForIn(AST_StatementWithBody):
|
|
232
245
|
"A `for ... in` statement"
|
|
233
246
|
properties = {
|
|
234
247
|
'init': "[AST_Node] the `for/in` initialization code",
|
|
235
248
|
'name': "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var",
|
|
236
|
-
'object': "[AST_Node] the object that we're looping through"
|
|
249
|
+
'object': "[AST_Node] the object that we're looping through",
|
|
250
|
+
'belse': "[AST_Else?] the `else` clause, run when no break occurred"
|
|
237
251
|
}
|
|
238
252
|
|
|
239
253
|
def _walk(self, visitor):
|
|
@@ -243,6 +257,8 @@ class AST_ForIn(AST_StatementWithBody):
|
|
|
243
257
|
self.object._walk(visitor)
|
|
244
258
|
if self.body:
|
|
245
259
|
self.body._walk(visitor)
|
|
260
|
+
if self.belse:
|
|
261
|
+
self.belse._walk(visitor)
|
|
246
262
|
)
|
|
247
263
|
|
|
248
264
|
class AST_ForJS(AST_StatementWithBody):
|
|
@@ -647,6 +663,9 @@ class AST_Await(AST_Node):
|
|
|
647
663
|
|
|
648
664
|
class AST_Throw(AST_Exit):
|
|
649
665
|
"A `throw` statement"
|
|
666
|
+
properties = {
|
|
667
|
+
'cause': "[AST_Node?] the __cause__ of the exception (from `raise X from Y`); may be None"
|
|
668
|
+
}
|
|
650
669
|
|
|
651
670
|
class AST_LoopControl(AST_Jump):
|
|
652
671
|
"Base class for loop control statements (`break` and `continue`)"
|
|
@@ -663,7 +682,8 @@ class AST_If(AST_StatementWithBody):
|
|
|
663
682
|
"A `if` statement"
|
|
664
683
|
properties = {
|
|
665
684
|
'condition': "[AST_Node] the `if` condition",
|
|
666
|
-
'alternative': "[AST_Statement?] the `else` part, or null if not present"
|
|
685
|
+
'alternative': "[AST_Statement?] the `else` part, or null if not present",
|
|
686
|
+
'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
|
|
667
687
|
}
|
|
668
688
|
|
|
669
689
|
def _walk(self, visitor):
|
|
@@ -767,7 +787,8 @@ class AST_BaseCall(AST_Node):
|
|
|
767
787
|
class AST_Call(AST_BaseCall):
|
|
768
788
|
"A function call expression"
|
|
769
789
|
properties = {
|
|
770
|
-
'expression': "[AST_Node] expression to invoke as function"
|
|
790
|
+
'expression': "[AST_Node] expression to invoke as function",
|
|
791
|
+
'python_truthiness': "[bool] Whether to use Python truthiness for __call__ dispatch"
|
|
771
792
|
}
|
|
772
793
|
|
|
773
794
|
def _walk(self, visitor):
|
|
@@ -941,7 +962,8 @@ class AST_Unary(AST_Node):
|
|
|
941
962
|
'operator': "[string] the operator",
|
|
942
963
|
'expression': "[AST_Node] expression that this unary operator applies to",
|
|
943
964
|
'parenthesized': "[bool] Whether this unary expression was parenthesized",
|
|
944
|
-
'overloaded': "[bool] Whether to use Python-style operator overloading dispatch"
|
|
965
|
+
'overloaded': "[bool] Whether to use Python-style operator overloading dispatch",
|
|
966
|
+
'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
|
|
945
967
|
}
|
|
946
968
|
|
|
947
969
|
def _walk(self, visitor):
|
|
@@ -958,7 +980,8 @@ class AST_Binary(AST_Node):
|
|
|
958
980
|
'left': "[AST_Node] left-hand side expression",
|
|
959
981
|
'operator': "[string] the operator",
|
|
960
982
|
'right': "[AST_Node] right-hand side expression",
|
|
961
|
-
'overloaded': "[bool] Whether to use Python-style operator overloading dispatch"
|
|
983
|
+
'overloaded': "[bool] Whether to use Python-style operator overloading dispatch",
|
|
984
|
+
'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
|
|
962
985
|
}
|
|
963
986
|
|
|
964
987
|
def _walk(self, visitor):
|
|
@@ -987,6 +1010,7 @@ class AST_Conditional(AST_Node):
|
|
|
987
1010
|
'condition': "[AST_Node]",
|
|
988
1011
|
'consequent': "[AST_Node]",
|
|
989
1012
|
'alternative': "[AST_Node]",
|
|
1013
|
+
'python_truthiness': "[bool] Whether to use Python truthiness (from __python__ import truthiness)"
|
|
990
1014
|
}
|
|
991
1015
|
|
|
992
1016
|
def _walk(self, visitor):
|
package/src/baselib-builtins.pyj
CHANGED
|
@@ -5,7 +5,21 @@
|
|
|
5
5
|
# globals: exports, console, ρσ_iterator_symbol, ρσ_kwargs_symbol, ρσ_arraylike, ρσ_list_contains
|
|
6
6
|
|
|
7
7
|
def ρσ_bool(val):
|
|
8
|
-
|
|
8
|
+
# Python truthiness — written entirely with verbatim JS so that the
|
|
9
|
+
# compiled ρσ_bool function never calls itself (no bootstrap recursion).
|
|
10
|
+
v'if (val === null || val === undefined) return false'
|
|
11
|
+
v'var ρσ_bool_t = typeof val'
|
|
12
|
+
v'if (ρσ_bool_t === "boolean") return val'
|
|
13
|
+
v'if (ρσ_bool_t === "number") return val !== 0'
|
|
14
|
+
v'if (ρσ_bool_t === "string") return val.length > 0'
|
|
15
|
+
v'if (ρσ_bool_t === "function") return true'
|
|
16
|
+
v'if (val.constructor && val.constructor.prototype === val) return true'
|
|
17
|
+
v'if (typeof val.__bool__ === "function") return !!val.__bool__()'
|
|
18
|
+
v'if (Array.isArray(val)) return val.length > 0'
|
|
19
|
+
v'if (typeof val.__len__ === "function") return val.__len__() > 0'
|
|
20
|
+
v'if ((typeof Set === "function" && val instanceof Set) || (typeof Map === "function" && val instanceof Map)) return val.size > 0'
|
|
21
|
+
v'if (!val.constructor || val.constructor === Object) return Object.keys(val).length > 0'
|
|
22
|
+
return True
|
|
9
23
|
|
|
10
24
|
def ρσ_print(*args, **kwargs):
|
|
11
25
|
if v'typeof console' is 'object':
|
|
@@ -73,7 +87,24 @@ def ρσ_chr(code):
|
|
|
73
87
|
return String.fromCharCode(0xD800+(code>>10), 0xDC00+(code&0x3FF))
|
|
74
88
|
|
|
75
89
|
def ρσ_callable(x):
|
|
76
|
-
return v'typeof x === "function"'
|
|
90
|
+
return v'typeof x === "function" || (x !== null && x !== undefined && typeof x.__call__ === "function")'
|
|
91
|
+
|
|
92
|
+
def ρσ_callable_call(fn):
|
|
93
|
+
# Dispatch fn(args): check __call__ first (callable objects), then direct call.
|
|
94
|
+
# __call__ is checked first so callable objects (not functions) are dispatched correctly.
|
|
95
|
+
args = v'Array.prototype.slice.call(arguments, 1)'
|
|
96
|
+
if v'fn !== null && fn !== undefined && typeof fn.__call__ === "function"':
|
|
97
|
+
return v'fn.__call__.apply(fn, args)'
|
|
98
|
+
if v'typeof fn === "function"':
|
|
99
|
+
return v'fn.apply(undefined, args)'
|
|
100
|
+
raise TypeError('object is not callable')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def ρσ_round(x, ndigits):
|
|
104
|
+
if ndigits is undefined or ndigits is 0:
|
|
105
|
+
return Math.round(x)
|
|
106
|
+
factor = Math.pow(10, ndigits)
|
|
107
|
+
return Math.round(x * factor) / factor
|
|
77
108
|
|
|
78
109
|
def ρσ_bin(x):
|
|
79
110
|
if jstype(x) is not 'number' or x % 1 is not 0:
|
|
@@ -95,15 +126,17 @@ def ρσ_hex(x):
|
|
|
95
126
|
ans = '0x' + ans
|
|
96
127
|
return ans
|
|
97
128
|
|
|
98
|
-
def ρσ_enumerate(iterable):
|
|
99
|
-
|
|
129
|
+
def ρσ_enumerate(iterable, start):
|
|
130
|
+
offset = 0 if start is undefined else start
|
|
131
|
+
ans = {'_i': offset - 1}
|
|
100
132
|
ans[ρσ_iterator_symbol] = def():
|
|
101
133
|
return this
|
|
102
134
|
if ρσ_arraylike(iterable):
|
|
103
135
|
ans['next'] = def():
|
|
104
136
|
this._i += 1
|
|
105
|
-
|
|
106
|
-
|
|
137
|
+
idx = this._i - offset
|
|
138
|
+
if idx < iterable.length:
|
|
139
|
+
return {'done': False, 'value': [this._i, iterable[idx]]}
|
|
107
140
|
return v"{'done':true}"
|
|
108
141
|
return ans
|
|
109
142
|
if jstype(iterable[ρσ_iterator_symbol]) is 'function':
|
|
@@ -114,9 +147,9 @@ def ρσ_enumerate(iterable):
|
|
|
114
147
|
if r.done:
|
|
115
148
|
return v"{'done':true}"
|
|
116
149
|
this._i += 1
|
|
117
|
-
return
|
|
150
|
+
return {'done': False, 'value': [this._i, r.value]}
|
|
118
151
|
return ans
|
|
119
|
-
return ρσ_enumerate(Object.keys(iterable))
|
|
152
|
+
return ρσ_enumerate(Object.keys(iterable), start)
|
|
120
153
|
|
|
121
154
|
def ρσ_reversed(iterable):
|
|
122
155
|
if ρσ_arraylike(iterable):
|
|
@@ -244,6 +277,67 @@ def ρσ_type(x):
|
|
|
244
277
|
return x.constructor
|
|
245
278
|
|
|
246
279
|
|
|
280
|
+
def ρσ_issubclass(cls, base):
|
|
281
|
+
if Array.isArray(base):
|
|
282
|
+
for b in base:
|
|
283
|
+
if ρσ_issubclass(cls, b):
|
|
284
|
+
return True
|
|
285
|
+
return False
|
|
286
|
+
if jstype(cls) is not 'function':
|
|
287
|
+
raise TypeError('issubclass() arg 1 must be a class')
|
|
288
|
+
if jstype(base) is not 'function':
|
|
289
|
+
raise TypeError('issubclass() arg 2 must be a class')
|
|
290
|
+
if cls is base:
|
|
291
|
+
return True
|
|
292
|
+
v'var proto = cls.prototype; while (proto !== null && proto !== undefined) { if (proto === base.prototype) return true; proto = Object.getPrototypeOf(proto); }'
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
v'var ρσ_hash_id_counter = 0'
|
|
297
|
+
|
|
298
|
+
def ρσ_hash(obj):
|
|
299
|
+
v'var ρσ_t = typeof obj'
|
|
300
|
+
v'if (obj === null || obj === undefined) return 0'
|
|
301
|
+
v'if (ρσ_t === "boolean") return obj ? 1 : 0'
|
|
302
|
+
v'if (ρσ_t === "number") { return (obj === Math.floor(obj)) ? (obj | 0) : ((obj * 2654435761) | 0); }'
|
|
303
|
+
v'''if (ρσ_t === "string") {
|
|
304
|
+
var ρσ_h = 5381;
|
|
305
|
+
for (var ρσ_i = 0; ρσ_i < obj.length; ρσ_i++) {
|
|
306
|
+
ρσ_h = (((ρσ_h << 5) + ρσ_h) ^ obj.charCodeAt(ρσ_i)) | 0;
|
|
307
|
+
}
|
|
308
|
+
return ρσ_h;
|
|
309
|
+
}'''
|
|
310
|
+
v'if (typeof obj.__hash__ === "function") return obj.__hash__()'
|
|
311
|
+
v'if (Array.isArray(obj)) throw new TypeError("unhashable type: \'list\'")'
|
|
312
|
+
v'if (typeof ρσ_set === "function" && obj instanceof ρσ_set) throw new TypeError("unhashable type: \'set\'")'
|
|
313
|
+
v'if (typeof Set === "function" && obj instanceof Set) throw new TypeError("unhashable type: \'set\'")'
|
|
314
|
+
v'if (typeof ρσ_dict === "function" && obj instanceof ρσ_dict) throw new TypeError("unhashable type: \'dict\'")'
|
|
315
|
+
v'if (typeof Map === "function" && obj instanceof Map) throw new TypeError("unhashable type: \'dict\'")'
|
|
316
|
+
v'if (!obj.constructor || obj.constructor === Object) throw new TypeError("unhashable type: \'dict\'")'
|
|
317
|
+
v'if (obj.ρσ_object_id === undefined) obj.ρσ_object_id = ++ρσ_hash_id_counter'
|
|
318
|
+
v'return obj.ρσ_object_id'
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def ρσ_next(iterator, defval):
|
|
322
|
+
if iterator is None or iterator is undefined:
|
|
323
|
+
raise TypeError('object is not an iterator')
|
|
324
|
+
if jstype(iterator.next) is 'function':
|
|
325
|
+
r = iterator.next()
|
|
326
|
+
if r.done:
|
|
327
|
+
if arguments.length > 1:
|
|
328
|
+
return defval
|
|
329
|
+
raise StopIteration()
|
|
330
|
+
return r.value
|
|
331
|
+
if jstype(iterator.__next__) is 'function':
|
|
332
|
+
try:
|
|
333
|
+
return iterator.__next__()
|
|
334
|
+
except StopIteration:
|
|
335
|
+
if arguments.length > 1:
|
|
336
|
+
return defval
|
|
337
|
+
raise
|
|
338
|
+
raise TypeError("object is not an iterator")
|
|
339
|
+
|
|
340
|
+
|
|
247
341
|
def ρσ_divmod(x, y):
|
|
248
342
|
if y is 0:
|
|
249
343
|
raise ZeroDivisionError('integer division or modulo by zero')
|
|
@@ -272,8 +366,8 @@ def ρσ_max(*args, **kwargs):
|
|
|
272
366
|
v'var abs = Math.abs, max = ρσ_max.bind(Math.max), min = ρσ_max.bind(Math.min), bool = ρσ_bool, type = ρσ_type'
|
|
273
367
|
v'var float = ρσ_float, int = ρσ_int, arraylike = ρσ_arraylike_creator(), ρσ_arraylike = arraylike'
|
|
274
368
|
v'var id = ρσ_id, get_module = ρσ_get_module, pow = ρσ_pow, divmod = ρσ_divmod'
|
|
275
|
-
v'var dir = ρσ_dir, ord = ρσ_ord, chr = ρσ_chr, bin = ρσ_bin, hex = ρσ_hex, callable = ρσ_callable'
|
|
369
|
+
v'var dir = ρσ_dir, ord = ρσ_ord, chr = ρσ_chr, bin = ρσ_bin, hex = ρσ_hex, callable = ρσ_callable, round = ρσ_round'
|
|
276
370
|
v'var enumerate = ρσ_enumerate, iter = ρσ_iter, reversed = ρσ_reversed, len = ρσ_len'
|
|
277
|
-
v'var range = ρσ_range, getattr = ρσ_getattr, setattr = ρσ_setattr, hasattr = ρσ_hasattr'
|
|
371
|
+
v'var range = ρσ_range, getattr = ρσ_getattr, setattr = ρσ_setattr, hasattr = ρσ_hasattr, issubclass = ρσ_issubclass, hash = ρσ_hash, next = ρσ_next'
|
|
278
372
|
v'var ρσ_Ellipsis = Object.freeze({toString: function(){return "Ellipsis";}, __repr__: function(){return "Ellipsis";}})'
|
|
279
373
|
v'var Ellipsis = ρσ_Ellipsis'
|