fpy2 0.0.3__tar.gz → 0.0.4__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.

Potentially problematic release.


This version of fpy2 might be problematic. Click here for more details.

Files changed (123) hide show
  1. {fpy2-0.0.3 → fpy2-0.0.4}/PKG-INFO +3 -3
  2. {fpy2-0.0.3 → fpy2-0.0.4}/README.md +2 -2
  3. fpy2-0.0.4/fpy2/__init__.py +113 -0
  4. fpy2-0.0.4/fpy2/analysis/__init__.py +5 -0
  5. fpy2-0.0.4/fpy2/analysis/define_use.py +227 -0
  6. {fpy2-0.0.3/fpy2/ast → fpy2-0.0.4/fpy2/analysis}/live_vars.py +22 -18
  7. {fpy2-0.0.3/fpy2/ast → fpy2-0.0.4/fpy2/analysis}/syntax_check.py +51 -44
  8. fpy2-0.0.4/fpy2/ast/__init__.py +9 -0
  9. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/ast/formatter.py +101 -47
  10. fpy2-0.0.4/fpy2/ast/fpyast.py +1247 -0
  11. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/ast/visitor.py +77 -59
  12. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/backend/__init__.py +0 -1
  13. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/backend/backend.py +2 -3
  14. fpy2-0.0.4/fpy2/backend/fpc.py +795 -0
  15. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/decorator.py +61 -10
  16. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/fpc_context.py +28 -7
  17. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/frontend/fpc.py +152 -132
  18. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/frontend/parser.py +120 -136
  19. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/function.py +2 -28
  20. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/interpret/__init__.py +0 -1
  21. fpy2-0.0.4/fpy2/interpret/default.py +737 -0
  22. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/interpret/interpreter.py +27 -10
  23. fpy2-0.0.4/fpy2/interpret/native.py +673 -0
  24. fpy2-0.0.4/fpy2/libraries/__init__.py +0 -0
  25. fpy2-0.0.4/fpy2/libraries/core.py +158 -0
  26. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/math.py +107 -76
  27. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/__init__.py +97 -19
  28. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/context.py +18 -5
  29. fpy2-0.0.3/fpy2/number/ext.py → fpy2-0.0.4/fpy2/number/ext_float.py +39 -35
  30. fpy2-0.0.4/fpy2/number/fixed.py +158 -0
  31. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/globals.py +1 -2
  32. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/gmp.py +155 -158
  33. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/ieee754.py +7 -4
  34. fpy2-0.0.4/fpy2/number/mp_fixed.py +305 -0
  35. fpy2-0.0.3/fpy2/number/mp.py → fpy2-0.0.4/fpy2/number/mp_float.py +10 -6
  36. fpy2-0.0.4/fpy2/number/mpb_fixed.py +390 -0
  37. fpy2-0.0.3/fpy2/number/mpb.py → fpy2-0.0.4/fpy2/number/mpb_float.py +22 -15
  38. fpy2-0.0.3/fpy2/number/mps.py → fpy2-0.0.4/fpy2/number/mps_float.py +14 -10
  39. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/native.py +1 -2
  40. fpy2-0.0.3/fpy2/number/real.py → fpy2-0.0.4/fpy2/number/number.py +469 -12
  41. fpy2-0.0.4/fpy2/number/real.py +206 -0
  42. fpy2-0.0.4/fpy2/primitive.py +33 -0
  43. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/__init__.py +1 -1
  44. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/applier.py +31 -28
  45. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/matcher.py +49 -27
  46. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/pattern.py +3 -2
  47. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/rewrite.py +1 -1
  48. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/sample/sample.py +9 -10
  49. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/sample/table.py +15 -15
  50. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/transform/__init__.py +5 -2
  51. {fpy2-0.0.3/fpy2/ast → fpy2-0.0.4/fpy2/transform}/context_inline.py +7 -4
  52. fpy2-0.0.4/fpy2/transform/copy_propagate.py +76 -0
  53. fpy2-0.0.4/fpy2/transform/for_bundling.py +177 -0
  54. fpy2-0.0.4/fpy2/transform/for_unpack.py +69 -0
  55. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/transform/func_update.py +11 -13
  56. fpy2-0.0.4/fpy2/transform/if_bundling.py +254 -0
  57. fpy2-0.0.4/fpy2/transform/rename_target.py +95 -0
  58. fpy2-0.0.4/fpy2/transform/simplify_if.py +185 -0
  59. fpy2-0.0.4/fpy2/transform/while_bundling.py +136 -0
  60. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/typing/__init__.pyi +3 -3
  61. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/__init__.py +1 -0
  62. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/fractions.py +38 -24
  63. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/gensym.py +9 -1
  64. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/identifier.py +25 -1
  65. fpy2-0.0.4/fpy2/utils/inspect.py +15 -0
  66. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/PKG-INFO +3 -3
  67. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/SOURCES.txt +18 -30
  68. {fpy2-0.0.3 → fpy2-0.0.4}/pyproject.toml +1 -1
  69. fpy2-0.0.3/fpy2/__init__.py +0 -73
  70. fpy2-0.0.3/fpy2/analysis/__init__.py +0 -8
  71. fpy2-0.0.3/fpy2/analysis/define_use.py +0 -105
  72. fpy2-0.0.3/fpy2/analysis/live_vars.py +0 -225
  73. fpy2-0.0.3/fpy2/analysis/reaching_defs.py +0 -132
  74. fpy2-0.0.3/fpy2/analysis/verify.py +0 -228
  75. fpy2-0.0.3/fpy2/ast/__init__.py +0 -15
  76. fpy2-0.0.3/fpy2/ast/define_use.py +0 -86
  77. fpy2-0.0.3/fpy2/ast/fpyast.py +0 -1070
  78. fpy2-0.0.3/fpy2/backend/fpc.py +0 -539
  79. fpy2-0.0.3/fpy2/backend/fpy.py +0 -306
  80. fpy2-0.0.3/fpy2/interpret/default.py +0 -724
  81. fpy2-0.0.3/fpy2/interpret/native.py +0 -564
  82. fpy2-0.0.3/fpy2/interpret/real.py +0 -647
  83. fpy2-0.0.3/fpy2/ir/__init__.py +0 -12
  84. fpy2-0.0.3/fpy2/ir/codegen.py +0 -324
  85. fpy2-0.0.3/fpy2/ir/formatter.py +0 -301
  86. fpy2-0.0.3/fpy2/ir/ir.py +0 -804
  87. fpy2-0.0.3/fpy2/ir/types.py +0 -29
  88. fpy2-0.0.3/fpy2/ir/visitor.py +0 -654
  89. fpy2-0.0.3/fpy2/number/number.py +0 -427
  90. fpy2-0.0.3/fpy2/profile/__init__.py +0 -2
  91. fpy2-0.0.3/fpy2/profile/expr_profiler.py +0 -240
  92. fpy2-0.0.3/fpy2/profile/function_profiler.py +0 -140
  93. fpy2-0.0.3/fpy2/runtime/__init__.py +0 -1
  94. fpy2-0.0.3/fpy2/runtime/metric.py +0 -32
  95. fpy2-0.0.3/fpy2/runtime/real/__init__.py +0 -2
  96. fpy2-0.0.3/fpy2/runtime/real/interval.py +0 -61
  97. fpy2-0.0.3/fpy2/runtime/real/rival_manager.py +0 -142
  98. fpy2-0.0.3/fpy2/runtime/trace.py +0 -12
  99. fpy2-0.0.3/fpy2/transform/for_bundling.py +0 -109
  100. fpy2-0.0.3/fpy2/transform/simplify_if.py +0 -114
  101. fpy2-0.0.3/fpy2/transform/ssa.py +0 -337
  102. fpy2-0.0.3/fpy2/transform/unssa.py +0 -196
  103. fpy2-0.0.3/fpy2/transform/while_bundling.py +0 -125
  104. {fpy2-0.0.3 → fpy2-0.0.4}/LICENSE +0 -0
  105. {fpy2-0.0.3/fpy2/runtime → fpy2-0.0.4/fpy2}/env.py +0 -0
  106. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/frontend/__init__.py +0 -0
  107. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/number/round.py +0 -0
  108. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/rewrite/subst.py +0 -0
  109. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/sample/__init__.py +0 -0
  110. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/typing/__init__.py +0 -0
  111. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/bits.py +0 -0
  112. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/compare.py +0 -0
  113. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/decorator.py +0 -0
  114. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/error.py +0 -0
  115. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/float_params.py +0 -0
  116. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/iterator.py +0 -0
  117. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/location.py +0 -0
  118. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/ordering.py +0 -0
  119. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2/utils/string.py +0 -0
  120. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/dependency_links.txt +0 -0
  121. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/requires.txt +0 -0
  122. {fpy2-0.0.3 → fpy2-0.0.4}/fpy2.egg-info/top_level.txt +0 -0
  123. {fpy2-0.0.3 → fpy2-0.0.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fpy2
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: An embedded DSL for numerical computing
5
5
  Author-email: Brett Saiki <bsaiki@cs.washington.edu>
6
6
  License-Expression: MIT
@@ -27,9 +27,9 @@ Dynamic: license-file
27
27
 
28
28
  An embedded Python DSL for specifying and simulating numerical algorithms.
29
29
 
30
- Important lints:
30
+ Important links:
31
31
  - PyPI package: [fpy2](https://pypi.org/project/fpy2/)
32
- - Documentation: [fpy2.readthedocs.io](https://fpy2.readthedocs.io/)
32
+ - Documentation: [fpy.readthedocs.io](https://fpy.readthedocs.io/)
33
33
  - GitHub: [fpy](https://github.com/bksaiki/fpy)
34
34
 
35
35
  ## Installation
@@ -2,9 +2,9 @@
2
2
 
3
3
  An embedded Python DSL for specifying and simulating numerical algorithms.
4
4
 
5
- Important lints:
5
+ Important links:
6
6
  - PyPI package: [fpy2](https://pypi.org/project/fpy2/)
7
- - Documentation: [fpy2.readthedocs.io](https://fpy2.readthedocs.io/)
7
+ - Documentation: [fpy.readthedocs.io](https://fpy.readthedocs.io/)
8
8
  - GitHub: [fpy](https://github.com/bksaiki/fpy)
9
9
 
10
10
  ## Installation
@@ -0,0 +1,113 @@
1
+ """
2
+ FPy is a library for simulating numerical programs
3
+ with many different number systems.
4
+
5
+ It provides an embedded DSL for specifying programs via its `@fpy` decorator.
6
+ The language has a runtime that can simulate programs
7
+ under different number systems and compilers to other languages.
8
+
9
+ The numbers library supports many different number types including:
10
+
11
+ - multiprecision floating point (`MPFloatContext`)
12
+ - multiprecision floatingpoint with subnormalization (`MPSFloatContext`)
13
+ - bounded, multiprecision floating point (`MPBFloatContext`)
14
+ - IEEE 754 floating point (`IEEEContext`)
15
+
16
+ These number systems guarantee correct rounding via MPFR.
17
+ """
18
+
19
+ from .number import (
20
+ # number types
21
+ Float,
22
+ RealFloat,
23
+ # abstract context types
24
+ Context,
25
+ OrdinalContext,
26
+ SizedContext,
27
+ EncodableContext,
28
+ # concrete context types
29
+ ExtFloatContext,
30
+ FixedContext,
31
+ MPFixedContext,
32
+ MPFloatContext,
33
+ MPBFixedContext,
34
+ MPBFloatContext,
35
+ MPSFloatContext,
36
+ IEEEContext,
37
+ RealContext,
38
+ # rounding utilities
39
+ RoundingMode,
40
+ RoundingDirection, RM,
41
+ # encoding utilities
42
+ ExtFloatNanKind,
43
+ FixedOverflowKind, OF,
44
+ # type aliases
45
+ FP256, FP128, FP64, FP32, FP16,
46
+ TF32, BF16,
47
+ S1E5M2, S1E4M3,
48
+ MX_E5M2, MX_E4M3, MX_E3M2, MX_E2M3, MX_E2M1,
49
+ FP8P1, FP8P2, FP8P3, FP8P4, FP8P5, FP8P6, FP8P7,
50
+ INTEGER,
51
+ SINT8, SINT16, SINT32, SINT64,
52
+ UINT8, UINT16, UINT32, UINT64
53
+ )
54
+
55
+ from .fpc_context import FPCoreContext, NoSuchContextError
56
+
57
+ from .decorator import fpy, pattern, fpy_prim
58
+
59
+ from .backend import (
60
+ Backend,
61
+ FPCoreCompiler
62
+ )
63
+
64
+ from .interpret import (
65
+ Interpreter,
66
+ PythonInterpreter,
67
+ DefaultInterpreter,
68
+ set_default_interpreter,
69
+ get_default_interpreter,
70
+ )
71
+
72
+ from .function import Function
73
+ from .env import ForeignEnv
74
+
75
+ from .utils import (
76
+ fraction,
77
+ digits_to_fraction as digits,
78
+ decnum_to_fraction as decnum,
79
+ hexnum_to_fraction as hexnum,
80
+ )
81
+
82
+ ###########################################################
83
+ # Re-exports
84
+
85
+ from titanfp.titanic.ndarray import NDArray
86
+
87
+ ###########################################################
88
+ # typing hints
89
+
90
+ import typing
91
+
92
+ from typing import Literal as Dim
93
+
94
+ _Dims = typing.TypeVarTuple('_Dims')
95
+ _DType = typing.TypeVar('_DType')
96
+
97
+ class Tensor(tuple, typing.Generic[*_Dims, _DType]):
98
+ """
99
+ FPy type hint for a homogenous tensor object::
100
+
101
+ from fpy2 import Tensor, Real
102
+ from typing import TypeAlias
103
+
104
+ MatrixN: TypeAlias = Tensor[Literal['N', 'N'], Real]
105
+ Matrix3: TypeAlias = Tensor[Literal[3, 3], Real]
106
+
107
+ Tensors have fixed or symbolic sizes and a uniform scalar data type.
108
+
109
+ Values of this type should not be constructed directly.
110
+ """
111
+
112
+ def __init__(self, *args, **kwargs):
113
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,5 @@
1
+ """Program analyses for FPy programs"""
2
+
3
+ from .define_use import DefineUse, DefineUseAnalysis, Definition, DefinitionCtx
4
+ from .live_vars import LiveVars
5
+ from .syntax_check import SyntaxCheck
@@ -0,0 +1,227 @@
1
+ """Definition use analysis for FPy ASTs"""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TypeAlias, Union
5
+
6
+ from ..ast.fpyast import *
7
+ from ..ast.visitor import DefaultVisitor
8
+ from ..utils import default_repr
9
+
10
+ Definition: TypeAlias = Argument | Stmt | CompExpr
11
+
12
+ @default_repr
13
+ class _DefineUnion:
14
+ """Union of possible definition sites."""
15
+ defs: set[Definition]
16
+
17
+ def __init__(self, defs: set[Definition]):
18
+ self.defs = set(defs)
19
+
20
+ def __eq__(self, other):
21
+ if not isinstance(other, _DefineUnion):
22
+ return NotImplemented
23
+ return self.defs == other.defs
24
+
25
+ def __hash__(self):
26
+ return hash(tuple(self.defs))
27
+
28
+ @staticmethod
29
+ def union(*defs_or_unions: Union[Definition, '_DefineUnion']):
30
+ """Create a union of definitions from a set of definitions or unions."""
31
+ defs: set[Definition] = set()
32
+ for item in defs_or_unions:
33
+ if isinstance(item, _DefineUnion):
34
+ defs.update(item.defs)
35
+ else:
36
+ defs.add(item)
37
+
38
+ if len(defs) == 0:
39
+ raise ValueError('Cannot create a union of an empty set of definitions')
40
+ elif len(defs) == 1:
41
+ return defs.pop()
42
+ else:
43
+ return _DefineUnion(defs)
44
+
45
+ class DefinitionCtx(dict[NamedId, Definition | _DefineUnion]):
46
+ """Mapping from variable to its definition (or possible definitions)."""
47
+
48
+ def copy(self) -> 'DefinitionCtx':
49
+ """Returns a shallow copy of the context."""
50
+ return DefinitionCtx(self)
51
+
52
+ def mutated_in(self, other: 'DefinitionCtx') -> list[NamedId]:
53
+ """
54
+ Returns the set of variables that are defined in `self`
55
+ and mutated in `other`.
56
+ """
57
+ names: set[NamedId] = set()
58
+ for name in self.keys() & other.keys():
59
+ if self[name] != other[name]:
60
+ names.add(name)
61
+ return list(names)
62
+
63
+ def fresh_in(self, other: 'DefinitionCtx') -> set[NamedId]:
64
+ """
65
+ Returns the set of variables that are defined in `other`
66
+ but not in `self`.
67
+ """
68
+ return set(other.keys() - self.keys())
69
+
70
+
71
+ @dataclass
72
+ class DefineUseAnalysis:
73
+ """Result of definition-use analysis"""
74
+ defs: dict[NamedId, set[Definition]]
75
+ uses: dict[Definition, set[Var | IndexedAssign]]
76
+ stmts: dict[Stmt, tuple[DefinitionCtx, DefinitionCtx]]
77
+ blocks: dict[StmtBlock, tuple[DefinitionCtx, DefinitionCtx]]
78
+
79
+ @staticmethod
80
+ def default():
81
+ """Default analysis with empty definitions and uses"""
82
+ return DefineUseAnalysis({}, {}, {}, {})
83
+
84
+ @property
85
+ def names(self) -> set[NamedId]:
86
+ """Returns the set of all variable names in the analysis"""
87
+ return set(self.defs.keys())
88
+
89
+
90
+ class _DefineUseInstance(DefaultVisitor):
91
+ """Per-IR instance of definition-use analysis"""
92
+ ast: FuncDef | StmtBlock
93
+ analysis: DefineUseAnalysis
94
+
95
+ def __init__(self, ast: FuncDef | StmtBlock):
96
+ self.ast = ast
97
+ self.analysis = DefineUseAnalysis.default()
98
+
99
+ def analyze(self):
100
+ match self.ast:
101
+ case FuncDef():
102
+ self._visit_function(self.ast, DefinitionCtx())
103
+ case StmtBlock():
104
+ self._visit_block(self.ast, DefinitionCtx())
105
+ case _:
106
+ raise RuntimeError(f'unreachable case: {self.ast}')
107
+ return self.analysis
108
+
109
+ def _add_def(self, name: NamedId, definition: Definition):
110
+ if name not in self.analysis.defs:
111
+ self.analysis.defs[name] = set()
112
+ self.analysis.defs[name].add(definition)
113
+ self.analysis.uses[definition] = set()
114
+
115
+ def _add_use(self, name: NamedId, use: Var | IndexedAssign, ctx: DefinitionCtx):
116
+ def_or_union = ctx[name]
117
+ if isinstance(def_or_union, _DefineUnion):
118
+ for def_ in def_or_union.defs:
119
+ self.analysis.uses[def_].add(use)
120
+ else:
121
+ self.analysis.uses[def_or_union].add(use)
122
+
123
+ def _visit_var(self, e: Var, ctx: DefinitionCtx):
124
+ if e.name not in ctx:
125
+ raise NotImplementedError(f'undefined variable {e.name}')
126
+ self._add_use(e.name, e, ctx)
127
+
128
+ def _visit_comp_expr(self, e: CompExpr, ctx: DefinitionCtx):
129
+ for iterable in e.iterables:
130
+ self._visit_expr(iterable, ctx)
131
+ ctx = ctx.copy()
132
+ for target in e.targets:
133
+ for name in target.names():
134
+ self._add_def(name, e)
135
+ ctx[name] = e
136
+ self._visit_expr(e.elt, ctx)
137
+
138
+ def _visit_assign(self, stmt: Assign, ctx: DefinitionCtx):
139
+ self._visit_expr(stmt.expr, ctx)
140
+ for var in stmt.binding.names():
141
+ self._add_def(var, stmt)
142
+ ctx[var] = stmt
143
+
144
+ def _visit_indexed_assign(self, stmt: IndexedAssign, ctx: DefinitionCtx):
145
+ self._add_use(stmt.var, stmt, ctx)
146
+ for slice in stmt.slices:
147
+ self._visit_expr(slice, ctx)
148
+ self._visit_expr(stmt.expr, ctx)
149
+
150
+ def _visit_if1(self, stmt: If1Stmt, ctx: DefinitionCtx):
151
+ self._visit_expr(stmt.cond, ctx)
152
+ body_ctx = self._visit_block(stmt.body, ctx.copy())
153
+ # merge contexts along both paths
154
+ # definitions cannot be introduced in the body
155
+ for var in ctx:
156
+ ctx[var] = _DefineUnion.union(ctx[var], body_ctx[var])
157
+
158
+ def _visit_if(self, stmt: IfStmt, ctx: DefinitionCtx):
159
+ self._visit_expr(stmt.cond, ctx)
160
+ ift_ctx = self._visit_block(stmt.ift, ctx.copy())
161
+ iff_ctx = self._visit_block(stmt.iff, ctx.copy())
162
+ # merge contexts along both paths
163
+ for var in ift_ctx.keys() & iff_ctx.keys():
164
+ ctx[var] = _DefineUnion.union(ift_ctx[var], iff_ctx[var])
165
+
166
+ def _visit_while(self, stmt: WhileStmt, ctx: DefinitionCtx):
167
+ self._visit_expr(stmt.cond, ctx)
168
+ body_ctx = self._visit_block(stmt.body, ctx.copy())
169
+ # merge contexts along both paths
170
+ # definitions cannot be introduced in the body
171
+ for var in ctx:
172
+ ctx[var] = _DefineUnion.union(ctx[var], body_ctx[var])
173
+
174
+ def _visit_for(self, stmt: ForStmt, ctx: DefinitionCtx):
175
+ self._visit_expr(stmt.iterable, ctx)
176
+ body_ctx = ctx.copy()
177
+ match stmt.target:
178
+ case NamedId():
179
+ self._add_def(stmt.target, stmt)
180
+ body_ctx[stmt.target] = stmt
181
+ case TupleBinding():
182
+ for var in stmt.target.names():
183
+ self._add_def(var, stmt)
184
+ body_ctx[var] = stmt
185
+
186
+ body_ctx = self._visit_block(stmt.body, body_ctx)
187
+ # merge contexts along both paths
188
+ # definitions cannot be introduced in the body
189
+ for var in ctx:
190
+ ctx[var] = _DefineUnion.union(ctx[var], body_ctx[var])
191
+
192
+ def _visit_statement(self, stmt: Stmt, ctx: DefinitionCtx):
193
+ ctx_in = ctx.copy()
194
+ super()._visit_statement(stmt, ctx)
195
+ self.analysis.stmts[stmt] = (ctx_in, ctx.copy())
196
+
197
+ def _visit_block(self, block: StmtBlock, ctx: DefinitionCtx):
198
+ ctx_in = ctx.copy()
199
+ for stmt in block.stmts:
200
+ self._visit_statement(stmt, ctx)
201
+ self.analysis.blocks[block] = (ctx_in, ctx.copy())
202
+ return ctx
203
+
204
+ def _visit_function(self, func: FuncDef, ctx: DefinitionCtx):
205
+ for arg in func.args:
206
+ if isinstance(arg.name, NamedId):
207
+ self._add_def(arg.name, arg)
208
+ ctx[arg.name] = arg
209
+ self._visit_block(func.body, ctx.copy())
210
+
211
+
212
+ class DefineUse:
213
+ """
214
+ Definition-use analyzer for the FPy IR.
215
+
216
+ Computes definition-use chains for each variable.
217
+
218
+ name ---> definition ---> use1, use2, ...
219
+ ---> definition ---> use1, use2, ...
220
+ ...
221
+ """
222
+
223
+ @staticmethod
224
+ def analyze(ast: FuncDef | StmtBlock):
225
+ if not isinstance(ast, FuncDef | StmtBlock):
226
+ raise TypeError(f'Expected \'FuncDef\' or \'StmtBlock\', got {type(ast)} for {ast}')
227
+ return _DefineUseInstance(ast).analyze()
@@ -1,10 +1,10 @@
1
1
  """Live variable analysis for the FPy AST."""
2
2
 
3
- from . import *
3
+ from ..ast import *
4
4
 
5
5
  _LiveSet = set[NamedId]
6
6
 
7
- class LiveVarsInstance(AstVisitor):
7
+ class LiveVarsInstance(Visitor):
8
8
  """Single-use live variable analyzer"""
9
9
 
10
10
  ast: FuncDef | Expr
@@ -53,12 +53,12 @@ class LiveVarsInstance(AstVisitor):
53
53
  return self._visit_expr(e.arg, ctx)
54
54
 
55
55
  def _visit_binaryop(self, e: BinaryOp, ctx: None) -> _LiveSet:
56
- return self._visit_expr(e.left, ctx) | self._visit_expr(e.right, ctx)
56
+ return self._visit_expr(e.first, ctx) | self._visit_expr(e.second, ctx)
57
57
 
58
58
  def _visit_ternaryop(self, e: TernaryOp, ctx: None) -> _LiveSet:
59
- live0 = self._visit_expr(e.arg0, ctx)
60
- live1 = self._visit_expr(e.arg1, ctx)
61
- live2 = self._visit_expr(e.arg2, ctx)
59
+ live0 = self._visit_expr(e.first, ctx)
60
+ live1 = self._visit_expr(e.second, ctx)
61
+ live2 = self._visit_expr(e.third, ctx)
62
62
  return live0 | live1 | live2
63
63
 
64
64
  def _visit_naryop(self, e: NaryOp, ctx: None) -> _LiveSet:
@@ -87,7 +87,8 @@ class LiveVarsInstance(AstVisitor):
87
87
 
88
88
  def _visit_comp_expr(self, e: CompExpr, ctx: None) -> _LiveSet:
89
89
  live = self._visit_expr(e.elt, ctx)
90
- live -= set([var for var in e.vars if isinstance(var, NamedId)])
90
+ for target in e.targets:
91
+ live |= target.names()
91
92
  for iterable in e.iterables:
92
93
  live |= self._visit_expr(iterable, ctx)
93
94
  return live
@@ -98,6 +99,13 @@ class LiveVarsInstance(AstVisitor):
98
99
  live |= self._visit_expr(s, ctx)
99
100
  return live
100
101
 
102
+ def _visit_tuple_set(self, e: TupleSet, ctx: None) -> _LiveSet:
103
+ live = self._visit_expr(e.array, ctx)
104
+ for s in e.slices:
105
+ live |= self._visit_expr(s, ctx)
106
+ live |= self._visit_expr(e.value, ctx)
107
+ return live
108
+
101
109
  def _visit_if_expr(self, e: IfExpr, ctx: None) -> _LiveSet:
102
110
  cond_live = self._visit_expr(e.cond, ctx)
103
111
  ift_live = self._visit_expr(e.ift, ctx)
@@ -111,20 +119,13 @@ class LiveVarsInstance(AstVisitor):
111
119
  live.add(arg)
112
120
  return live
113
121
 
114
- def _visit_simple_assign(self, stmt: SimpleAssign, live: _LiveSet) -> _LiveSet:
115
- live = set(live)
116
- if isinstance(stmt.var, NamedId):
117
- live -= { stmt.var }
118
- live |= self._visit_expr(stmt.expr, None)
119
- return live
120
-
121
- def _visit_tuple_unpack(self, stmt: TupleUnpack, live: _LiveSet) -> _LiveSet:
122
+ def _visit_assign(self, stmt: Assign, live: _LiveSet) -> _LiveSet:
122
123
  live = set(live)
123
124
  live -= stmt.binding.names()
124
125
  live |= self._visit_expr(stmt.expr, None)
125
126
  return live
126
127
 
127
- def _visit_index_assign(self, stmt: IndexAssign, live: _LiveSet) -> _LiveSet:
128
+ def _visit_indexed_assign(self, stmt: IndexedAssign, live: _LiveSet) -> _LiveSet:
128
129
  live = set(live)
129
130
  live |= self._visit_expr(stmt.expr, None)
130
131
  for s in stmt.slices:
@@ -154,8 +155,11 @@ class LiveVarsInstance(AstVisitor):
154
155
  def _visit_for(self, stmt: ForStmt, live: _LiveSet) -> _LiveSet:
155
156
  live = set(live)
156
157
  live |= self._visit_block(stmt.body, live)
157
- if isinstance(stmt.var, NamedId):
158
- live -= { stmt.var }
158
+ match stmt.target:
159
+ case NamedId():
160
+ live -= { stmt.target }
161
+ case TupleBinding():
162
+ live -= stmt.target.names()
159
163
  live |= self._visit_expr(stmt.iterable, None)
160
164
  return live
161
165
 
@@ -4,8 +4,8 @@ from typing import Optional, Self
4
4
 
5
5
  from ..utils import FPySyntaxError
6
6
 
7
- from .fpyast import *
8
- from .visitor import AstVisitor
7
+ from ..ast.fpyast import *
8
+ from ..ast.visitor import Visitor
9
9
  from .live_vars import LiveVars
10
10
 
11
11
  class _Env:
@@ -41,10 +41,10 @@ _Ctx = tuple[_Env, bool]
41
41
  2nd element: whether the current block is at the top-level.
42
42
  """
43
43
 
44
- class SyntaxCheckInstance(AstVisitor):
44
+ class SyntaxCheckInstance(Visitor):
45
45
  """Single-use instance of syntax checking"""
46
46
  func: FuncDef
47
- free_vars: set[str]
47
+ free_vars: set[NamedId]
48
48
  ignore_unknown: bool
49
49
  ignore_noreturn: bool
50
50
  allow_wildcard: bool
@@ -55,7 +55,7 @@ class SyntaxCheckInstance(AstVisitor):
55
55
  def __init__(
56
56
  self,
57
57
  func: FuncDef,
58
- free_vars: set[str],
58
+ free_vars: set[NamedId],
59
59
  ignore_unknown: bool,
60
60
  ignore_noreturn: bool,
61
61
  allow_wildcard: bool
@@ -90,7 +90,7 @@ class SyntaxCheckInstance(AstVisitor):
90
90
  raise FPySyntaxError(f'unbound variable `{name}`')
91
91
  if not env[name]:
92
92
  raise FPySyntaxError(f'variable `{name}` not defined along all paths')
93
- if name.base in self.free_vars:
93
+ if name in self.free_vars:
94
94
  self.free_var_args.add(name)
95
95
 
96
96
  def _visit_var(self, e: Var, ctx: _Ctx):
@@ -148,15 +148,15 @@ class SyntaxCheckInstance(AstVisitor):
148
148
 
149
149
  def _visit_binaryop(self, e: BinaryOp, ctx: _Ctx):
150
150
  env, _ = ctx
151
- self._visit_expr(e.left, ctx)
152
- self._visit_expr(e.right, ctx)
151
+ self._visit_expr(e.first, ctx)
152
+ self._visit_expr(e.second, ctx)
153
153
  return env
154
154
 
155
155
  def _visit_ternaryop(self, e: TernaryOp, ctx: _Ctx):
156
156
  env, _ = ctx
157
- self._visit_expr(e.arg0, ctx)
158
- self._visit_expr(e.arg1, ctx)
159
- self._visit_expr(e.arg2, ctx)
157
+ self._visit_expr(e.first, ctx)
158
+ self._visit_expr(e.second, ctx)
159
+ self._visit_expr(e.third, ctx)
160
160
  return env
161
161
 
162
162
  def _visit_naryop(self, e: NaryOp, ctx: _Ctx):
@@ -173,7 +173,7 @@ class SyntaxCheckInstance(AstVisitor):
173
173
 
174
174
  def _visit_call(self, e: Call, ctx: _Ctx):
175
175
  env, _ = ctx
176
- self._mark_use(NamedId(e.op), env, ignore_missing=self.ignore_unknown)
176
+ self._mark_use(NamedId(e.name), env, ignore_missing=self.ignore_unknown)
177
177
  for c in e.args:
178
178
  self._visit_expr(c, ctx)
179
179
  return env
@@ -188,9 +188,8 @@ class SyntaxCheckInstance(AstVisitor):
188
188
  env, _ = ctx
189
189
  for iterable in e.iterables:
190
190
  self._visit_expr(iterable, ctx)
191
- for var in e.vars:
192
- if isinstance(var, NamedId):
193
- env = env.extend(var)
191
+ for target in e.targets:
192
+ env = self._visit_binding(target, env)
194
193
  self._visit_expr(e.elt, (env, False))
195
194
  return env
196
195
 
@@ -201,6 +200,14 @@ class SyntaxCheckInstance(AstVisitor):
201
200
  self._visit_expr(s, ctx)
202
201
  return env
203
202
 
203
+ def _visit_tuple_set(self, e: TupleSet, ctx: _Ctx):
204
+ env, _ = ctx
205
+ self._visit_expr(e.array, ctx)
206
+ for s in e.slices:
207
+ self._visit_expr(s, ctx)
208
+ self._visit_expr(e.value, ctx)
209
+ return env
210
+
204
211
  def _visit_if_expr(self, e: IfExpr, ctx: _Ctx):
205
212
  env, _ = ctx
206
213
  self._visit_expr(e.cond, ctx)
@@ -231,32 +238,29 @@ class SyntaxCheckInstance(AstVisitor):
231
238
  if free not in self.free_vars:
232
239
  raise FPySyntaxError('context is data-dependent')
233
240
 
234
- def _visit_simple_assign(self, stmt: SimpleAssign, ctx: _Ctx):
235
- env, _ = ctx
236
- self._visit_expr(stmt.expr, ctx)
237
- if isinstance(stmt.var, NamedId):
238
- env = env.extend(stmt.var)
241
+ def _visit_binding(self, binding: Id | TupleBinding, env: _Env):
242
+ match binding:
243
+ case NamedId():
244
+ env = env.extend(binding)
245
+ case UnderscoreId():
246
+ pass
247
+ case TupleBinding():
248
+ env = self._visit_tuple_binding(binding, env)
249
+ case _:
250
+ raise RuntimeError('unreachable', binding)
239
251
  return env
240
252
 
241
- def _visit_tuple_binding(self, binding: TupleBinding, ctx: _Ctx):
242
- env, _ = ctx
253
+ def _visit_tuple_binding(self, binding: TupleBinding, env: _Env):
243
254
  for elt in binding.elts:
244
- match elt:
245
- case NamedId():
246
- env = env.extend(elt)
247
- case TupleBinding():
248
- env = self._visit_tuple_binding(elt, ctx)
249
- case UnderscoreId():
250
- pass
251
- case _:
252
- raise NotImplementedError('unreachable', elt)
255
+ env = self._visit_binding(elt, env)
253
256
  return env
254
257
 
255
- def _visit_tuple_unpack(self, stmt: TupleUnpack, ctx: _Ctx):
258
+ def _visit_assign(self, stmt: Assign, ctx: _Ctx):
259
+ env, _ = ctx
256
260
  self._visit_expr(stmt.expr, ctx)
257
- return self._visit_tuple_binding(stmt.binding, ctx)
261
+ return self._visit_binding(stmt.binding, env)
258
262
 
259
- def _visit_index_assign(self, stmt: IndexAssign, ctx: _Ctx):
263
+ def _visit_indexed_assign(self, stmt: IndexedAssign, ctx: _Ctx):
260
264
  env, _ = ctx
261
265
  self._mark_use(stmt.var, env)
262
266
  for s in stmt.slices:
@@ -286,8 +290,7 @@ class SyntaxCheckInstance(AstVisitor):
286
290
  def _visit_for(self, stmt: ForStmt, ctx: _Ctx):
287
291
  env, _ = ctx
288
292
  self._visit_expr(stmt.iterable, ctx)
289
- if isinstance(stmt.var, NamedId):
290
- env = env.extend(stmt.var)
293
+ env = self._visit_binding(stmt.target, env)
291
294
  body_env = self._visit_block(stmt.body, (env, False))
292
295
  return env.merge(body_env)
293
296
 
@@ -332,7 +335,7 @@ class SyntaxCheckInstance(AstVisitor):
332
335
  def _visit_function(self, func: FuncDef, ctx: _Ctx):
333
336
  env, _ = ctx
334
337
  for var in self.free_vars:
335
- env = env.extend(NamedId(var))
338
+ env = env.extend(var)
336
339
  for arg in func.args:
337
340
  if isinstance(arg.name, NamedId):
338
341
  env = env.extend(arg.name)
@@ -357,22 +360,25 @@ class SyntaxCheck:
357
360
  Rules enforced:
358
361
 
359
362
  Variables:
363
+
360
364
  - any variables must be defined before it is used;
361
365
 
362
366
  Return statements:
363
- - all functions must have exactly one return statement,
364
- - must be at the end of the function definiton;
367
+
368
+ - all functions must have exactly one return statement,
369
+ - must be at the end of the function definiton;
365
370
 
366
371
  If statements
367
- - any variable must be defined along both branches when
368
- used after the `if` statement
372
+
373
+ - any variable must be defined along both branches when
374
+ used after the `if` statement
369
375
  """
370
376
 
371
377
  @staticmethod
372
- def analyze(
378
+ def check(
373
379
  func: FuncDef,
374
380
  *,
375
- free_vars: Optional[set[str]] = None,
381
+ free_vars: Optional[set[NamedId]] = None,
376
382
  ignore_unknown: bool = False,
377
383
  ignore_noreturn: bool = False,
378
384
  allow_wildcard: bool = False
@@ -385,8 +391,9 @@ class SyntaxCheck:
385
391
 
386
392
  if not isinstance(func, FuncDef):
387
393
  raise TypeError(f'expected a Function, got {func}')
394
+
388
395
  if free_vars is None:
389
- free_vars = set()
396
+ free_vars = set(func.free_vars)
390
397
 
391
398
  inst = SyntaxCheckInstance(func, free_vars, ignore_unknown, ignore_noreturn, allow_wildcard)
392
399
  return inst.analyze()
@@ -0,0 +1,9 @@
1
+ """
2
+ Abstract Syntax Tree (AST) for the FPy language.
3
+ """
4
+
5
+ from .fpyast import *
6
+ from .formatter import Formatter, BaseFormatter
7
+ from .visitor import Visitor, DefaultVisitor, DefaultTransformVisitor
8
+
9
+ set_default_formatter(Formatter())