astichi 0.1.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.
- astichi/__init__.py +17 -0
- astichi/ast_provenance.py +131 -0
- astichi/asttools/__init__.py +40 -0
- astichi/asttools/imports.py +47 -0
- astichi/asttools/inserts.py +33 -0
- astichi/asttools/scopes.py +157 -0
- astichi/asttools/shapes.py +86 -0
- astichi/builder/__init__.py +35 -0
- astichi/builder/api.py +10 -0
- astichi/builder/graph.py +452 -0
- astichi/builder/handles.py +1257 -0
- astichi/diagnostics/__init__.py +13 -0
- astichi/diagnostics/formatting.py +48 -0
- astichi/emit/__init__.py +19 -0
- astichi/emit/api.py +87 -0
- astichi/frontend/__init__.py +18 -0
- astichi/frontend/api.py +239 -0
- astichi/frontend/compiled.py +9 -0
- astichi/frontend/source_kind.py +51 -0
- astichi/hygiene/__init__.py +29 -0
- astichi/hygiene/api.py +1316 -0
- astichi/lowering/__init__.py +129 -0
- astichi/lowering/boundaries.py +324 -0
- astichi/lowering/call_argument_payloads.py +375 -0
- astichi/lowering/external_bind.py +492 -0
- astichi/lowering/external_ref.py +517 -0
- astichi/lowering/marker_contexts.py +61 -0
- astichi/lowering/markers.py +1260 -0
- astichi/lowering/parameters.py +138 -0
- astichi/lowering/pyimport.py +371 -0
- astichi/lowering/sentinel_attrs.py +41 -0
- astichi/lowering/unroll.py +565 -0
- astichi/lowering/unroll_domain.py +147 -0
- astichi/materialize/__init__.py +9 -0
- astichi/materialize/api.py +4227 -0
- astichi/materialize/pyimport.py +217 -0
- astichi/model/__init__.py +99 -0
- astichi/model/basic.py +639 -0
- astichi/model/composable.py +29 -0
- astichi/model/descriptors.py +398 -0
- astichi/model/external_values.py +224 -0
- astichi/model/origin.py +14 -0
- astichi/model/ports.py +295 -0
- astichi/model/semantics.py +352 -0
- astichi/path_resolution.py +710 -0
- astichi/shell_refs.py +340 -0
- astichi-0.1.0.dist-info/METADATA +310 -0
- astichi-0.1.0.dist-info/RECORD +50 -0
- astichi-0.1.0.dist-info/WHEEL +4 -0
- astichi-0.1.0.dist-info/licenses/LICENSE +21 -0
astichi/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""astichi — AST composition for ahead-of-time Python codegen."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from astichi.builder import build
|
|
6
|
+
from astichi.frontend import compile
|
|
7
|
+
from astichi.model import Composable, ComposableDescription, ComposableHole, TargetAddress
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"__version__",
|
|
11
|
+
"Composable",
|
|
12
|
+
"ComposableDescription",
|
|
13
|
+
"ComposableHole",
|
|
14
|
+
"TargetAddress",
|
|
15
|
+
"build",
|
|
16
|
+
"compile",
|
|
17
|
+
]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""AST source location (lineno / col_offset) propagation for synthetic nodes.
|
|
2
|
+
|
|
3
|
+
Astichi constructs many `ast.AST` nodes programmatically. Those nodes must
|
|
4
|
+
inherit line/column information from the authored subtree they replace or from
|
|
5
|
+
an immediate surrounding node so diagnostics and downstream passes can anchor
|
|
6
|
+
errors without reading implementation code.
|
|
7
|
+
|
|
8
|
+
Policy:
|
|
9
|
+
|
|
10
|
+
- After building a fresh subtree, call :func:`propagate_ast_source_locations`
|
|
11
|
+
with a *donor* that already carries a valid ``lineno`` (typically the hole,
|
|
12
|
+
insert site, or a copied authored node).
|
|
13
|
+
- :func:`ast.fix_missing_locations` fills remaining gaps inside the subtree.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import ast
|
|
19
|
+
from collections.abc import Iterator
|
|
20
|
+
from typing import TypeGuard
|
|
21
|
+
|
|
22
|
+
ASTICHI_SRC_FILE_ATTR = "_astichi_src_file"
|
|
23
|
+
|
|
24
|
+
# `ast.type_param` exists on Python 3.12+; omit when absent (e.g. older runtimes).
|
|
25
|
+
_located: list[type] = [
|
|
26
|
+
ast.stmt,
|
|
27
|
+
ast.expr,
|
|
28
|
+
ast.excepthandler,
|
|
29
|
+
ast.pattern,
|
|
30
|
+
]
|
|
31
|
+
if hasattr(ast, "type_param"):
|
|
32
|
+
_located.append(ast.type_param)
|
|
33
|
+
_AST_LOCATION_TYPES: tuple[type, ...] = tuple(_located)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def requires_ast_source_location(node: ast.AST) -> bool:
|
|
37
|
+
"""Whether *node* should carry ``lineno`` for user-facing provenance."""
|
|
38
|
+
return isinstance(node, _AST_LOCATION_TYPES)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _lineno_ok(node: ast.AST) -> TypeGuard[ast.AST]:
|
|
42
|
+
lo = getattr(node, "lineno", None)
|
|
43
|
+
return isinstance(lo, int) and lo >= 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def has_valid_ast_source_location(node: ast.AST) -> bool:
|
|
47
|
+
"""Return True if *node* has a usable ``lineno`` (and is a located kind)."""
|
|
48
|
+
if not requires_ast_source_location(node):
|
|
49
|
+
return True
|
|
50
|
+
return _lineno_ok(node)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def iter_nodes_missing_ast_source_location(tree: ast.AST) -> Iterator[ast.AST]:
|
|
54
|
+
"""Yield located AST nodes that lack a valid ``lineno``."""
|
|
55
|
+
for node in ast.walk(tree):
|
|
56
|
+
if requires_ast_source_location(node) and not _lineno_ok(node):
|
|
57
|
+
yield node
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def first_ast_source_location_donor(tree: ast.AST) -> ast.AST | None:
|
|
61
|
+
"""Return the first node in *tree* that already has a valid ``lineno``."""
|
|
62
|
+
for node in ast.walk(tree):
|
|
63
|
+
if _lineno_ok(node):
|
|
64
|
+
return node
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def astichi_source_file(node: ast.AST) -> str | None:
|
|
69
|
+
"""Return Astichi's private source-file metadata for *node*, if present."""
|
|
70
|
+
value = getattr(node, ASTICHI_SRC_FILE_ATTR, None)
|
|
71
|
+
return value if isinstance(value, str) else None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def attach_astichi_source_file(tree: ast.AST, file_name: str) -> None:
|
|
75
|
+
"""Attach Astichi source-file metadata to every node in *tree*."""
|
|
76
|
+
for node in ast.walk(tree):
|
|
77
|
+
setattr(node, ASTICHI_SRC_FILE_ATTR, file_name)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def copy_astichi_location(target: ast.AST, source: ast.AST) -> ast.AST:
|
|
81
|
+
"""Copy Python and Astichi source location from *source* to *target*."""
|
|
82
|
+
ast.copy_location(target, source)
|
|
83
|
+
src_file = astichi_source_file(source)
|
|
84
|
+
if src_file is not None:
|
|
85
|
+
setattr(target, ASTICHI_SRC_FILE_ATTR, src_file)
|
|
86
|
+
return target
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def propagate_astichi_source_file(root: ast.AST, donor: ast.AST | None) -> None:
|
|
90
|
+
"""Fill missing Astichi source-file metadata on *root* from *donor*."""
|
|
91
|
+
if donor is None:
|
|
92
|
+
return
|
|
93
|
+
src_file = astichi_source_file(donor)
|
|
94
|
+
if src_file is None:
|
|
95
|
+
return
|
|
96
|
+
for node in ast.walk(root):
|
|
97
|
+
if astichi_source_file(node) is None:
|
|
98
|
+
setattr(node, ASTICHI_SRC_FILE_ATTR, src_file)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def propagate_ast_source_locations(root: ast.AST, donor: ast.AST | None) -> None:
|
|
102
|
+
"""Attach line/column info to *root* and its descendants.
|
|
103
|
+
|
|
104
|
+
If *donor* has a valid ``lineno``, copy it onto *root* with
|
|
105
|
+
:func:`ast.copy_location` (when supported), then run
|
|
106
|
+
:func:`ast.fix_missing_locations` on *root*.
|
|
107
|
+
|
|
108
|
+
If *donor* is missing or has no line, only :func:`ast.fix_missing_locations`
|
|
109
|
+
runs (best-effort defaults — callers should prefer a real donor).
|
|
110
|
+
"""
|
|
111
|
+
if isinstance(root, ast.Module):
|
|
112
|
+
ast.fix_missing_locations(root)
|
|
113
|
+
propagate_astichi_source_file(root, donor)
|
|
114
|
+
return
|
|
115
|
+
if donor is not None and _lineno_ok(donor):
|
|
116
|
+
try:
|
|
117
|
+
copy_astichi_location(root, donor)
|
|
118
|
+
except (TypeError, ValueError):
|
|
119
|
+
pass
|
|
120
|
+
ast.fix_missing_locations(root)
|
|
121
|
+
propagate_astichi_source_file(root, donor)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def assert_tree_has_ast_source_locations(tree: ast.AST) -> None:
|
|
125
|
+
"""Raise ``AssertionError`` if any located node lacks a valid ``lineno``."""
|
|
126
|
+
missing = tuple(iter_nodes_missing_ast_source_location(tree))
|
|
127
|
+
if missing:
|
|
128
|
+
kinds = ", ".join(sorted({type(n).__name__ for n in missing}))
|
|
129
|
+
raise AssertionError(
|
|
130
|
+
f"{len(missing)} AST node(s) lack source location (lineno): {kinds}"
|
|
131
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""AST helper utilities for Astichi."""
|
|
2
|
+
|
|
3
|
+
from astichi.asttools.shapes import (
|
|
4
|
+
BLOCK,
|
|
5
|
+
IDENTIFIER,
|
|
6
|
+
NAMED_VARIADIC,
|
|
7
|
+
PARAMETER,
|
|
8
|
+
POSITIONAL_VARIADIC,
|
|
9
|
+
SCALAR_EXPR,
|
|
10
|
+
MarkerShape,
|
|
11
|
+
)
|
|
12
|
+
from astichi.asttools.imports import (
|
|
13
|
+
import_alias_binding_name,
|
|
14
|
+
import_statement_binding_names,
|
|
15
|
+
)
|
|
16
|
+
from astichi.asttools.inserts import (
|
|
17
|
+
has_astichi_insert_decorator,
|
|
18
|
+
is_astichi_insert_call,
|
|
19
|
+
is_astichi_insert_shell,
|
|
20
|
+
is_expression_insert_call,
|
|
21
|
+
)
|
|
22
|
+
from astichi.asttools.scopes import AstichiScope, AstichiScopeMap
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"AstichiScope",
|
|
26
|
+
"AstichiScopeMap",
|
|
27
|
+
"BLOCK",
|
|
28
|
+
"IDENTIFIER",
|
|
29
|
+
"NAMED_VARIADIC",
|
|
30
|
+
"PARAMETER",
|
|
31
|
+
"POSITIONAL_VARIADIC",
|
|
32
|
+
"SCALAR_EXPR",
|
|
33
|
+
"MarkerShape",
|
|
34
|
+
"has_astichi_insert_decorator",
|
|
35
|
+
"import_alias_binding_name",
|
|
36
|
+
"import_statement_binding_names",
|
|
37
|
+
"is_astichi_insert_call",
|
|
38
|
+
"is_astichi_insert_shell",
|
|
39
|
+
"is_expression_insert_call",
|
|
40
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Helpers for Python import-statement binding names."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def import_alias_binding_name(
|
|
9
|
+
alias: ast.alias,
|
|
10
|
+
*,
|
|
11
|
+
from_import: bool,
|
|
12
|
+
include_star: bool = False,
|
|
13
|
+
) -> str | None:
|
|
14
|
+
"""Return the local name bound by one ordinary Python import alias."""
|
|
15
|
+
if from_import:
|
|
16
|
+
if alias.name == "*" and not include_star:
|
|
17
|
+
return None
|
|
18
|
+
return alias.asname or alias.name
|
|
19
|
+
return alias.asname or alias.name.split(".")[0]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def import_statement_binding_names(
|
|
23
|
+
node: ast.Import | ast.ImportFrom,
|
|
24
|
+
*,
|
|
25
|
+
include_star: bool = False,
|
|
26
|
+
) -> tuple[str, ...]:
|
|
27
|
+
"""Return the local binding names introduced by an import statement."""
|
|
28
|
+
names: list[str] = []
|
|
29
|
+
if isinstance(node, ast.Import):
|
|
30
|
+
for alias in node.names:
|
|
31
|
+
name = import_alias_binding_name(
|
|
32
|
+
alias,
|
|
33
|
+
from_import=False,
|
|
34
|
+
include_star=include_star,
|
|
35
|
+
)
|
|
36
|
+
if name is not None:
|
|
37
|
+
names.append(name)
|
|
38
|
+
return tuple(names)
|
|
39
|
+
for alias in node.names:
|
|
40
|
+
name = import_alias_binding_name(
|
|
41
|
+
alias,
|
|
42
|
+
from_import=True,
|
|
43
|
+
include_star=include_star,
|
|
44
|
+
)
|
|
45
|
+
if name is not None:
|
|
46
|
+
names.append(name)
|
|
47
|
+
return tuple(names)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Predicates for internal ``astichi_insert`` surfaces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
import ast
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def is_astichi_insert_call(node: ast.AST) -> bool:
|
|
10
|
+
"""Whether ``node`` is a direct ``astichi_insert(...)`` call."""
|
|
11
|
+
return (
|
|
12
|
+
isinstance(node, ast.Call)
|
|
13
|
+
and isinstance(node.func, ast.Name)
|
|
14
|
+
and node.func.id == "astichi_insert"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def is_expression_insert_call(node: ast.AST) -> bool:
|
|
19
|
+
"""Whether ``node`` is expression-form ``astichi_insert(target, payload)``."""
|
|
20
|
+
return is_astichi_insert_call(node) and len(node.args) == 2
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def has_astichi_insert_decorator(decorators: Iterable[ast.expr]) -> bool:
|
|
24
|
+
"""Whether a decorator list contains direct ``@astichi_insert(...)``."""
|
|
25
|
+
return any(is_astichi_insert_call(decorator) for decorator in decorators)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_astichi_insert_shell(node: ast.AST) -> bool:
|
|
29
|
+
"""Whether ``node`` is a class/def decorated by ``astichi_insert(...)``."""
|
|
30
|
+
return (
|
|
31
|
+
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
|
|
32
|
+
and has_astichi_insert_decorator(node.decorator_list)
|
|
33
|
+
)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"""Astichi owner-scope mapping helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
from astichi.asttools.inserts import (
|
|
9
|
+
is_astichi_insert_shell,
|
|
10
|
+
is_expression_insert_call,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(eq=False)
|
|
15
|
+
class AstichiScope:
|
|
16
|
+
"""Astichi owner-scope identity for one AST owner root."""
|
|
17
|
+
|
|
18
|
+
root: ast.AST
|
|
19
|
+
label: str
|
|
20
|
+
parent: "AstichiScope | None" = None
|
|
21
|
+
_node_ids: set[int] = field(default_factory=set, repr=False, compare=False)
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def root_id(self) -> int:
|
|
25
|
+
return id(self.root)
|
|
26
|
+
|
|
27
|
+
def owns(self, node: ast.AST) -> bool:
|
|
28
|
+
return id(node) in self._node_ids
|
|
29
|
+
|
|
30
|
+
def is_root(self, node: ast.AST) -> bool:
|
|
31
|
+
return id(node) == self.root_id
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AstichiScopeMap:
|
|
35
|
+
"""Per-node Astichi owner-scope lookup."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, tree: ast.Module) -> None:
|
|
38
|
+
self.root = AstichiScope(root=tree, label="module body", parent=None)
|
|
39
|
+
self._by_id: dict[int, AstichiScope] = {}
|
|
40
|
+
self._nested_python_root_by_id: dict[int, ast.AST] = {}
|
|
41
|
+
self._walk(tree, self.root, nested_python_root=None)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_tree(cls, tree: ast.Module) -> "AstichiScopeMap":
|
|
45
|
+
return cls(tree)
|
|
46
|
+
|
|
47
|
+
def scope_for(self, node: ast.AST) -> AstichiScope:
|
|
48
|
+
return self._by_id.get(id(node), self.root)
|
|
49
|
+
|
|
50
|
+
def parent_scope_for(self, scope: AstichiScope) -> AstichiScope | None:
|
|
51
|
+
return scope.parent
|
|
52
|
+
|
|
53
|
+
def nested_python_root_for(self, node: ast.AST) -> ast.AST | None:
|
|
54
|
+
return self._nested_python_root_by_id.get(id(node))
|
|
55
|
+
|
|
56
|
+
def _record(
|
|
57
|
+
self,
|
|
58
|
+
node: ast.AST,
|
|
59
|
+
scope: AstichiScope,
|
|
60
|
+
*,
|
|
61
|
+
nested_python_root: ast.AST | None,
|
|
62
|
+
) -> None:
|
|
63
|
+
self._by_id[id(node)] = scope
|
|
64
|
+
scope._node_ids.add(id(node))
|
|
65
|
+
if nested_python_root is not None:
|
|
66
|
+
self._nested_python_root_by_id[id(node)] = nested_python_root
|
|
67
|
+
|
|
68
|
+
def _walk(
|
|
69
|
+
self,
|
|
70
|
+
node: ast.AST,
|
|
71
|
+
scope: AstichiScope,
|
|
72
|
+
*,
|
|
73
|
+
nested_python_root: ast.AST | None,
|
|
74
|
+
) -> None:
|
|
75
|
+
self._record(node, scope, nested_python_root=nested_python_root)
|
|
76
|
+
if isinstance(node, ast.Module):
|
|
77
|
+
for child in node.body:
|
|
78
|
+
self._walk(child, scope, nested_python_root=None)
|
|
79
|
+
return
|
|
80
|
+
if is_expression_insert_call(node):
|
|
81
|
+
# Intentional ownership widening over the historical boundary-only
|
|
82
|
+
# mapper: expression-insert payloads are Astichi scopes too.
|
|
83
|
+
self._walk(node.func, scope, nested_python_root=nested_python_root)
|
|
84
|
+
self._walk(node.args[0], scope, nested_python_root=nested_python_root)
|
|
85
|
+
payload_scope = AstichiScope(
|
|
86
|
+
root=node,
|
|
87
|
+
label="expression insert payload",
|
|
88
|
+
parent=scope,
|
|
89
|
+
)
|
|
90
|
+
self._walk(node.args[1], payload_scope, nested_python_root=None)
|
|
91
|
+
for keyword in node.keywords:
|
|
92
|
+
self._walk(keyword, scope, nested_python_root=nested_python_root)
|
|
93
|
+
return
|
|
94
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
95
|
+
is_shell = is_astichi_insert_shell(node)
|
|
96
|
+
body_scope = scope
|
|
97
|
+
body_nested_root = node if not is_shell else None
|
|
98
|
+
if is_shell:
|
|
99
|
+
body_scope = AstichiScope(
|
|
100
|
+
root=node,
|
|
101
|
+
label=f"shell {node.name!r} body",
|
|
102
|
+
parent=scope,
|
|
103
|
+
)
|
|
104
|
+
for decorator in node.decorator_list:
|
|
105
|
+
self._walk(decorator, scope, nested_python_root=nested_python_root)
|
|
106
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
107
|
+
# Preserve pre-refactor asymmetry: shell args/defaults are
|
|
108
|
+
# body-owned, while decorators/returns remain outer-owned.
|
|
109
|
+
self._walk_arguments(
|
|
110
|
+
node.args,
|
|
111
|
+
body_scope,
|
|
112
|
+
nested_python_root=body_nested_root,
|
|
113
|
+
)
|
|
114
|
+
if node.returns is not None:
|
|
115
|
+
self._walk(
|
|
116
|
+
node.returns,
|
|
117
|
+
scope,
|
|
118
|
+
nested_python_root=nested_python_root,
|
|
119
|
+
)
|
|
120
|
+
if isinstance(node, ast.ClassDef):
|
|
121
|
+
# Preserve pre-refactor behavior: class bases/keywords are
|
|
122
|
+
# evaluated in the outer scope, not the shell body scope.
|
|
123
|
+
for base in node.bases:
|
|
124
|
+
self._walk(base, scope, nested_python_root=nested_python_root)
|
|
125
|
+
for keyword in node.keywords:
|
|
126
|
+
self._walk(keyword, scope, nested_python_root=nested_python_root)
|
|
127
|
+
for child in node.body:
|
|
128
|
+
self._walk(
|
|
129
|
+
child,
|
|
130
|
+
body_scope,
|
|
131
|
+
nested_python_root=body_nested_root,
|
|
132
|
+
)
|
|
133
|
+
return
|
|
134
|
+
for child in ast.iter_child_nodes(node):
|
|
135
|
+
self._walk(child, scope, nested_python_root=nested_python_root)
|
|
136
|
+
|
|
137
|
+
def _walk_arguments(
|
|
138
|
+
self,
|
|
139
|
+
args: ast.arguments,
|
|
140
|
+
scope: AstichiScope,
|
|
141
|
+
*,
|
|
142
|
+
nested_python_root: ast.AST | None,
|
|
143
|
+
) -> None:
|
|
144
|
+
self._record(args, scope, nested_python_root=nested_python_root)
|
|
145
|
+
for argument in (
|
|
146
|
+
list(args.posonlyargs)
|
|
147
|
+
+ list(args.args)
|
|
148
|
+
+ list(args.kwonlyargs)
|
|
149
|
+
):
|
|
150
|
+
self._walk(argument, scope, nested_python_root=nested_python_root)
|
|
151
|
+
if args.vararg is not None:
|
|
152
|
+
self._walk(args.vararg, scope, nested_python_root=nested_python_root)
|
|
153
|
+
if args.kwarg is not None:
|
|
154
|
+
self._walk(args.kwarg, scope, nested_python_root=nested_python_root)
|
|
155
|
+
for default in args.defaults + args.kw_defaults:
|
|
156
|
+
if default is not None:
|
|
157
|
+
self._walk(default, scope, nested_python_root=nested_python_root)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Shape semantics for Astichi marker usage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MarkerShape(ABC):
|
|
9
|
+
"""Behavior-bearing marker usage shape."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, name: str) -> None:
|
|
12
|
+
self.name = name
|
|
13
|
+
|
|
14
|
+
def is_scalar_expr(self) -> bool:
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
def is_positional_variadic(self) -> bool:
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
def is_named_variadic(self) -> bool:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
def is_block(self) -> bool:
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
def is_identifier(self) -> bool:
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
def is_parameter(self) -> bool:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _ScalarExprShape(MarkerShape):
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
super().__init__("scalar_expr")
|
|
36
|
+
|
|
37
|
+
def is_scalar_expr(self) -> bool:
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _PositionalVariadicShape(MarkerShape):
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
super().__init__("positional_variadic")
|
|
44
|
+
|
|
45
|
+
def is_positional_variadic(self) -> bool:
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _NamedVariadicShape(MarkerShape):
|
|
50
|
+
def __init__(self) -> None:
|
|
51
|
+
super().__init__("named_variadic")
|
|
52
|
+
|
|
53
|
+
def is_named_variadic(self) -> bool:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _BlockShape(MarkerShape):
|
|
58
|
+
def __init__(self) -> None:
|
|
59
|
+
super().__init__("block")
|
|
60
|
+
|
|
61
|
+
def is_block(self) -> bool:
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _IdentifierShape(MarkerShape):
|
|
66
|
+
def __init__(self) -> None:
|
|
67
|
+
super().__init__("identifier")
|
|
68
|
+
|
|
69
|
+
def is_identifier(self) -> bool:
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class _ParameterShape(MarkerShape):
|
|
74
|
+
def __init__(self) -> None:
|
|
75
|
+
super().__init__("parameter")
|
|
76
|
+
|
|
77
|
+
def is_parameter(self) -> bool:
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
SCALAR_EXPR = _ScalarExprShape()
|
|
82
|
+
POSITIONAL_VARIADIC = _PositionalVariadicShape()
|
|
83
|
+
NAMED_VARIADIC = _NamedVariadicShape()
|
|
84
|
+
BLOCK = _BlockShape()
|
|
85
|
+
IDENTIFIER = _IdentifierShape()
|
|
86
|
+
PARAMETER = _ParameterShape()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Mutable builder graph and handle surfaces for Astichi."""
|
|
2
|
+
|
|
3
|
+
from astichi.builder.api import build
|
|
4
|
+
from astichi.builder.graph import (
|
|
5
|
+
AdditiveEdge,
|
|
6
|
+
BuilderGraph,
|
|
7
|
+
EdgeSourceOverlay,
|
|
8
|
+
IdentifierBinding,
|
|
9
|
+
InstanceRecord,
|
|
10
|
+
TargetRef,
|
|
11
|
+
)
|
|
12
|
+
from astichi.builder.handles import (
|
|
13
|
+
AddProxy,
|
|
14
|
+
AddToTargetProxy,
|
|
15
|
+
BindIdentifierProxy,
|
|
16
|
+
BuilderHandle,
|
|
17
|
+
InstanceHandle,
|
|
18
|
+
TargetHandle,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"AddProxy",
|
|
23
|
+
"AddToTargetProxy",
|
|
24
|
+
"AdditiveEdge",
|
|
25
|
+
"BindIdentifierProxy",
|
|
26
|
+
"BuilderGraph",
|
|
27
|
+
"BuilderHandle",
|
|
28
|
+
"EdgeSourceOverlay",
|
|
29
|
+
"IdentifierBinding",
|
|
30
|
+
"InstanceHandle",
|
|
31
|
+
"InstanceRecord",
|
|
32
|
+
"TargetHandle",
|
|
33
|
+
"TargetRef",
|
|
34
|
+
"build",
|
|
35
|
+
]
|
astichi/builder/api.py
ADDED