pine2pyne 0.2.4__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.
- pine2pyne/__init__.py +84 -0
- pine2pyne/__main__.py +12 -0
- pine2pyne/ast_nodes.py +405 -0
- pine2pyne/ast_walk.py +93 -0
- pine2pyne/block_value_lowering.py +694 -0
- pine2pyne/bool_builtins.py +42 -0
- pine2pyne/bool_value_typing.py +580 -0
- pine2pyne/cli.py +247 -0
- pine2pyne/codegen.py +886 -0
- pine2pyne/compat/__init__.py +19 -0
- pine2pyne/compat/__main__.py +6 -0
- pine2pyne/compat/cli.py +114 -0
- pine2pyne/compat/comparison.py +105 -0
- pine2pyne/compat/corpus.py +73 -0
- pine2pyne/compat/report.py +95 -0
- pine2pyne/compat/runner.py +63 -0
- pine2pyne/condition_typing.py +260 -0
- pine2pyne/conditional_history_calls.py +96 -0
- pine2pyne/core/__init__.py +26 -0
- pine2pyne/core/contracts.py +125 -0
- pine2pyne/di.py +173 -0
- pine2pyne/emit/__init__.py +5 -0
- pine2pyne/emit/legacy.py +14 -0
- pine2pyne/emit/pynecomp/__init__.py +20 -0
- pine2pyne/emit/pynecomp/banner.py +30 -0
- pine2pyne/emit/pynecomp/blank_lines.py +69 -0
- pine2pyne/emit/pynecomp/emitter.py +61 -0
- pine2pyne/emit/pynecomp/imports.py +65 -0
- pine2pyne/emit/pynecomp/passes/__init__.py +84 -0
- pine2pyne/emit/pynecomp/passes/annotations.py +422 -0
- pine2pyne/emit/pynecomp/passes/base.py +69 -0
- pine2pyne/emit/pynecomp/passes/builtins.py +169 -0
- pine2pyne/emit/pynecomp/passes/hoisting.py +205 -0
- pine2pyne/emit/pynecomp/passes/imports.py +112 -0
- pine2pyne/emit/pynecomp/passes/legacy_colors.py +101 -0
- pine2pyne/emit/pynecomp/passes/library.py +205 -0
- pine2pyne/emit/pynecomp/passes/rewrites.py +215 -0
- pine2pyne/emit/pynecomp/printer.py +288 -0
- pine2pyne/emit/pynecomp/unparser.py +186 -0
- pine2pyne/emitted_names.py +152 -0
- pine2pyne/errors.py +69 -0
- pine2pyne/for_loop_end_bound.py +172 -0
- pine2pyne/grouped_history_lowering.py +310 -0
- pine2pyne/import_resolver.py +469 -0
- pine2pyne/input_hoisting.py +195 -0
- pine2pyne/legacy_function_names.py +74 -0
- pine2pyne/legacy_sessions.py +68 -0
- pine2pyne/lexer.py +630 -0
- pine2pyne/library_collision.py +434 -0
- pine2pyne/namespace_members.py +247 -0
- pine2pyne/node_ids.py +235 -0
- pine2pyne/once_lowering.py +75 -0
- pine2pyne/overload_resolution.py +475 -0
- pine2pyne/parse_repair.py +398 -0
- pine2pyne/parser.py +1873 -0
- pine2pyne/pine_builtins.py +394 -0
- pine2pyne/pine_semantics.py +604 -0
- pine2pyne/pipeline/__init__.py +18 -0
- pine2pyne/pipeline/service.py +70 -0
- pine2pyne/pipeline/stages.py +73 -0
- pine2pyne/scope_renamer.py +965 -0
- pine2pyne/strategy_when_lowering.py +101 -0
- pine2pyne/strict_boolean.py +101 -0
- pine2pyne/symbol_table.py +166 -0
- pine2pyne/tokens.py +143 -0
- pine2pyne/transformer.py +2510 -0
- pine2pyne/type_inference.py +422 -0
- pine2pyne/udt_field_defaults.py +149 -0
- pine2pyne/value_blocks.py +357 -0
- pine2pyne-0.2.4.dist-info/METADATA +99 -0
- pine2pyne-0.2.4.dist-info/RECORD +75 -0
- pine2pyne-0.2.4.dist-info/WHEEL +5 -0
- pine2pyne-0.2.4.dist-info/entry_points.txt +3 -0
- pine2pyne-0.2.4.dist-info/licenses/LICENSE +201 -0
- pine2pyne-0.2.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Library collisions: a user binding named like a pynecore.lib name (card #9).
|
|
3
|
+
|
|
4
|
+
Pine keeps a variable, a parameter or a function apart from a built-in
|
|
5
|
+
namespace or function of the same name: `color.new(color, 50)` inside
|
|
6
|
+
`f(color = color.red)` calls the namespace on the parameter. Generated Python
|
|
7
|
+
has one namespace for both, and pynecore's import normalizer turns every read
|
|
8
|
+
of an imported name into the library object. The scope renamer therefore gives
|
|
9
|
+
such a binding a fresh name; this module decides which names collide and, for
|
|
10
|
+
each `N.m`, whether it reads the binding or the namespace N.
|
|
11
|
+
"""
|
|
12
|
+
from typing import Callable, Dict, FrozenSet, List, Mapping, NamedTuple, Optional, Set
|
|
13
|
+
|
|
14
|
+
from .ast_nodes import (
|
|
15
|
+
Assignment, Expression, ExpressionStatement, ForInLoop, ForLoop, FuncDecl, FunctionCall, Identifier,
|
|
16
|
+
IfExpression, InputDecl, Literal, MemberAccess, NaLiteral, Parameter, Script, SwitchExpression,
|
|
17
|
+
TernaryOp, TupleDestructure, TypeDecl, VarDecl, VaripDecl,
|
|
18
|
+
)
|
|
19
|
+
from .ast_walk import walk
|
|
20
|
+
from .namespace_members import NAMESPACE_MEMBERS
|
|
21
|
+
from .pine_builtins import PYNECORE_LIB_MODULES
|
|
22
|
+
from .tokens import TYPE_KEYWORDS
|
|
23
|
+
|
|
24
|
+
NO_MEMBERS: FrozenSet[str] = frozenset()
|
|
25
|
+
# A constructor is called on the namespace, never as a method of a value:
|
|
26
|
+
# `line.new(...)` next to an array named `line`, `array.new_float(...)`, `array.from(...)`
|
|
27
|
+
CONSTRUCTORS = frozenset({'new', 'from'})
|
|
28
|
+
TYPED_CONSTRUCTOR_PREFIX = 'new_'
|
|
29
|
+
# `x.copy()` makes a value of x's type, and is a method of every object value
|
|
30
|
+
COPY_MEMBER = 'copy'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def colliding_names(script: Script) -> FrozenSet[str]:
|
|
34
|
+
"""Names pynecore defines that the script binds itself (a variable, parameter, loop
|
|
35
|
+
variable, input or function). PyneComp renames every one of them, whether or not
|
|
36
|
+
the script reads the builtin (`pine_builtins.PYNECOMP_RESERVED_NAMES`)."""
|
|
37
|
+
from .pine_builtins import PYNECOMP_RESERVED_NAMES
|
|
38
|
+
bound: Set[str] = set()
|
|
39
|
+
for node in walk(script):
|
|
40
|
+
bound.update(_names_bound_by(node))
|
|
41
|
+
return frozenset(bound & PYNECOMP_RESERVED_NAMES)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _names_bound_by(node):
|
|
45
|
+
if isinstance(node, (VarDecl, VaripDecl, InputDecl, Parameter)):
|
|
46
|
+
return (node.name,)
|
|
47
|
+
if isinstance(node, Assignment):
|
|
48
|
+
return node.target.names if isinstance(node.target, TupleDestructure) else (node.target,)
|
|
49
|
+
if isinstance(node, FuncDecl) and not node.is_method:
|
|
50
|
+
return (node.name,)
|
|
51
|
+
if isinstance(node, ForLoop):
|
|
52
|
+
return (node.var,)
|
|
53
|
+
if isinstance(node, ForInLoop):
|
|
54
|
+
return node.vars
|
|
55
|
+
return ()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _dotted_head_or_callee(node) -> Optional[str]:
|
|
59
|
+
if isinstance(node, FunctionCall) and isinstance(node.func, str):
|
|
60
|
+
return node.func.split('.')[0]
|
|
61
|
+
if isinstance(node, Identifier) and '.' in node.name:
|
|
62
|
+
return node.name.split('.')[0]
|
|
63
|
+
if isinstance(node, MemberAccess) and isinstance(node.object, str):
|
|
64
|
+
return node.object.split('.')[0]
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TypeEvidence(NamedTuple):
|
|
69
|
+
"""What proves a value's type beyond its own declaration (card #91): the script's user type
|
|
70
|
+
names, the user type a user function provably returns, the type a variable holds where the
|
|
71
|
+
value is read (a container keeps its element type: `array<T>`), and the user method names (a
|
|
72
|
+
call by one may select the method, so it proves no element). Empty: the declaration alone."""
|
|
73
|
+
type_names: FrozenSet[str] = NO_MEMBERS
|
|
74
|
+
function_type: Callable[[str], Optional[str]] = lambda name: None
|
|
75
|
+
variable_type: Callable[[str], Optional[str]] = lambda name: None
|
|
76
|
+
method_names: FrozenSet[str] = NO_MEMBERS
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
NO_EVIDENCE = TypeEvidence()
|
|
80
|
+
# What `array.get(a, 0)`, `a.first()`, `matrix.get(m, 0, 0)` or `m.get(k)` returns: an element of the
|
|
81
|
+
# container (a map's value)
|
|
82
|
+
ELEMENT_ACCESSORS = {'array': frozenset({'get', 'first', 'last', 'pop', 'shift'}),
|
|
83
|
+
'matrix': frozenset({'get'}), 'map': frozenset({'get'})}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def base_type(type_name: Optional[str]) -> Optional[str]:
|
|
87
|
+
"""A type without its qualifier and element types: `array<T>` -> `array`, `series float` -> `float`."""
|
|
88
|
+
return type_name.split('<')[0].split()[-1] if type_name else None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def element_type(type_name: Optional[str]) -> Optional[str]:
|
|
92
|
+
"""The type of a container's elements, a map's values: `array<T>`, `map<string, T>` -> `T`."""
|
|
93
|
+
if not type_name or '<' not in type_name or not type_name.endswith('>'):
|
|
94
|
+
return None
|
|
95
|
+
arguments, depth, start = type_name[type_name.index('<') + 1:-1], 0, 0
|
|
96
|
+
for index, character in enumerate(arguments):
|
|
97
|
+
depth += {'<': 1, '>': -1}.get(character, 0)
|
|
98
|
+
if character == ',' and depth == 0:
|
|
99
|
+
start = index + 1
|
|
100
|
+
return arguments[start:].strip() or None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def binding_type(type_hint: Optional[str], value, evidence: TypeEvidence = NO_EVIDENCE) -> Optional[str]:
|
|
104
|
+
"""The Pine type a declaration gives its binding, as far as it is proven: its type annotation,
|
|
105
|
+
a constructor call (`P.new()`, `array.new<P>()`, `array.from(...)`), a literal, a call of a user
|
|
106
|
+
function that provably returns a user-defined type (`evidence.function_type`, from
|
|
107
|
+
`user_function_types`), or a value whose user type `evidence` proves (`_proven_user_type`).
|
|
108
|
+
A container keeps its element type (`array<P>`; `base_type` gives `array`). None when nothing
|
|
109
|
+
proves it: never a guess."""
|
|
110
|
+
if isinstance(value, FunctionCall) and isinstance(value.func, str) and '.' not in value.func:
|
|
111
|
+
return _without_qualifier(type_hint) if type_hint else evidence.function_type(value.func)
|
|
112
|
+
if type_hint:
|
|
113
|
+
return _without_qualifier(type_hint) # `series float`, `map<string, float>`
|
|
114
|
+
if isinstance(value, Literal):
|
|
115
|
+
return value.literal_type or None
|
|
116
|
+
constructed = _constructed_type(value, evidence)
|
|
117
|
+
return constructed if constructed is not None else _proven_user_type(value, evidence)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _without_qualifier(type_hint: str) -> str:
|
|
121
|
+
prefix, bracket, elements = type_hint.partition('<')
|
|
122
|
+
return prefix.split()[-1] + bracket + elements
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _constructed_type(value, evidence: TypeEvidence) -> Optional[str]:
|
|
126
|
+
"""`T.new()` -> T, `array.new<T>()` -> array<T>, `array.from(T.new())` -> array<T> when every
|
|
127
|
+
element is that user type; `p.copy()` copies a variable, its head names no type."""
|
|
128
|
+
if not (isinstance(value, FunctionCall) and isinstance(value.func, str) and '.' in value.func):
|
|
129
|
+
return None
|
|
130
|
+
head, _, member = value.func.rpartition('.')
|
|
131
|
+
makes_a_value = is_constructor(member) or member == COPY_MEMBER
|
|
132
|
+
if not makes_a_value or not (head in TYPE_KEYWORDS or head in PYNECORE_LIB_MODULES or head[:1].isupper()):
|
|
133
|
+
return None
|
|
134
|
+
_, bracket, elements = member.partition('<')
|
|
135
|
+
if bracket:
|
|
136
|
+
return head + bracket + elements
|
|
137
|
+
if head == 'array' and member == 'from' and value.args:
|
|
138
|
+
element_types = {_user_type(element, evidence) for element in value.args}
|
|
139
|
+
if len(element_types) == 1 and None not in element_types:
|
|
140
|
+
return f'array<{element_types.pop()}>'
|
|
141
|
+
return head
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _proven_user_type(value, evidence: TypeEvidence) -> Optional[str]:
|
|
145
|
+
"""The user type `evidence` proves a value has (card #91): a variable that holds one, an element
|
|
146
|
+
of a container of one, or a ternary, switch or if-expression whose every branch that is not
|
|
147
|
+
`na` is that one type. Only a user type: a builtin type stays unknown here, as before."""
|
|
148
|
+
if isinstance(value, Identifier) and '.' not in value.name:
|
|
149
|
+
return _as_user_type(evidence.variable_type(value.name), evidence)
|
|
150
|
+
if isinstance(value, (TernaryOp, IfExpression, SwitchExpression)):
|
|
151
|
+
branch_types = {_branch_type(branch, evidence) for branch in _branches(value)} - {NA_BRANCH}
|
|
152
|
+
return branch_types.pop() if len(branch_types) == 1 else None
|
|
153
|
+
if isinstance(value, FunctionCall):
|
|
154
|
+
return _as_user_type(element_type(_accessed_container(value, evidence)), evidence)
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
NA_BRANCH = 'na'
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _branches(value) -> list:
|
|
162
|
+
if isinstance(value, TernaryOp):
|
|
163
|
+
return [value.true_expr, value.false_expr]
|
|
164
|
+
if isinstance(value, IfExpression):
|
|
165
|
+
return [value.true_expr, value.false_expr] # no `else`: the value is na
|
|
166
|
+
return [body for _, body in value.cases] + [value.default]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _branch_type(branch, evidence: TypeEvidence) -> Optional[str]:
|
|
170
|
+
"""The user type of one branch, NA_BRANCH for `na`. A block's value is its last expression,
|
|
171
|
+
read with the block's own declarations: a block local hides the outer name it shares."""
|
|
172
|
+
if isinstance(branch, list):
|
|
173
|
+
branch, evidence = _block_value(branch, evidence)
|
|
174
|
+
if branch is None or isinstance(branch, NaLiteral):
|
|
175
|
+
return NA_BRANCH
|
|
176
|
+
return _user_type(branch, evidence)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _block_value(block: list, evidence: TypeEvidence):
|
|
180
|
+
"""A block's last expression (False when the block ends otherwise) and the evidence it is read
|
|
181
|
+
with: each declaration at the block's top typed in order; a name bound anywhere else in the
|
|
182
|
+
block (a nested block, a loop, a tuple) proves nothing."""
|
|
183
|
+
last = block[-1] if block else None
|
|
184
|
+
last = last.expr if isinstance(last, ExpressionStatement) else last
|
|
185
|
+
if not isinstance(last, Expression):
|
|
186
|
+
return False, evidence
|
|
187
|
+
local_types: Dict[str, Optional[str]] = {}
|
|
188
|
+
outer = evidence.variable_type
|
|
189
|
+
for statement in block[:-1]:
|
|
190
|
+
evidence_here = evidence._replace(
|
|
191
|
+
variable_type=lambda name, known=dict(local_types): known[name] if name in known else outer(name))
|
|
192
|
+
if isinstance(statement, (VarDecl, VaripDecl)) or (
|
|
193
|
+
isinstance(statement, Assignment) and not isinstance(statement.target, TupleDestructure)):
|
|
194
|
+
local_types.setdefault(statement.name if isinstance(statement, (VarDecl, VaripDecl))
|
|
195
|
+
else statement.target,
|
|
196
|
+
binding_type(statement.type_hint, statement.value, evidence_here))
|
|
197
|
+
for node in walk(block):
|
|
198
|
+
for name in _names_bound_by(node):
|
|
199
|
+
local_types.setdefault(name, None)
|
|
200
|
+
return last, evidence._replace(
|
|
201
|
+
variable_type=lambda name: local_types[name] if name in local_types else outer(name))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _user_type(value, evidence: TypeEvidence) -> Optional[str]:
|
|
205
|
+
return _as_user_type(binding_type(None, value, evidence), evidence)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _as_user_type(type_name: Optional[str], evidence: TypeEvidence) -> Optional[str]:
|
|
209
|
+
return type_name if type_name in evidence.type_names else None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _accessed_container(call: FunctionCall, evidence: TypeEvidence) -> Optional[str]:
|
|
213
|
+
"""The type of the container whose element `call` returns: `array.get(a, 0)` reads its first
|
|
214
|
+
argument, `a.first()` the variable `a`; None for any other call, or a method form whose
|
|
215
|
+
member a user method shares."""
|
|
216
|
+
if not isinstance(call.func, str) or '.' not in call.func:
|
|
217
|
+
return None
|
|
218
|
+
head, _, member = call.func.rpartition('.')
|
|
219
|
+
if head in ELEMENT_ACCESSORS:
|
|
220
|
+
kind = head
|
|
221
|
+
container = _container_type(call.args[0], evidence) if call.args else None
|
|
222
|
+
elif '.' not in head and member not in evidence.method_names:
|
|
223
|
+
container = evidence.variable_type(head)
|
|
224
|
+
kind = base_type(container)
|
|
225
|
+
else:
|
|
226
|
+
return None
|
|
227
|
+
return container if base_type(container) == kind and member in ELEMENT_ACCESSORS.get(kind, ()) else None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _container_type(value, evidence: TypeEvidence) -> Optional[str]:
|
|
231
|
+
if isinstance(value, Identifier) and '.' not in value.name:
|
|
232
|
+
return evidence.variable_type(value.name)
|
|
233
|
+
return binding_type(None, value, evidence)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def script_type_evidence(script: Script) -> TypeEvidence:
|
|
237
|
+
"""The evidence a whole script gives (no variables): its user types, user function returns
|
|
238
|
+
and user method names. A caller adds `variable_type` from its own scope model."""
|
|
239
|
+
return TypeEvidence(
|
|
240
|
+
frozenset(node.name for node in script.declarations if isinstance(node, TypeDecl)),
|
|
241
|
+
user_function_types(script).get, NO_MEMBERS,
|
|
242
|
+
frozenset(node.name for node in script.declarations if isinstance(node, FuncDecl) and node.is_method))
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def user_function_types(script: Script) -> Dict[str, str]:
|
|
246
|
+
"""User function name -> the user-defined type every declaration of it provably
|
|
247
|
+
returns (card #87): its last expression is a constructor or a copy of that type
|
|
248
|
+
(`mk() => T.new(close)`), a parameter or local declared with it, a call of such
|
|
249
|
+
a function, or a value whose type those prove (card #91: a ternary of that type, an
|
|
250
|
+
element of a container of it). A name a method shares is left out: a call by it may
|
|
251
|
+
select the method."""
|
|
252
|
+
type_names = frozenset(node.name for node in script.declarations if isinstance(node, TypeDecl))
|
|
253
|
+
functions: Dict[str, List[FuncDecl]] = {}
|
|
254
|
+
method_names = set()
|
|
255
|
+
for node in script.declarations:
|
|
256
|
+
if isinstance(node, FuncDecl):
|
|
257
|
+
if node.is_method:
|
|
258
|
+
method_names.add(node.name)
|
|
259
|
+
else:
|
|
260
|
+
functions.setdefault(node.name, []).append(node)
|
|
261
|
+
resolved: Dict[str, Optional[str]] = {}
|
|
262
|
+
|
|
263
|
+
def returned(name: str, visiting: FrozenSet[str]) -> Optional[str]:
|
|
264
|
+
if name in resolved:
|
|
265
|
+
return resolved[name]
|
|
266
|
+
if name not in functions or name in method_names or name in visiting:
|
|
267
|
+
return None
|
|
268
|
+
evidence = TypeEvidence(type_names, lambda callee: returned(callee, visiting | {name}),
|
|
269
|
+
method_names=frozenset(method_names))
|
|
270
|
+
returns = {_returned_type(declaration, evidence) for declaration in functions[name]}
|
|
271
|
+
result = returns.pop() if len(returns) == 1 else None
|
|
272
|
+
resolved[name] = result if result in type_names else None
|
|
273
|
+
return resolved[name]
|
|
274
|
+
|
|
275
|
+
return {name: type_name for name in functions
|
|
276
|
+
if (type_name := returned(name, frozenset())) is not None}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _returned_type(function: FuncDecl, evidence: TypeEvidence) -> Optional[str]:
|
|
280
|
+
"""The type of a function's last expression, as its declarations prove it: a name reads
|
|
281
|
+
the function's own parameters and locals only."""
|
|
282
|
+
local_types: Dict[str, Optional[str]] = {
|
|
283
|
+
parameter.name: binding_type(parameter.type_hint, None) for parameter in function.params}
|
|
284
|
+
body = function.body if isinstance(function.body, list) else [function.body]
|
|
285
|
+
for statement in body[:-1]:
|
|
286
|
+
if isinstance(statement, (VarDecl, VaripDecl)) or (
|
|
287
|
+
isinstance(statement, Assignment) and not isinstance(statement.target, TupleDestructure)):
|
|
288
|
+
name = statement.name if isinstance(statement, (VarDecl, VaripDecl)) else statement.target
|
|
289
|
+
local_types.setdefault(name, binding_type(statement.type_hint, statement.value,
|
|
290
|
+
evidence._replace(variable_type=dict(local_types).get)))
|
|
291
|
+
last = body[-1] if body else None
|
|
292
|
+
last = last.expr if isinstance(last, ExpressionStatement) else last
|
|
293
|
+
if isinstance(last, Identifier) and '.' not in last.name:
|
|
294
|
+
return local_types.get(last.name)
|
|
295
|
+
return binding_type(None, last, evidence._replace(variable_type=local_types.get))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def binding_types(script: Script) -> Dict[str, Optional[str]]:
|
|
299
|
+
"""Every name the script binds, with the Pine type its declarations give it
|
|
300
|
+
(`declared_type`); None when no declaration says or two disagree."""
|
|
301
|
+
evidence = script_type_evidence(script)
|
|
302
|
+
types: Dict[str, Optional[str]] = {}
|
|
303
|
+
for node in walk(script):
|
|
304
|
+
for name in _names_bound_by(node):
|
|
305
|
+
declared = declared_type(node, evidence._replace(variable_type=types.get))
|
|
306
|
+
types[name] = declared if types.get(name, declared) == declared else None
|
|
307
|
+
return types
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def global_declarations(script: Script) -> list:
|
|
311
|
+
"""The declarations of globals (`var`/`varip`, inputs, top-level assignments) in source order,
|
|
312
|
+
the order the scope renamer binds and types them up front (card #91 review): a value reads the
|
|
313
|
+
globals declared above it, whichever kind each is. The parser keeps `var` declarations and
|
|
314
|
+
inputs apart from the body, so the two lists are merged by line."""
|
|
315
|
+
nodes = [node for node in list(script.declarations) + list(script.body)
|
|
316
|
+
if isinstance(node, (VarDecl, VaripDecl, InputDecl, Assignment))]
|
|
317
|
+
return sorted(nodes, key=lambda node: node.line)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def global_binding_types(script: Script) -> Dict[str, Optional[str]]:
|
|
321
|
+
"""Every global name with the type of its first declaration, in the order the scope
|
|
322
|
+
renamer binds globals up front (`global_declarations`); a value reads the globals typed
|
|
323
|
+
before it."""
|
|
324
|
+
evidence = script_type_evidence(script)
|
|
325
|
+
types: Dict[str, Optional[str]] = {}
|
|
326
|
+
for node in global_declarations(script):
|
|
327
|
+
for name in _names_bound_by(node):
|
|
328
|
+
types.setdefault(name, declared_type(node, evidence._replace(variable_type=types.get)))
|
|
329
|
+
return types
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def declared_type(node, evidence: TypeEvidence = NO_EVIDENCE) -> Optional[str]:
|
|
333
|
+
"""The Pine type a declaring node gives the names it binds: a variable, an input, a
|
|
334
|
+
parameter (`binding_type`); None for a tuple, a loop variable or a function. An
|
|
335
|
+
untyped parameter's type comes from each call's argument, so it is None here."""
|
|
336
|
+
if isinstance(node, (VarDecl, VaripDecl)):
|
|
337
|
+
return binding_type(node.type_hint, node.value, evidence)
|
|
338
|
+
if isinstance(node, Assignment) and not isinstance(node.target, TupleDestructure):
|
|
339
|
+
return binding_type(node.type_hint, node.value, evidence)
|
|
340
|
+
if isinstance(node, Parameter):
|
|
341
|
+
return binding_type(node.type_hint, node.default)
|
|
342
|
+
if isinstance(node, InputDecl):
|
|
343
|
+
return input_type(node)
|
|
344
|
+
return None
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def is_constructor(member: str) -> bool:
|
|
348
|
+
"""`new`, `new<float>`, `new_float`, `from`: a namespace function that
|
|
349
|
+
makes a value (a field such as `newest` is not one)."""
|
|
350
|
+
name = member.split('<')[0]
|
|
351
|
+
return name in CONSTRUCTORS or name.startswith(TYPED_CONSTRUCTOR_PREFIX)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def input_type(declaration: InputDecl) -> str:
|
|
355
|
+
"""An input's value type is named by its function: `input.int` -> int."""
|
|
356
|
+
return declaration.func.rpartition('.')[2]
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class MemberRule:
|
|
360
|
+
"""Decides, for `N.m` where N names a binding, whether `m` is a member of
|
|
361
|
+
the binding (a field or method of its type) or of the namespace N."""
|
|
362
|
+
|
|
363
|
+
def __init__(self, script: Script):
|
|
364
|
+
self.type_fields: Dict[str, FrozenSet[str]] = {
|
|
365
|
+
node.name: frozenset(field[0] for field in node.fields)
|
|
366
|
+
for node in script.declarations if isinstance(node, TypeDecl)}
|
|
367
|
+
# user method name -> the base types of its receiver (None: untyped)
|
|
368
|
+
self.method_receivers: Dict[str, Set[Optional[str]]] = {}
|
|
369
|
+
for node in script.declarations:
|
|
370
|
+
if isinstance(node, FuncDecl) and node.is_method and node.params:
|
|
371
|
+
receiver = base_type(binding_type(node.params[0].type_hint, None))
|
|
372
|
+
self.method_receivers.setdefault(node.name, set()).add(receiver)
|
|
373
|
+
|
|
374
|
+
def binding_owns(self, type_name: Optional[str], namespace: str, member: str) -> bool:
|
|
375
|
+
"""True when `namespace.member` reads the binding named `namespace`,
|
|
376
|
+
whose type is `type_name` (None when unknown). `member` is Pine's
|
|
377
|
+
spelling (`from`, `new<float>`), so a constructor is told by its name. `type_name` may
|
|
378
|
+
keep its element type (`array<T>`): the decision reads its base."""
|
|
379
|
+
type_name = base_type(type_name)
|
|
380
|
+
receivers = self.method_receivers.get(member, ())
|
|
381
|
+
if receivers and (type_name is None or None in receivers or type_name in receivers):
|
|
382
|
+
return True
|
|
383
|
+
if is_constructor(member):
|
|
384
|
+
return False
|
|
385
|
+
if type_name is None:
|
|
386
|
+
# Unknown type: the binding's own member is what the namespace lacks. A member the
|
|
387
|
+
# namespace has stays the namespace's, never a guess (card #87 review): a binding
|
|
388
|
+
# holding a user type is known by its declaration (`binding_type`)
|
|
389
|
+
return member not in NAMESPACE_MEMBERS.get(namespace, NO_MEMBERS)
|
|
390
|
+
if type_name in self.type_fields:
|
|
391
|
+
return member in self.type_fields[type_name] or member == COPY_MEMBER
|
|
392
|
+
if type_name == namespace or (namespace, type_name) == ('str', 'string') or type_name not in TYPE_KEYWORDS:
|
|
393
|
+
# `matrix.set(matrix, ...)` on a matrix named matrix is the namespace call; so is `str.tostring(x)`
|
|
394
|
+
# where a string is named `str` (facejungle/utils)
|
|
395
|
+
return False
|
|
396
|
+
# A built-in type's methods are its namespace's functions: `a.size()` is array.size(a)
|
|
397
|
+
return member in NAMESPACE_MEMBERS.get(type_name, NO_MEMBERS)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class HeadBinding(NamedTuple):
|
|
401
|
+
"""The binding a dotted head names where it is read: its Pine type, None when unknown."""
|
|
402
|
+
type_name: Optional[str]
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class FieldReadRule:
|
|
406
|
+
"""Decides whether the object of a history index reads a field of a user-defined
|
|
407
|
+
type (card #85): a member of an expression (`f().x`, `(t[1]).x`), or a dotted name
|
|
408
|
+
`N.m` whose head N is a binding that owns `m`. A head named like a namespace reads the
|
|
409
|
+
namespace unless MemberRule says `m` is the binding's own member: `session.ismarket`
|
|
410
|
+
beside an input named `session` reads the session namespace. The Pine v6 refusal
|
|
411
|
+
(pine_semantics.py) and the v5 lowering (grouped_history_lowering.py) both ask it.
|
|
412
|
+
|
|
413
|
+
The head's binding is the one in scope where the index is read, with the type its
|
|
414
|
+
declaration gives it (`declared_type`), as the scope renamer resolves `N.m` (card #88);
|
|
415
|
+
each caller says which binding that is, from its own scope model."""
|
|
416
|
+
|
|
417
|
+
def __init__(self, script: Script):
|
|
418
|
+
self.member_rule = MemberRule(script)
|
|
419
|
+
|
|
420
|
+
def is_field_read(self, value, binding_in_scope: Callable[[str], Optional[HeadBinding]]) -> bool:
|
|
421
|
+
"""`binding_in_scope(head)`: the binding the head names where `value` is read, None
|
|
422
|
+
when it names no binding there."""
|
|
423
|
+
if isinstance(value, MemberAccess) and not isinstance(value.object, str):
|
|
424
|
+
return True
|
|
425
|
+
if isinstance(value, MemberAccess):
|
|
426
|
+
dotted = f'{value.object}.{value.member}'
|
|
427
|
+
else:
|
|
428
|
+
dotted = value.name if isinstance(value, Identifier) else ''
|
|
429
|
+
head, dot, path = dotted.partition('.')
|
|
430
|
+
binding = binding_in_scope(head) if dot else None
|
|
431
|
+
if binding is None:
|
|
432
|
+
return False
|
|
433
|
+
return head not in PYNECORE_LIB_MODULES or self.member_rule.binding_owns(
|
|
434
|
+
binding.type_name, head, path.partition('.')[0])
|