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.
Files changed (75) hide show
  1. pine2pyne/__init__.py +84 -0
  2. pine2pyne/__main__.py +12 -0
  3. pine2pyne/ast_nodes.py +405 -0
  4. pine2pyne/ast_walk.py +93 -0
  5. pine2pyne/block_value_lowering.py +694 -0
  6. pine2pyne/bool_builtins.py +42 -0
  7. pine2pyne/bool_value_typing.py +580 -0
  8. pine2pyne/cli.py +247 -0
  9. pine2pyne/codegen.py +886 -0
  10. pine2pyne/compat/__init__.py +19 -0
  11. pine2pyne/compat/__main__.py +6 -0
  12. pine2pyne/compat/cli.py +114 -0
  13. pine2pyne/compat/comparison.py +105 -0
  14. pine2pyne/compat/corpus.py +73 -0
  15. pine2pyne/compat/report.py +95 -0
  16. pine2pyne/compat/runner.py +63 -0
  17. pine2pyne/condition_typing.py +260 -0
  18. pine2pyne/conditional_history_calls.py +96 -0
  19. pine2pyne/core/__init__.py +26 -0
  20. pine2pyne/core/contracts.py +125 -0
  21. pine2pyne/di.py +173 -0
  22. pine2pyne/emit/__init__.py +5 -0
  23. pine2pyne/emit/legacy.py +14 -0
  24. pine2pyne/emit/pynecomp/__init__.py +20 -0
  25. pine2pyne/emit/pynecomp/banner.py +30 -0
  26. pine2pyne/emit/pynecomp/blank_lines.py +69 -0
  27. pine2pyne/emit/pynecomp/emitter.py +61 -0
  28. pine2pyne/emit/pynecomp/imports.py +65 -0
  29. pine2pyne/emit/pynecomp/passes/__init__.py +84 -0
  30. pine2pyne/emit/pynecomp/passes/annotations.py +422 -0
  31. pine2pyne/emit/pynecomp/passes/base.py +69 -0
  32. pine2pyne/emit/pynecomp/passes/builtins.py +169 -0
  33. pine2pyne/emit/pynecomp/passes/hoisting.py +205 -0
  34. pine2pyne/emit/pynecomp/passes/imports.py +112 -0
  35. pine2pyne/emit/pynecomp/passes/legacy_colors.py +101 -0
  36. pine2pyne/emit/pynecomp/passes/library.py +205 -0
  37. pine2pyne/emit/pynecomp/passes/rewrites.py +215 -0
  38. pine2pyne/emit/pynecomp/printer.py +288 -0
  39. pine2pyne/emit/pynecomp/unparser.py +186 -0
  40. pine2pyne/emitted_names.py +152 -0
  41. pine2pyne/errors.py +69 -0
  42. pine2pyne/for_loop_end_bound.py +172 -0
  43. pine2pyne/grouped_history_lowering.py +310 -0
  44. pine2pyne/import_resolver.py +469 -0
  45. pine2pyne/input_hoisting.py +195 -0
  46. pine2pyne/legacy_function_names.py +74 -0
  47. pine2pyne/legacy_sessions.py +68 -0
  48. pine2pyne/lexer.py +630 -0
  49. pine2pyne/library_collision.py +434 -0
  50. pine2pyne/namespace_members.py +247 -0
  51. pine2pyne/node_ids.py +235 -0
  52. pine2pyne/once_lowering.py +75 -0
  53. pine2pyne/overload_resolution.py +475 -0
  54. pine2pyne/parse_repair.py +398 -0
  55. pine2pyne/parser.py +1873 -0
  56. pine2pyne/pine_builtins.py +394 -0
  57. pine2pyne/pine_semantics.py +604 -0
  58. pine2pyne/pipeline/__init__.py +18 -0
  59. pine2pyne/pipeline/service.py +70 -0
  60. pine2pyne/pipeline/stages.py +73 -0
  61. pine2pyne/scope_renamer.py +965 -0
  62. pine2pyne/strategy_when_lowering.py +101 -0
  63. pine2pyne/strict_boolean.py +101 -0
  64. pine2pyne/symbol_table.py +166 -0
  65. pine2pyne/tokens.py +143 -0
  66. pine2pyne/transformer.py +2510 -0
  67. pine2pyne/type_inference.py +422 -0
  68. pine2pyne/udt_field_defaults.py +149 -0
  69. pine2pyne/value_blocks.py +357 -0
  70. pine2pyne-0.2.4.dist-info/METADATA +99 -0
  71. pine2pyne-0.2.4.dist-info/RECORD +75 -0
  72. pine2pyne-0.2.4.dist-info/WHEEL +5 -0
  73. pine2pyne-0.2.4.dist-info/entry_points.txt +3 -0
  74. pine2pyne-0.2.4.dist-info/licenses/LICENSE +201 -0
  75. pine2pyne-0.2.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,5 @@
1
+ """Code emitters, one per output profile (see :class:`pine2pyne.core.OutputProfile`)."""
2
+ from .legacy import LegacyEmitter
3
+ from .pynecomp import PyneCompEmitter, PyneCompModulePrinter
4
+
5
+ __all__ = ['LegacyEmitter', 'PyneCompEmitter', 'PyneCompModulePrinter']
@@ -0,0 +1,14 @@
1
+ """pine2pyne's own output layout (the historical default)."""
2
+ from __future__ import annotations
3
+
4
+ from ..codegen import CodeGenerator
5
+ from ..core.contracts import CompilationUnit, OutputProfile
6
+
7
+
8
+ class LegacyEmitter:
9
+ """Prints the transformed tree with :class:`~pine2pyne.codegen.CodeGenerator` as is."""
10
+
11
+ profile = OutputProfile.LEGACY
12
+
13
+ def emit(self, unit: CompilationUnit) -> str:
14
+ return CodeGenerator().generate(unit.transformed)
@@ -0,0 +1,20 @@
1
+ """Output profile reproducing the layout of the closed PyneComp v6.0.79 compiler."""
2
+ from .banner import PyneCompBanner
3
+ from .blank_lines import AfterDefinitionPolicy, BlankLinePolicy, SourceLayoutPolicy
4
+ from .emitter import PyneCompEmitter
5
+ from .imports import ImportBlockFormatter
6
+ from .printer import LayoutSource, PyneCompModulePrinter
7
+ from .unparser import ExpressionPrinter, SourceQuotingUnparser, source_quoted_strings, string_literal
8
+
9
+ __all__ = [
10
+ 'AfterDefinitionPolicy',
11
+ 'BlankLinePolicy',
12
+ 'ExpressionPrinter',
13
+ 'ImportBlockFormatter',
14
+ 'LayoutSource',
15
+ 'PyneCompBanner',
16
+ 'PyneCompEmitter',
17
+ 'PyneCompModulePrinter',
18
+ 'SourceLayoutPolicy',
19
+ 'SourceQuotingUnparser',
20
+ ]
@@ -0,0 +1,30 @@
1
+ """The fixed text PyneComp puts around every module it compiles."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class PyneCompBanner:
9
+ """Module docstring and ``__main__`` footer of a PyneComp module."""
10
+
11
+ compiler: str = 'PyneComp v6.0.79'
12
+ magic: str = '@pyne edge'
13
+
14
+ def header(self) -> list[str]:
15
+ return [
16
+ '"""',
17
+ self.magic,
18
+ '',
19
+ f'This code was compiled by {self.compiler} — the Pine Script to Python compiler.',
20
+ 'Run with open-source PyneCore: https://pynecore.org',
21
+ 'Compile Pine Scripts online at PyneSys: https://pynesys.io',
22
+ '"""',
23
+ ]
24
+
25
+ def footer(self) -> list[str]:
26
+ return [
27
+ 'if __name__ == "__main__":',
28
+ ' from pynecore.standalone import run',
29
+ ' run(__file__)',
30
+ ]
@@ -0,0 +1,69 @@
1
+ """Where PyneComp leaves blank lines inside a function body.
2
+
3
+ Measured on ``assets/pyne``: for a Pine v4/v5 script every blank line in
4
+ ``main`` follows a nested ``def``/``class`` (619 of 622 modules); a Pine v6
5
+ script keeps the blank lines of its source (291 of 293 modules have some).
6
+
7
+ A Pine v6 statement *marks* a gap when the Pine line just above it is blank or
8
+ a comment. The statements PyneComp moves out of ``main`` to the module (the
9
+ constants the inputs read) keep their marks: between two statements of
10
+ ``main``, ``n`` marks (the moved statements' and the second statement's own)
11
+ print ``(2n + 1) // 3`` blank lines. Inputs, which become ``main``'s
12
+ parameters, leave no mark. Measured with probe scripts compiled by PyneComp
13
+ v6.0.80 (``n`` = 1..6) and checked on the 71 such gaps of the reference.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import ast
18
+ from typing import Mapping, Protocol, Sequence
19
+
20
+ _BLOCK_DEFINITIONS = (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)
21
+
22
+
23
+ class BlankLinePolicy(Protocol):
24
+ def blank_lines_before(self, previous: ast.stmt, current: ast.stmt) -> int: ...
25
+
26
+
27
+ class AfterDefinitionPolicy:
28
+ """Pine v4/v5: one blank line after a nested function or class, none elsewhere."""
29
+
30
+ def blank_lines_before(self, previous: ast.stmt, current: ast.stmt) -> int:
31
+ return 1 if isinstance(previous, _BLOCK_DEFINITIONS) else 0
32
+
33
+
34
+ class SourceLayoutPolicy:
35
+ """Pine v6: a blank line where the Pine line just above the statement is
36
+ blank or a comment (a dropped comment leaves a blank line behind).
37
+
38
+ ``origins`` maps a Python line number to the Pine line the statement
39
+ printed there came from.
40
+ """
41
+
42
+ def __init__(self, origins: Mapping[int, int], pine_lines: Sequence[str],
43
+ moved_lines: Sequence[int] = ()) -> None:
44
+ self._origins = origins
45
+ self._pine_lines = pine_lines
46
+ #: the Pine lines of the statements moved out of main() to the module
47
+ self._moved_lines = sorted(set(moved_lines))
48
+
49
+ def _pine_line(self, node: ast.stmt) -> int:
50
+ decorators = getattr(node, 'decorator_list', None)
51
+ first = min(decorator.lineno for decorator in decorators) if decorators else node.lineno
52
+ return self._origins.get(first, 0)
53
+
54
+ def _marks(self, line: int) -> bool:
55
+ above = self._pine_lines[line - 2].strip() if 0 <= line - 2 < len(self._pine_lines) else ''
56
+ return not above or above.startswith('//')
57
+
58
+ def blank_lines_before(self, previous: ast.stmt, current: ast.stmt) -> int:
59
+ line = self._pine_line(current)
60
+ previous_line = self._pine_line(previous)
61
+ after_definition = isinstance(previous, _BLOCK_DEFINITIONS)
62
+ # `a = 1, b = 2` on one Pine line; a value's last assignment after its lowered block (`x = switch`)
63
+ if not after_definition and (line <= 1 or line <= previous_line):
64
+ return 0
65
+ # a nested def or class marks the gap after it once, whatever its source has (467 of 467 gaps)
66
+ marks = (1 if after_definition else 0) + (1 if line > 1 and self._marks(line) else 0)
67
+ if 0 < previous_line < line:
68
+ marks += sum(1 for moved in self._moved_lines if previous_line < moved < line and self._marks(moved))
69
+ return max((2 * marks + 1) // 3, 1 if after_definition else 0)
@@ -0,0 +1,61 @@
1
+ """The PyneComp output profile: pine2pyne semantics, PyneComp layout."""
2
+ from __future__ import annotations
3
+
4
+ import ast
5
+ import sys
6
+ from typing import Sequence
7
+
8
+ from ...codegen import CodeGenerator
9
+ from ...core.contracts import CompilationUnit, OutputProfile
10
+ from ...errors import CodeGenError
11
+ from .passes import ModulePass, PassContext
12
+ from .printer import LayoutSource, PyneCompModulePrinter
13
+
14
+
15
+ class PyneCompEmitter:
16
+ """Generates code with :class:`~pine2pyne.codegen.CodeGenerator`, then
17
+ re-prints it with :class:`PyneCompModulePrinter`.
18
+
19
+ Three separate concerns: the generator decides *what* the program says,
20
+ the passes respell it the way PyneComp spells it, the printer lays it out.
21
+ """
22
+
23
+ profile = OutputProfile.PYNECOMP
24
+
25
+ def __init__(self, printer: PyneCompModulePrinter, passes: Sequence[ModulePass] = ()) -> None:
26
+ self._printer = printer
27
+ self._passes = tuple(passes)
28
+
29
+ #: the AST passes walk an expression recursively: a long concatenation (forex_factory_events_id_A) is deep
30
+ RECURSION_LIMIT = 20000
31
+
32
+ def emit(self, unit: CompilationUnit) -> str:
33
+ if sys.getrecursionlimit() < self.RECURSION_LIMIT:
34
+ sys.setrecursionlimit(self.RECURSION_LIMIT)
35
+ # Pine v4/v5 strict and/or is StrictBooleanHoistingPass's job, not all((...))
36
+ unit.transformed.strict_boolean_operators = False
37
+ generator = CodeGenerator(keep_keyword_string_quotes=True)
38
+ python_source = generator.generate(unit.transformed)
39
+ # an output element may span several lines (the import block does)
40
+ first_line_of: list[int] = []
41
+ line_number = 1
42
+ for element in generator.output:
43
+ first_line_of.append(line_number)
44
+ line_number += element.count('\n') + 1
45
+ origins = {first_line_of[index]: pine_line for index, pine_line in generator.line_origins.items()
46
+ if index < len(first_line_of)}
47
+ source = LayoutSource(
48
+ python_source=python_source,
49
+ pine_source=unit.pine_source,
50
+ pine_version=unit.pine_version,
51
+ origins=origins,
52
+ )
53
+ try:
54
+ tree = ast.parse(python_source)
55
+ except SyntaxError as error: # a construct the generator spelled as no valid Python
56
+ raise CodeGenError(f'the generated code is not valid Python: {error.msg}',
57
+ line=origins.get(error.lineno or 0, 0)) from error
58
+ context = PassContext(unit=unit, origins=origins)
59
+ for module_pass in self._passes:
60
+ module_pass.apply(tree, context)
61
+ return self._printer.render(source, tree, context.spellings)
@@ -0,0 +1,65 @@
1
+ """The import block, laid out the way PyneComp lays it out.
2
+
3
+ Measured on the 1005 reference modules in ``assets/pyne``:
4
+
5
+ * ``from`` imports come first, one per module, modules in alphabetical order;
6
+ plain ``import`` statements (library imports) follow, sorted;
7
+ * names of a ``pynecore`` module are sorted (code-point order), ``typing``
8
+ keeps the order it was given;
9
+ * a ``from`` line up to 100 characters stays on one line; a longer one is
10
+ parenthesised and its names filled greedily, 77 characters on the first
11
+ inner line and 99 on the following ones (856 of 857 wrapped imports).
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import ast
16
+ from dataclasses import dataclass
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ImportBlockFormatter:
21
+ single_line_limit: int = 100
22
+ first_line_width: int = 77
23
+ next_line_width: int = 99
24
+ indent: str = ' '
25
+ unsorted_modules: frozenset = frozenset({'typing'})
26
+
27
+ def format(self, statements: list[ast.stmt]) -> list[str]:
28
+ from_imports: dict[str, list[str]] = {}
29
+ plain_imports: list[str] = []
30
+ for statement in statements:
31
+ if isinstance(statement, ast.ImportFrom):
32
+ module = '.' * statement.level + (statement.module or '')
33
+ names = from_imports.setdefault(module, [])
34
+ for alias in statement.names:
35
+ spelled = alias.name if alias.asname is None else f'{alias.name} as {alias.asname}'
36
+ if spelled not in names:
37
+ names.append(spelled)
38
+ elif isinstance(statement, ast.Import):
39
+ line = ast.unparse(statement)
40
+ if line not in plain_imports:
41
+ plain_imports.append(line)
42
+ lines: list[str] = []
43
+ for module in sorted(from_imports):
44
+ names = from_imports[module]
45
+ if module not in self.unsorted_modules:
46
+ names = sorted(names)
47
+ lines.extend(self._from_import(module, names))
48
+ return lines + sorted(plain_imports) # library imports sorted by path (23 of 23 in the reference)
49
+
50
+ def _from_import(self, module: str, names: list[str]) -> list[str]:
51
+ single = f'from {module} import {", ".join(names)}'
52
+ if len(single) <= self.single_line_limit:
53
+ return [single]
54
+ rows: list[list[str]] = [[]]
55
+ for name in names:
56
+ row = rows[-1]
57
+ width = self.first_line_width if len(rows) == 1 else self.next_line_width
58
+ candidate = self.indent + ', '.join(row + [name]) + ','
59
+ if row and len(candidate) > width:
60
+ rows.append([name])
61
+ else:
62
+ row.append(name)
63
+ body = [self.indent + ', '.join(row) + ',' for row in rows[:-1]]
64
+ body.append(self.indent + ', '.join(rows[-1]))
65
+ return [f'from {module} import ('] + body + [')']
@@ -0,0 +1,84 @@
1
+ """Python-AST rewrites from pine2pyne's spelling to PyneComp's."""
2
+ from .annotations import ClosureHistoryPass, ColorCastPass, DeclarationAnnotationPass, SeriesAnnotationPass
3
+ from .base import ModulePass, PassContext
4
+ from .builtins import (
5
+ CurrentValuePass,
6
+ InputOptionsPass,
7
+ LegacyHlineStylePass,
8
+ LegacyLineWidthPass,
9
+ LegacyStrategyDirectionPass,
10
+ ShadowedNamespacePass,
11
+ TaSeriesPass,
12
+ )
13
+ from .hoisting import LegacyIffPass, StrictBooleanHoistingPass
14
+ from .imports import ImportReconciliationPass
15
+ from .legacy_colors import LegacyTransparencyPass
16
+ from .library import LibraryPreamblePass
17
+ from .rewrites import (
18
+ ArithmeticReassociationPass,
19
+ DeclarationCallShapePass,
20
+ FlatBoolOpPass,
21
+ HexColorPass,
22
+ MathNamespacePass,
23
+ NaBoolFlagPass,
24
+ StrategyDefaultsPass,
25
+ )
26
+
27
+
28
+ def default_passes() -> tuple[ModulePass, ...]:
29
+ """The PyneComp rewrites in the order they apply; imports are reconciled last."""
30
+ return (
31
+ DeclarationCallShapePass(),
32
+ StrategyDefaultsPass(),
33
+ NaBoolFlagPass(),
34
+ SeriesAnnotationPass(),
35
+ LegacyTransparencyPass(),
36
+ HexColorPass(),
37
+ ColorCastPass(),
38
+ MathNamespacePass(),
39
+ TaSeriesPass(),
40
+ LegacyStrategyDirectionPass(),
41
+ LegacyLineWidthPass(),
42
+ CurrentValuePass(),
43
+ LegacyHlineStylePass(),
44
+ InputOptionsPass(),
45
+ FlatBoolOpPass(),
46
+ ArithmeticReassociationPass(),
47
+ StrictBooleanHoistingPass(),
48
+ LegacyIffPass(),
49
+ DeclarationAnnotationPass(),
50
+ ClosureHistoryPass(),
51
+ ShadowedNamespacePass(),
52
+ LibraryPreamblePass(),
53
+ ImportReconciliationPass(),
54
+ )
55
+
56
+
57
+ __all__ = [
58
+ 'LibraryPreamblePass',
59
+ 'ArithmeticReassociationPass',
60
+ 'ClosureHistoryPass',
61
+ 'ColorCastPass',
62
+ 'CurrentValuePass',
63
+ 'InputOptionsPass',
64
+ 'LegacyHlineStylePass',
65
+ 'LegacyLineWidthPass',
66
+ 'LegacyStrategyDirectionPass',
67
+ 'TaSeriesPass',
68
+ 'DeclarationCallShapePass',
69
+ 'FlatBoolOpPass',
70
+ 'HexColorPass',
71
+ 'ImportReconciliationPass',
72
+ 'LegacyIffPass',
73
+ 'LegacyTransparencyPass',
74
+ 'MathNamespacePass',
75
+ 'DeclarationAnnotationPass',
76
+ 'ModulePass',
77
+ 'NaBoolFlagPass',
78
+ 'PassContext',
79
+ 'SeriesAnnotationPass',
80
+ 'ShadowedNamespacePass',
81
+ 'StrategyDefaultsPass',
82
+ 'StrictBooleanHoistingPass',
83
+ 'default_passes',
84
+ ]