py2dag 0.3.3__tar.gz → 0.3.5__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.
- {py2dag-0.3.3 → py2dag-0.3.5}/PKG-INFO +1 -1
- {py2dag-0.3.3 → py2dag-0.3.5}/py2dag/parser.py +98 -21
- {py2dag-0.3.3 → py2dag-0.3.5}/pyproject.toml +1 -1
- {py2dag-0.3.3 → py2dag-0.3.5}/LICENSE +0 -0
- {py2dag-0.3.3 → py2dag-0.3.5}/README.md +0 -0
- {py2dag-0.3.3 → py2dag-0.3.5}/py2dag/__init__.py +0 -0
- {py2dag-0.3.3 → py2dag-0.3.5}/py2dag/cli.py +0 -0
- {py2dag-0.3.3 → py2dag-0.3.5}/py2dag/colors.py +0 -0
- {py2dag-0.3.3 → py2dag-0.3.5}/py2dag/export_dagre.py +0 -0
- {py2dag-0.3.3 → py2dag-0.3.5}/py2dag/export_svg.py +0 -0
- {py2dag-0.3.3 → py2dag-0.3.5}/py2dag/pseudo.py +0 -0
@@ -63,6 +63,7 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
63
63
|
latest: Dict[str, str] = {}
|
64
64
|
context_suffix: str = ""
|
65
65
|
ctx_counts: Dict[str, int] = {"if": 0, "loop": 0, "while": 0, "except": 0}
|
66
|
+
loop_depth = 0
|
66
67
|
|
67
68
|
def _ssa_new(name: str) -> str:
|
68
69
|
if not VALID_NAME_RE.match(name):
|
@@ -331,6 +332,29 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
331
332
|
})
|
332
333
|
return ssa
|
333
334
|
|
335
|
+
def _emit_assign_to_subscript(target: ast.Subscript, value: ast.AST) -> str:
|
336
|
+
"""Emit a SET.item node for an assignment like ``name[key] = value``."""
|
337
|
+
base = target.value
|
338
|
+
if not isinstance(base, ast.Name):
|
339
|
+
raise DSLParseError("Subscript base must be a variable name")
|
340
|
+
# Extract slice expression across Python versions
|
341
|
+
sl = getattr(target, "slice", None)
|
342
|
+
if hasattr(ast, "Index") and isinstance(sl, getattr(ast, "Index")): # type: ignore[attr-defined]
|
343
|
+
sl = sl.value # type: ignore[assignment]
|
344
|
+
key = _literal(sl)
|
345
|
+
|
346
|
+
val_id = _emit_value(f"{base.id}_item", value)
|
347
|
+
|
348
|
+
base_id = _ssa_get(base.id)
|
349
|
+
ssa = _ssa_new(base.id)
|
350
|
+
ops.append({
|
351
|
+
"id": ssa,
|
352
|
+
"op": "SET.item",
|
353
|
+
"deps": [base_id, val_id],
|
354
|
+
"args": {"key": key},
|
355
|
+
})
|
356
|
+
return ssa
|
357
|
+
|
334
358
|
def _emit_cond(node: ast.AST, kind: str = "if") -> str:
|
335
359
|
expr = _stringify(node)
|
336
360
|
deps = [_ssa_get(n) for n in _collect_value_deps(node)]
|
@@ -338,6 +362,51 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
338
362
|
ops.append({"id": ssa, "op": "COND.eval", "deps": deps, "args": {"expr": expr, "kind": kind}})
|
339
363
|
return ssa
|
340
364
|
|
365
|
+
def _emit_assign_from_ifexp(var_name: str, node: ast.IfExp) -> str:
|
366
|
+
"""Emit operations for an inline ``a if cond else b`` expression."""
|
367
|
+
cond_id = _emit_cond(node.test, kind="ifexp")
|
368
|
+
|
369
|
+
then_start = len(ops)
|
370
|
+
then_id = _emit_value(f"{var_name}_then", node.body)
|
371
|
+
if len(ops) > then_start:
|
372
|
+
first = ops[then_start]
|
373
|
+
deps0 = first.get("deps", []) or []
|
374
|
+
if cond_id not in deps0:
|
375
|
+
first["deps"] = [*deps0, cond_id]
|
376
|
+
|
377
|
+
else_start = len(ops)
|
378
|
+
else_id = _emit_value(f"{var_name}_else", node.orelse)
|
379
|
+
if len(ops) > else_start:
|
380
|
+
first = ops[else_start]
|
381
|
+
deps0 = first.get("deps", []) or []
|
382
|
+
if cond_id not in deps0:
|
383
|
+
first["deps"] = [*deps0, cond_id]
|
384
|
+
|
385
|
+
ssa = _ssa_new(var_name)
|
386
|
+
ops.append({"id": ssa, "op": "PHI", "deps": [then_id, else_id], "args": {"var": var_name}})
|
387
|
+
return ssa
|
388
|
+
|
389
|
+
def _emit_value(var_name: str, value: ast.AST) -> str:
|
390
|
+
awaited = False
|
391
|
+
if isinstance(value, ast.Await):
|
392
|
+
value = value.value
|
393
|
+
awaited = True
|
394
|
+
if isinstance(value, ast.Call):
|
395
|
+
return _emit_assign_from_call(var_name, value, awaited)
|
396
|
+
if isinstance(value, ast.JoinedStr):
|
397
|
+
return _emit_assign_from_fstring(var_name, value)
|
398
|
+
if isinstance(value, (ast.Constant, ast.List, ast.Tuple, ast.Dict)):
|
399
|
+
return _emit_assign_from_literal_or_pack(var_name, value)
|
400
|
+
if isinstance(value, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
|
401
|
+
return _emit_assign_from_comp(var_name, value)
|
402
|
+
if isinstance(value, ast.Subscript):
|
403
|
+
return _emit_assign_from_subscript(var_name, value)
|
404
|
+
if isinstance(value, ast.IfExp):
|
405
|
+
return _emit_assign_from_ifexp(var_name, value)
|
406
|
+
if isinstance(value, ast.Name):
|
407
|
+
return _ssa_get(value.id)
|
408
|
+
raise DSLParseError("Right hand side must be a call or f-string")
|
409
|
+
|
341
410
|
def _emit_iter(node: ast.AST, target_label: Optional[str] = None) -> str:
|
342
411
|
expr = _stringify(node)
|
343
412
|
deps = [_ssa_get(n) for n in _collect_value_deps(node)]
|
@@ -349,28 +418,18 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
349
418
|
return ssa
|
350
419
|
|
351
420
|
def _parse_stmt(stmt: ast.stmt) -> Optional[str]:
|
352
|
-
nonlocal returned_var, versions, latest, context_suffix
|
421
|
+
nonlocal returned_var, versions, latest, context_suffix, loop_depth
|
353
422
|
if isinstance(stmt, ast.Assign):
|
354
|
-
if len(stmt.targets) != 1
|
355
|
-
raise DSLParseError("Assignment
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
if isinstance(value, ast.Call):
|
363
|
-
return _emit_assign_from_call(var_name, value, awaited)
|
364
|
-
elif isinstance(value, ast.JoinedStr):
|
365
|
-
return _emit_assign_from_fstring(var_name, value)
|
366
|
-
elif isinstance(value, (ast.Constant, ast.List, ast.Tuple, ast.Dict)):
|
367
|
-
return _emit_assign_from_literal_or_pack(var_name, value)
|
368
|
-
elif isinstance(value, (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp)):
|
369
|
-
return _emit_assign_from_comp(var_name, value)
|
370
|
-
elif isinstance(value, ast.Subscript):
|
371
|
-
return _emit_assign_from_subscript(var_name, value)
|
423
|
+
if len(stmt.targets) != 1:
|
424
|
+
raise DSLParseError("Assignment must have exactly one target")
|
425
|
+
target = stmt.targets[0]
|
426
|
+
if isinstance(target, ast.Name):
|
427
|
+
var_name = target.id
|
428
|
+
return _emit_value(var_name, stmt.value)
|
429
|
+
elif isinstance(target, ast.Subscript):
|
430
|
+
return _emit_assign_to_subscript(target, stmt.value)
|
372
431
|
else:
|
373
|
-
raise DSLParseError("
|
432
|
+
raise DSLParseError("Assignment targets must be simple names")
|
374
433
|
elif isinstance(stmt, ast.Expr):
|
375
434
|
call = stmt.value
|
376
435
|
awaited = False
|
@@ -406,8 +465,22 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
406
465
|
_emit_expr_call(call, awaited)
|
407
466
|
return None
|
408
467
|
elif isinstance(stmt, ast.Return):
|
468
|
+
if loop_depth > 0:
|
469
|
+
ssa = _ssa_new("break")
|
470
|
+
ops.append({"id": ssa, "op": "CTRL.break", "deps": [], "args": {}})
|
409
471
|
if isinstance(stmt.value, ast.Name):
|
410
|
-
|
472
|
+
name = stmt.value.id
|
473
|
+
if name in latest:
|
474
|
+
returned_var = _ssa_get(name)
|
475
|
+
else:
|
476
|
+
const_id = _ssa_new("return_value")
|
477
|
+
ops.append({
|
478
|
+
"id": const_id,
|
479
|
+
"op": "CONST.value",
|
480
|
+
"deps": [],
|
481
|
+
"args": {"value": None},
|
482
|
+
})
|
483
|
+
returned_var = const_id
|
411
484
|
elif isinstance(stmt.value, (ast.Constant, ast.List, ast.Tuple, ast.Dict)):
|
412
485
|
lit = _literal(stmt.value)
|
413
486
|
const_id = _ssa_new("return_value")
|
@@ -503,6 +576,7 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
503
576
|
saved_ctx = context_suffix
|
504
577
|
ctx_counts["loop"] += 1
|
505
578
|
context_suffix = f"loop{ctx_counts['loop']}"
|
579
|
+
loop_depth += 1
|
506
580
|
versions, latest = versions_body, latest_body
|
507
581
|
# Predefine loop target variables as items from iterator for dependency resolution
|
508
582
|
def _bind_loop_target(target: ast.AST):
|
@@ -529,6 +603,7 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
529
603
|
_bind_loop_target(stmt.target)
|
530
604
|
for inner in stmt.body:
|
531
605
|
_parse_stmt(inner)
|
606
|
+
loop_depth -= 1
|
532
607
|
versions_body, latest_body = versions, latest
|
533
608
|
versions, latest = saved_versions, saved_latest
|
534
609
|
context_suffix = saved_ctx
|
@@ -571,9 +646,11 @@ def parse(source: str, function_name: Optional[str] = None) -> Dict[str, Any]:
|
|
571
646
|
saved_ctx = context_suffix
|
572
647
|
ctx_counts["while"] += 1
|
573
648
|
context_suffix = f"while{ctx_counts['while']}"
|
649
|
+
loop_depth += 1
|
574
650
|
versions, latest = versions_body, latest_body
|
575
651
|
for inner in stmt.body:
|
576
652
|
_parse_stmt(inner)
|
653
|
+
loop_depth -= 1
|
577
654
|
versions_body, latest_body = versions, latest
|
578
655
|
versions, latest = saved_versions, saved_latest
|
579
656
|
context_suffix = saved_ctx
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|