efrog 0.4.0__py3-none-any.whl
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.
- efrog/__init__.py +46 -0
- efrog/cli.py +232 -0
- efrog/decompilers/__init__.py +1 -0
- efrog/decompilers/c.py +339 -0
- efrog/decompilers/javascript.py +472 -0
- efrog/decompilers/matlab.py +526 -0
- efrog/decompilers/python.py +737 -0
- efrog/decompilers/rust.py +659 -0
- efrog/genome.py +340 -0
- efrog/lean.py +302 -0
- efrog/mic.py +188 -0
- efrog/optimize.py +386 -0
- efrog/profile.py +146 -0
- efrog/verify.py +384 -0
- efrog-0.4.0.dist-info/METADATA +192 -0
- efrog-0.4.0.dist-info/RECORD +19 -0
- efrog-0.4.0.dist-info/WHEEL +5 -0
- efrog-0.4.0.dist-info/entry_points.txt +2 -0
- efrog-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""JavaScript / TypeScript-lite → EML.
|
|
2
|
+
|
|
3
|
+
Phase E2.5 — third decompiler. Wraps :mod:`esprima` (pure Python, no
|
|
4
|
+
native deps) and lowers the same shape we accept in Python and C: a
|
|
5
|
+
function declaration (or an arrow function bound at module scope)
|
|
6
|
+
with a parameter list and a body that ends in a single returned
|
|
7
|
+
expression.
|
|
8
|
+
|
|
9
|
+
What we accept in this revision:
|
|
10
|
+
|
|
11
|
+
- ``function f(x, y) { return <expr>; }``
|
|
12
|
+
- ``const f = (x, y) => <expr>;`` and ``= (x) => { return ...; }``
|
|
13
|
+
- ``var`` / ``let`` / ``const`` local declarations → ``let`` bindings
|
|
14
|
+
- ``Math.exp(...)``, ``Math.sin(...)``, ``Math.PI``, etc.
|
|
15
|
+
- Numeric literals (``1.5``, ``2e-3``) and unary ``+``/``-``
|
|
16
|
+
- JS arithmetic operators with ECMAScript precedence
|
|
17
|
+
- ``cond ? then : else`` ternaries (lowered branch-free)
|
|
18
|
+
|
|
19
|
+
What we don't (yet):
|
|
20
|
+
|
|
21
|
+
- ``if`` / ``for`` / ``while`` / ``switch`` (E3)
|
|
22
|
+
- Object literals, destructuring, classes, ``this`` (E3+)
|
|
23
|
+
- String operations, template literals, regular expressions
|
|
24
|
+
- TypeScript type annotations are *tolerated* (esprima parses
|
|
25
|
+
plain JS) — pass ``--lang javascript`` for ``.ts`` files that
|
|
26
|
+
don't actually use generics or annotations.
|
|
27
|
+
|
|
28
|
+
Anything else raises :class:`EFrogError` pointing at later phases.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from typing import List, Optional, Tuple
|
|
36
|
+
|
|
37
|
+
import esprima
|
|
38
|
+
|
|
39
|
+
from .python import (
|
|
40
|
+
DecompiledFunction,
|
|
41
|
+
DecompiledModule,
|
|
42
|
+
EFrogError,
|
|
43
|
+
_BUILTIN_FUNCS,
|
|
44
|
+
_PYTHON_TO_EML_FN,
|
|
45
|
+
_chain_order,
|
|
46
|
+
_emit_float,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ── Public entry points ──────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def decompile_javascript_source(
|
|
54
|
+
source: str,
|
|
55
|
+
*,
|
|
56
|
+
source_path: Optional[str] = None,
|
|
57
|
+
module_name_override: Optional[str] = None,
|
|
58
|
+
) -> DecompiledModule:
|
|
59
|
+
try:
|
|
60
|
+
tree = esprima.parseScript(source, {"loc": True})
|
|
61
|
+
except esprima.Error as exc:
|
|
62
|
+
raise EFrogError(f"JavaScript parse error: {exc}") from exc
|
|
63
|
+
|
|
64
|
+
module_name = (
|
|
65
|
+
module_name_override
|
|
66
|
+
or _module_name_from_path(source_path)
|
|
67
|
+
or "module"
|
|
68
|
+
)
|
|
69
|
+
mod = DecompiledModule(name=module_name, source_path=source_path)
|
|
70
|
+
|
|
71
|
+
for node in tree.body:
|
|
72
|
+
decl = _as_function_declaration(node)
|
|
73
|
+
if decl is None:
|
|
74
|
+
# Top-level statements we don't decompile (imports, side-effects)
|
|
75
|
+
# are silently skipped — same convention as the Python decompiler.
|
|
76
|
+
continue
|
|
77
|
+
mod.functions.append(_decompile_function(decl, mod))
|
|
78
|
+
|
|
79
|
+
if not mod.functions:
|
|
80
|
+
raise EFrogError(
|
|
81
|
+
"no top-level function declarations found — nothing to decompile"
|
|
82
|
+
)
|
|
83
|
+
return mod
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def decompile_javascript_file(path: str) -> DecompiledModule:
|
|
87
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
88
|
+
return decompile_javascript_source(f.read(), source_path=path)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ── Helpers: top-level recognition ──────────────────────────────────
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class _FuncShape:
|
|
96
|
+
name: str
|
|
97
|
+
params: List[object]
|
|
98
|
+
body: object # BlockStatement or expression node
|
|
99
|
+
body_is_expr: bool
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class _Ctx:
|
|
104
|
+
mod: DecompiledModule
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _module_name_from_path(path: Optional[str]) -> Optional[str]:
|
|
108
|
+
if not path:
|
|
109
|
+
return None
|
|
110
|
+
stem = os.path.splitext(os.path.basename(path))[0]
|
|
111
|
+
return stem or None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _as_function_declaration(node) -> Optional[_FuncShape]:
|
|
115
|
+
"""Return a :class:`_FuncShape` if `node` is a top-level function we
|
|
116
|
+
can decompile (declaration form or `const NAME = (…) => …`)."""
|
|
117
|
+
t = node.type
|
|
118
|
+
if t == "FunctionDeclaration":
|
|
119
|
+
return _FuncShape(
|
|
120
|
+
name=node.id.name,
|
|
121
|
+
params=node.params,
|
|
122
|
+
body=node.body,
|
|
123
|
+
body_is_expr=False,
|
|
124
|
+
)
|
|
125
|
+
if t == "VariableDeclaration":
|
|
126
|
+
if len(node.declarations) != 1:
|
|
127
|
+
return None
|
|
128
|
+
d = node.declarations[0]
|
|
129
|
+
if d.id.type != "Identifier" or d.init is None:
|
|
130
|
+
return None
|
|
131
|
+
init_t = d.init.type
|
|
132
|
+
if init_t == "ArrowFunctionExpression":
|
|
133
|
+
return _FuncShape(
|
|
134
|
+
name=d.id.name,
|
|
135
|
+
params=d.init.params,
|
|
136
|
+
body=d.init.body,
|
|
137
|
+
body_is_expr=(d.init.body.type != "BlockStatement"),
|
|
138
|
+
)
|
|
139
|
+
if init_t == "FunctionExpression":
|
|
140
|
+
return _FuncShape(
|
|
141
|
+
name=d.id.name,
|
|
142
|
+
params=d.init.params,
|
|
143
|
+
body=d.init.body,
|
|
144
|
+
body_is_expr=False,
|
|
145
|
+
)
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ── Function decompilation ──────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _decompile_function(shape: _FuncShape, mod: DecompiledModule) -> DecompiledFunction:
|
|
153
|
+
args: List[Tuple[str, str]] = []
|
|
154
|
+
for p in shape.params:
|
|
155
|
+
if p.type != "Identifier":
|
|
156
|
+
# Default values, rest params, destructuring patterns —
|
|
157
|
+
# all out of scope for the math-substrate slice.
|
|
158
|
+
raise EFrogError(
|
|
159
|
+
f"unsupported param node `{p.type}` in JS decompiler"
|
|
160
|
+
)
|
|
161
|
+
args.append((p.name, "Real"))
|
|
162
|
+
|
|
163
|
+
ctx = _Ctx(mod=mod)
|
|
164
|
+
if shape.body_is_expr:
|
|
165
|
+
let_bindings: List[Tuple[str, str]] = []
|
|
166
|
+
body_expr = _expr(shape.body, 0, ctx)
|
|
167
|
+
else:
|
|
168
|
+
let_bindings, body_expr = _decompile_body(shape.body, ctx)
|
|
169
|
+
|
|
170
|
+
chain = _chain_order(body_expr)
|
|
171
|
+
for _, e in let_bindings:
|
|
172
|
+
chain = max(chain, _chain_order(e))
|
|
173
|
+
|
|
174
|
+
return DecompiledFunction(
|
|
175
|
+
name=shape.name,
|
|
176
|
+
args=args,
|
|
177
|
+
return_type="Real",
|
|
178
|
+
chain_order=chain,
|
|
179
|
+
let_bindings=let_bindings,
|
|
180
|
+
body_expr=body_expr,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _decompile_body(block, ctx: _Ctx) -> Tuple[List[Tuple[str, str]], str]:
|
|
185
|
+
if block is None or block.type != "BlockStatement":
|
|
186
|
+
raise EFrogError("function body must be a block")
|
|
187
|
+
|
|
188
|
+
let_bindings: List[Tuple[str, str]] = []
|
|
189
|
+
return_expr: Optional[str] = None
|
|
190
|
+
seen_return = False
|
|
191
|
+
|
|
192
|
+
for stmt in block.body:
|
|
193
|
+
if seen_return:
|
|
194
|
+
raise EFrogError("statements after return not supported in JS")
|
|
195
|
+
|
|
196
|
+
t = stmt.type
|
|
197
|
+
if t == "VariableDeclaration":
|
|
198
|
+
for d in stmt.declarations:
|
|
199
|
+
if d.id.type != "Identifier":
|
|
200
|
+
raise EFrogError(
|
|
201
|
+
"destructured declarations unsupported in JS"
|
|
202
|
+
)
|
|
203
|
+
if d.init is None:
|
|
204
|
+
raise EFrogError(
|
|
205
|
+
f"local `{d.id.name}` declared without initializer"
|
|
206
|
+
)
|
|
207
|
+
let_bindings.append((d.id.name, _expr(d.init, 0, ctx)))
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
if t == "ExpressionStatement":
|
|
211
|
+
expr = stmt.expression
|
|
212
|
+
if expr.type != "AssignmentExpression":
|
|
213
|
+
raise EFrogError(
|
|
214
|
+
f"top-level `{expr.type}` statement unsupported in JS"
|
|
215
|
+
)
|
|
216
|
+
if expr.left.type != "Identifier":
|
|
217
|
+
raise EFrogError("only assignments to a name supported in JS")
|
|
218
|
+
if expr.operator == "=":
|
|
219
|
+
let_bindings.append((expr.left.name, _expr(expr.right, 0, ctx)))
|
|
220
|
+
elif expr.operator in _COMPOUND_ASSIGN:
|
|
221
|
+
op = _COMPOUND_ASSIGN[expr.operator]
|
|
222
|
+
rhs = _expr(expr.right, 0, ctx)
|
|
223
|
+
let_bindings.append(
|
|
224
|
+
(expr.left.name, f"{expr.left.name} {op} {rhs}")
|
|
225
|
+
)
|
|
226
|
+
else:
|
|
227
|
+
raise EFrogError(
|
|
228
|
+
f"compound assign `{expr.operator}` unsupported in JS"
|
|
229
|
+
)
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
if t == "ReturnStatement":
|
|
233
|
+
if stmt.argument is None:
|
|
234
|
+
raise EFrogError("bare `return;` not supported in JS")
|
|
235
|
+
return_expr = _expr(stmt.argument, 0, ctx)
|
|
236
|
+
seen_return = True
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
raise EFrogError(
|
|
240
|
+
f"`{t}` not supported in JS decompiler "
|
|
241
|
+
f"(if/for/while go to E3+)"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if return_expr is None:
|
|
245
|
+
raise EFrogError("function has no `return` statement")
|
|
246
|
+
return let_bindings, return_expr
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
_COMPOUND_ASSIGN = {
|
|
250
|
+
"+=": "+", "-=": "-", "*=": "*", "/=": "/", "%=": "%",
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# ── Expression printer ──────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# Subset of ECMA-262 binary operator precedence, restricted to what
|
|
258
|
+
# the math substrate accepts. Higher binds tighter.
|
|
259
|
+
_JS_PREC = {
|
|
260
|
+
"+": 10, "-": 10,
|
|
261
|
+
"*": 20, "/": 20, "%": 20,
|
|
262
|
+
"**": 30,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_JS_OP = {
|
|
266
|
+
"+": "+", "-": "-",
|
|
267
|
+
"*": "*", "/": "/", "%": "%",
|
|
268
|
+
"**": "^",
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# JS `Math.<member>` → EML callable name. Anything not listed errors.
|
|
273
|
+
_MATH_MEMBER = {
|
|
274
|
+
"exp": "exp",
|
|
275
|
+
"log": "log",
|
|
276
|
+
"log2": "log2",
|
|
277
|
+
"log10": "log10",
|
|
278
|
+
"sin": "sin",
|
|
279
|
+
"cos": "cos",
|
|
280
|
+
"tan": "tan",
|
|
281
|
+
"asin": "asin",
|
|
282
|
+
"acos": "acos",
|
|
283
|
+
"atan": "atan",
|
|
284
|
+
"sinh": "sinh",
|
|
285
|
+
"cosh": "cosh",
|
|
286
|
+
"tanh": "tanh",
|
|
287
|
+
"asinh": "asinh",
|
|
288
|
+
"acosh": "acosh",
|
|
289
|
+
"atanh": "atanh",
|
|
290
|
+
"sqrt": "sqrt",
|
|
291
|
+
"pow": "pow",
|
|
292
|
+
"abs": "abs",
|
|
293
|
+
"min": "min",
|
|
294
|
+
"max": "max",
|
|
295
|
+
"floor": "floor",
|
|
296
|
+
"ceil": "ceil",
|
|
297
|
+
"round": "round",
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
_MATH_CONSTS = {
|
|
301
|
+
"PI": 3.141592653589793,
|
|
302
|
+
"E": 2.718281828459045,
|
|
303
|
+
"LN2": 0.6931471805599453,
|
|
304
|
+
"LN10": 2.302585092994046,
|
|
305
|
+
"LOG2E": 1.4426950408889634,
|
|
306
|
+
"LOG10E": 0.4342944819032518,
|
|
307
|
+
"SQRT2": 1.4142135623730951,
|
|
308
|
+
"SQRT1_2": 0.7071067811865476,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _expr(node, parent_prec: int, ctx: _Ctx) -> str:
|
|
313
|
+
t = node.type
|
|
314
|
+
|
|
315
|
+
if t == "Literal":
|
|
316
|
+
return _emit_literal(node)
|
|
317
|
+
|
|
318
|
+
if t == "Identifier":
|
|
319
|
+
return node.name
|
|
320
|
+
|
|
321
|
+
if t == "UnaryExpression":
|
|
322
|
+
if not node.prefix:
|
|
323
|
+
raise EFrogError("postfix unary operators unsupported in JS")
|
|
324
|
+
if node.operator == "-":
|
|
325
|
+
inner = _expr(node.argument, _JS_PREC["*"], ctx)
|
|
326
|
+
return _wrap(f"-{inner}", _JS_PREC["*"], parent_prec)
|
|
327
|
+
if node.operator == "+":
|
|
328
|
+
return _expr(node.argument, parent_prec, ctx)
|
|
329
|
+
if node.operator == "!":
|
|
330
|
+
ctx.mod.needs_step01 = True
|
|
331
|
+
return f"(1.0 - {_condition_expr(node.argument, ctx)})"
|
|
332
|
+
raise EFrogError(f"unary `{node.operator}` unsupported in JS")
|
|
333
|
+
|
|
334
|
+
if t in ("BinaryExpression", "LogicalExpression"):
|
|
335
|
+
op = node.operator
|
|
336
|
+
if op not in _JS_OP:
|
|
337
|
+
raise EFrogError(f"binary `{op}` unsupported in JS")
|
|
338
|
+
prec = _JS_PREC[op]
|
|
339
|
+
# `**` is right-associative in ECMAScript; everything else left-assoc.
|
|
340
|
+
if op == "**":
|
|
341
|
+
left = _expr(node.left, prec + 1, ctx)
|
|
342
|
+
right = _expr(node.right, prec, ctx)
|
|
343
|
+
else:
|
|
344
|
+
left = _expr(node.left, prec, ctx)
|
|
345
|
+
right = _expr(node.right, prec + 1, ctx)
|
|
346
|
+
return _wrap(f"{left} {_JS_OP[op]} {right}", prec, parent_prec)
|
|
347
|
+
|
|
348
|
+
if t == "CallExpression":
|
|
349
|
+
return _emit_call(node, ctx)
|
|
350
|
+
|
|
351
|
+
if t == "MemberExpression":
|
|
352
|
+
const = _member_constant(node)
|
|
353
|
+
if const is not None:
|
|
354
|
+
return _emit_float(const)
|
|
355
|
+
raise EFrogError(
|
|
356
|
+
f"member access `{_describe_member(node)}` unsupported in JS"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if t == "ConditionalExpression":
|
|
360
|
+
cond = _condition_expr(node.test, ctx)
|
|
361
|
+
then = _expr(node.consequent, 0, ctx)
|
|
362
|
+
otherwise = _expr(node.alternate, 0, ctx)
|
|
363
|
+
ctx.mod.needs_step01 = True
|
|
364
|
+
return f"lerp({otherwise}, {then}, {cond})"
|
|
365
|
+
|
|
366
|
+
raise EFrogError(f"`{t}` unsupported in JS decompiler")
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _wrap(s: str, my_prec: int, parent_prec: int) -> str:
|
|
370
|
+
return f"({s})" if my_prec < parent_prec else s
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _emit_literal(node) -> str:
|
|
374
|
+
v = node.value
|
|
375
|
+
if isinstance(v, bool):
|
|
376
|
+
return "1.0" if v else "0.0"
|
|
377
|
+
if isinstance(v, (int, float)):
|
|
378
|
+
return _emit_float(float(v))
|
|
379
|
+
raise EFrogError(f"unsupported literal: {v!r}")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _member_constant(node) -> Optional[float]:
|
|
383
|
+
if (
|
|
384
|
+
getattr(node, "object", None) is not None
|
|
385
|
+
and node.object.type == "Identifier"
|
|
386
|
+
and node.object.name == "Math"
|
|
387
|
+
and not node.computed
|
|
388
|
+
and node.property.type == "Identifier"
|
|
389
|
+
):
|
|
390
|
+
return _MATH_CONSTS.get(node.property.name)
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _describe_member(node) -> str:
|
|
395
|
+
obj = node.object
|
|
396
|
+
obj_name = obj.name if obj.type == "Identifier" else "<expr>"
|
|
397
|
+
prop = node.property
|
|
398
|
+
prop_name = prop.name if prop.type == "Identifier" else "<expr>"
|
|
399
|
+
return f"{obj_name}.{prop_name}"
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _emit_call(node, ctx: _Ctx) -> str:
|
|
403
|
+
name = _resolve_call_name(node.callee)
|
|
404
|
+
if name is None:
|
|
405
|
+
raise EFrogError(
|
|
406
|
+
f"unrecognised call target `{_describe_callee(node.callee)}`"
|
|
407
|
+
)
|
|
408
|
+
if name not in _BUILTIN_FUNCS:
|
|
409
|
+
raise EFrogError(
|
|
410
|
+
f"call to non-math function `{name}` unsupported in JS"
|
|
411
|
+
)
|
|
412
|
+
eml_name = _PYTHON_TO_EML_FN.get(name, name)
|
|
413
|
+
args = ", ".join(_expr(a, 0, ctx) for a in node.arguments)
|
|
414
|
+
return f"{eml_name}({args})"
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _resolve_call_name(callee) -> Optional[str]:
|
|
418
|
+
if callee.type == "Identifier":
|
|
419
|
+
return callee.name
|
|
420
|
+
if (
|
|
421
|
+
callee.type == "MemberExpression"
|
|
422
|
+
and not callee.computed
|
|
423
|
+
and callee.object.type == "Identifier"
|
|
424
|
+
and callee.object.name == "Math"
|
|
425
|
+
and callee.property.type == "Identifier"
|
|
426
|
+
):
|
|
427
|
+
return _MATH_MEMBER.get(callee.property.name)
|
|
428
|
+
return None
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _describe_callee(callee) -> str:
|
|
432
|
+
if callee.type == "Identifier":
|
|
433
|
+
return callee.name
|
|
434
|
+
if callee.type == "MemberExpression":
|
|
435
|
+
return _describe_member(callee)
|
|
436
|
+
return f"<{callee.type}>"
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# ── Conditional → branch-free selector ──────────────────────────────
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
_CMP_TEMPLATE = {
|
|
443
|
+
">": "step01(({left}) - ({right}))",
|
|
444
|
+
">=": "step01(({left}) - ({right}))",
|
|
445
|
+
"<": "step01(({right}) - ({left}))",
|
|
446
|
+
"<=": "step01(({right}) - ({left}))",
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _condition_expr(node, ctx: _Ctx) -> str:
|
|
451
|
+
t = node.type
|
|
452
|
+
if t == "BinaryExpression" and node.operator in _CMP_TEMPLATE:
|
|
453
|
+
ctx.mod.needs_step01 = True
|
|
454
|
+
left = _expr(node.left, 0, ctx)
|
|
455
|
+
right = _expr(node.right, 0, ctx)
|
|
456
|
+
return _CMP_TEMPLATE[node.operator].format(left=left, right=right)
|
|
457
|
+
|
|
458
|
+
if t == "LogicalExpression":
|
|
459
|
+
if node.operator == "&&":
|
|
460
|
+
return f"(({_condition_expr(node.left, ctx)}) * ({_condition_expr(node.right, ctx)}))"
|
|
461
|
+
if node.operator == "||":
|
|
462
|
+
inv_l = f"(1.0 - {_condition_expr(node.left, ctx)})"
|
|
463
|
+
inv_r = f"(1.0 - {_condition_expr(node.right, ctx)})"
|
|
464
|
+
return f"(1.0 - ({inv_l} * {inv_r}))"
|
|
465
|
+
|
|
466
|
+
if t == "UnaryExpression" and node.operator == "!":
|
|
467
|
+
return f"(1.0 - {_condition_expr(node.argument, ctx)})"
|
|
468
|
+
|
|
469
|
+
raise EFrogError(
|
|
470
|
+
f"can't encode JS condition `{t}` branch-free; "
|
|
471
|
+
f"rewrite as <, <=, >, >=, &&, ||, !"
|
|
472
|
+
)
|