bruedemo 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.
- brue/__init__.py +65 -0
- brue/__main__.py +5 -0
- brue/api.py +182 -0
- brue/ast.py +319 -0
- brue/builtins.py +2280 -0
- brue/cli.py +369 -0
- brue/data.py +630 -0
- brue/errors.py +74 -0
- brue/indicators.py +2411 -0
- brue/jsmath.py +551 -0
- brue/jsnum.py +124 -0
- brue/output.py +626 -0
- brue/parser.py +1263 -0
- brue/quant.py +524 -0
- brue/runtime.py +2691 -0
- brue/session.py +234 -0
- brue/strategy.py +1017 -0
- brue/tokenizer.py +395 -0
- brue/value.py +285 -0
- bruedemo-0.1.0.dist-info/METADATA +20 -0
- bruedemo-0.1.0.dist-info/RECORD +22 -0
- bruedemo-0.1.0.dist-info/WHEEL +4 -0
brue/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""brue: pure-Python implementation of the Brue trading-strategy language.
|
|
2
|
+
|
|
3
|
+
Ported 1:1 from the Rust engine (src/lib.rs); the conformance harness in
|
|
4
|
+
conformance/ verifies byte-identical output against it.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_script(source, bars):
|
|
11
|
+
from .runtime import ExternalData
|
|
12
|
+
return run_script_with_data(source, bars, ExternalData())
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_script_with_data(source, bars, external):
|
|
16
|
+
from .runtime import RunOptions
|
|
17
|
+
return run_script_full(source, bars, external, {}, RunOptions())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run_script_full(source, bars, external, input_overrides, options):
|
|
21
|
+
"""tokenize + parse + execute, errors shaped exactly like the reference."""
|
|
22
|
+
from . import parser, tokenizer
|
|
23
|
+
from .errors import BrueError
|
|
24
|
+
|
|
25
|
+
# Windows editors and Python text-mode writes produce CRLF; the
|
|
26
|
+
# tokenizer speaks LF. Normalize at every entry point.
|
|
27
|
+
source = source.replace("\r\n", "\n").replace("\r", "\n")
|
|
28
|
+
try:
|
|
29
|
+
tokens = tokenizer.tokenize(source)
|
|
30
|
+
except BrueError as e:
|
|
31
|
+
return _error_output(e)
|
|
32
|
+
try:
|
|
33
|
+
program = parser.parse(tokens)
|
|
34
|
+
except BrueError as e:
|
|
35
|
+
return _error_output(e)
|
|
36
|
+
from .runtime import execute_with_options
|
|
37
|
+
return execute_with_options(program, bars, input_overrides, external, options)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# parse_time_arg and parse_bars_csv live in data.py, ported with Rust-exact
|
|
41
|
+
# parsing (float()/int() alone are looser than f64::from_str: underscores,
|
|
42
|
+
# unicode digits); re-exported here for the lib.rs-shaped public API.
|
|
43
|
+
from .data import parse_bars_csv, parse_time_arg # noqa: E402,F401
|
|
44
|
+
from .api import BacktestResult, BrueScriptError, backtest # noqa: E402,F401
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _error_output(e):
|
|
48
|
+
from .output import ErrorInfo, ExecOutput
|
|
49
|
+
from .value import Value
|
|
50
|
+
return ExecOutput(
|
|
51
|
+
success=False,
|
|
52
|
+
commands=[],
|
|
53
|
+
errors=[ErrorInfo(message=e.message, line=e.loc.line, col=e.loc.col,
|
|
54
|
+
phase=e.phase, suggestion=e.suggestion)],
|
|
55
|
+
inputs=[],
|
|
56
|
+
live_orders=[],
|
|
57
|
+
live_closes=[],
|
|
58
|
+
decl_title=Value.Null,
|
|
59
|
+
decl_overlay=Value.Bool(True),
|
|
60
|
+
decl_symbol=None,
|
|
61
|
+
decl_timeframe=None,
|
|
62
|
+
strategy_result=None,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
brue/__main__.py
ADDED
brue/api.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""The Python-native front door for the brue package.
|
|
2
|
+
|
|
3
|
+
The engine internals speak BarData lists and ExecOutput (ported 1:1 from
|
|
4
|
+
the reference engine; conformance-locked, do not touch). This module is the
|
|
5
|
+
convenience layer a Python user actually calls:
|
|
6
|
+
|
|
7
|
+
import brue
|
|
8
|
+
|
|
9
|
+
result = brue.backtest(script, df) # df: pandas or rows
|
|
10
|
+
result.stats["netProfit"], result.trades, result.equity_curve
|
|
11
|
+
|
|
12
|
+
Accepted data shapes: a pandas DataFrame (any case-insensitive subset of
|
|
13
|
+
time/open/high/low/close/volume columns, time in epoch s, epoch ms, or
|
|
14
|
+
datetimes), a list of dicts with those keys, a list of [t,o,h,l,c,v] rows,
|
|
15
|
+
or a path to a csv. pandas is NOT a dependency: it is duck-typed, so the
|
|
16
|
+
package installs clean without it.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BrueScriptError(Exception):
|
|
24
|
+
"""The script failed to tokenize/parse/run; message is user-facing."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, errors):
|
|
27
|
+
self.errors = errors
|
|
28
|
+
first = errors[0] if errors else {}
|
|
29
|
+
super().__init__(first.get("message", "script error"))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BacktestResult:
|
|
33
|
+
"""Friendly view over the engine's canonical output."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, raw: dict, bar_times=None):
|
|
36
|
+
self.raw = raw
|
|
37
|
+
sr = raw.get("strategyResult") or {}
|
|
38
|
+
self.stats = {k: v for k, v in sr.items()
|
|
39
|
+
if k not in ("trades", "equityCurve")}
|
|
40
|
+
self.trades = sr.get("trades", [])
|
|
41
|
+
# The engine emits one equity value per bar, no timestamps; the
|
|
42
|
+
# caller's bar times restore the x axis.
|
|
43
|
+
self.equity_curve = sr.get("equityCurve", [])
|
|
44
|
+
self.bar_times = bar_times or []
|
|
45
|
+
|
|
46
|
+
def trades_frame(self):
|
|
47
|
+
"""The trade ledger as a pandas DataFrame (pandas required here)."""
|
|
48
|
+
import pandas as pd
|
|
49
|
+
return pd.DataFrame(self.trades)
|
|
50
|
+
|
|
51
|
+
def equity_frame(self):
|
|
52
|
+
import pandas as pd
|
|
53
|
+
df = pd.DataFrame({"equity": self.equity_curve})
|
|
54
|
+
if len(self.bar_times) == len(df):
|
|
55
|
+
df.insert(0, "time", pd.to_datetime(self.bar_times, unit="ms"))
|
|
56
|
+
return df
|
|
57
|
+
|
|
58
|
+
def __repr__(self):
|
|
59
|
+
s = self.stats
|
|
60
|
+
return (f"BacktestResult(trades={s.get('totalTrades')}, "
|
|
61
|
+
f"netProfit={s.get('netProfit')}, "
|
|
62
|
+
f"winRate={s.get('winRate')}, "
|
|
63
|
+
f"profitFactor={s.get('profitFactor')})")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _to_ms(t) -> float:
|
|
67
|
+
"""Epoch seconds, epoch ms, or datetime-like -> epoch ms."""
|
|
68
|
+
if hasattr(t, "timestamp"):
|
|
69
|
+
return float(t.timestamp() * 1000.0)
|
|
70
|
+
x = float(t)
|
|
71
|
+
# Heuristic shared with the userdata importer: values before the year
|
|
72
|
+
# 5138 in seconds are seconds; anything bigger is already ms.
|
|
73
|
+
return x * 1000.0 if x < 1e11 else x
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _bars_from_any(data):
|
|
77
|
+
from .runtime import BarData
|
|
78
|
+
|
|
79
|
+
if isinstance(data, str):
|
|
80
|
+
from . import parse_bars_csv
|
|
81
|
+
with open(data, encoding="utf-8") as f:
|
|
82
|
+
return parse_bars_csv(f.read())
|
|
83
|
+
|
|
84
|
+
# pandas DataFrame, duck-typed so pandas stays optional.
|
|
85
|
+
if hasattr(data, "columns") and hasattr(data, "itertuples"):
|
|
86
|
+
cols = {str(c).strip().lower(): c for c in data.columns}
|
|
87
|
+
tcol = next((cols[k] for k in ("time", "ts", "timestamp", "date",
|
|
88
|
+
"datetime") if k in cols), None)
|
|
89
|
+
need = [k for k in ("open", "high", "low", "close") if k not in cols]
|
|
90
|
+
if tcol is None or need:
|
|
91
|
+
missing = (["time"] if tcol is None else []) + need
|
|
92
|
+
raise ValueError(f"data is missing columns: {', '.join(missing)}")
|
|
93
|
+
# Follow the index when there is no time column? No: explicit only,
|
|
94
|
+
# a wrong guess silently shifts every fill.
|
|
95
|
+
out = []
|
|
96
|
+
vol = cols.get("volume")
|
|
97
|
+
for row in data.itertuples(index=False):
|
|
98
|
+
d = dict(zip(data.columns, row))
|
|
99
|
+
out.append(BarData(
|
|
100
|
+
time=_to_ms(d[tcol]),
|
|
101
|
+
open=float(d[cols["open"]]), high=float(d[cols["high"]]),
|
|
102
|
+
low=float(d[cols["low"]]), close=float(d[cols["close"]]),
|
|
103
|
+
volume=float(d[vol]) if vol is not None else 0.0))
|
|
104
|
+
return out
|
|
105
|
+
|
|
106
|
+
if isinstance(data, (list, tuple)) and data:
|
|
107
|
+
first = data[0]
|
|
108
|
+
if isinstance(first, dict):
|
|
109
|
+
return [BarData(time=_to_ms(r.get("time", r.get("ts", r.get("timestamp")))),
|
|
110
|
+
open=float(r["open"]), high=float(r["high"]),
|
|
111
|
+
low=float(r["low"]), close=float(r["close"]),
|
|
112
|
+
volume=float(r.get("volume", 0.0)))
|
|
113
|
+
for r in data]
|
|
114
|
+
if isinstance(first, (list, tuple)) and len(first) >= 5:
|
|
115
|
+
return [BarData(time=_to_ms(r[0]), open=float(r[1]), high=float(r[2]),
|
|
116
|
+
low=float(r[3]), close=float(r[4]),
|
|
117
|
+
volume=float(r[5]) if len(r) > 5 else 0.0)
|
|
118
|
+
for r in data]
|
|
119
|
+
raise ValueError("unsupported data shape: pass a DataFrame, list of "
|
|
120
|
+
"dicts, list of [t,o,h,l,c,v] rows, or a csv path")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def indicator_series(script: str, data, inputs: dict | None = None) -> dict:
|
|
124
|
+
"""
|
|
125
|
+
Run a Brue script as a chart INDICATOR: every plot() call becomes a
|
|
126
|
+
named series aligned to the bars. This is how hosts (the LSE Terminal,
|
|
127
|
+
notebooks) draw Brue-written indicators on live charts.
|
|
128
|
+
|
|
129
|
+
Returns {"plots": {name: [float per bar]}, "meta": {name: kwargs},
|
|
130
|
+
"overlay": bool, "inputs": [declared input() specs],
|
|
131
|
+
"times": [epoch ms per bar]}.
|
|
132
|
+
Raises BrueScriptError on script errors.
|
|
133
|
+
"""
|
|
134
|
+
from . import run_script_full
|
|
135
|
+
from .runtime import ExternalData, RunOptions
|
|
136
|
+
from .value import Value
|
|
137
|
+
|
|
138
|
+
bars = _bars_from_any(data)
|
|
139
|
+
options = RunOptions()
|
|
140
|
+
options.collect_plots = True
|
|
141
|
+
overrides = {}
|
|
142
|
+
for k, v in (inputs or {}).items():
|
|
143
|
+
overrides[str(k)] = (Value.Bool(v) if isinstance(v, bool)
|
|
144
|
+
else Value.Num(v) if isinstance(v, (int, float))
|
|
145
|
+
else Value.Str(str(v)))
|
|
146
|
+
out = run_script_full(script, bars, ExternalData(), overrides, options)
|
|
147
|
+
raw = json.loads(out.to_json())
|
|
148
|
+
if not raw.get("success"):
|
|
149
|
+
raise BrueScriptError(raw.get("errors", []))
|
|
150
|
+
n = len(bars)
|
|
151
|
+
plots = {}
|
|
152
|
+
for name, points in getattr(out, "plots", {}).items():
|
|
153
|
+
col = [float("nan")] * n
|
|
154
|
+
for bar_i, v in points:
|
|
155
|
+
if 0 <= bar_i < n:
|
|
156
|
+
col[bar_i] = v
|
|
157
|
+
plots[name] = col
|
|
158
|
+
return {"plots": plots,
|
|
159
|
+
"meta": getattr(out, "plot_meta", {}),
|
|
160
|
+
"overlay": bool((raw.get("declaration") or {}).get("overlay", True)),
|
|
161
|
+
"inputs": raw.get("inputs", []),
|
|
162
|
+
"times": [b.time for b in bars]}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def backtest(script: str, data, extended_stats: bool = False,
|
|
166
|
+
trade_from=None, trade_to=None) -> BacktestResult:
|
|
167
|
+
"""Run a Brue strategy over the given bars, entirely in-process."""
|
|
168
|
+
from . import run_script_full
|
|
169
|
+
from .runtime import ExternalData, RunOptions
|
|
170
|
+
|
|
171
|
+
bars = _bars_from_any(data)
|
|
172
|
+
options = RunOptions()
|
|
173
|
+
options.extended_stats = bool(extended_stats)
|
|
174
|
+
if trade_from is not None:
|
|
175
|
+
options.trade_from = _to_ms(trade_from)
|
|
176
|
+
if trade_to is not None:
|
|
177
|
+
options.trade_to = _to_ms(trade_to)
|
|
178
|
+
out = run_script_full(script, bars, ExternalData(), {}, options)
|
|
179
|
+
raw = json.loads(out.to_json())
|
|
180
|
+
if not raw.get("success"):
|
|
181
|
+
raise BrueScriptError(raw.get("errors", []))
|
|
182
|
+
return BacktestResult(raw, bar_times=[b.time for b in bars])
|
brue/ast.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# AST node types, mirroring src/brue/ast.ts one-for-one (ported from
|
|
2
|
+
# brue-rust src/ast.rs). The kind_str names must match the TS `kind` strings
|
|
3
|
+
# exactly: the runtime's expression fingerprint falls back to
|
|
4
|
+
# "{kind}_{line}_{col}" and those keys decide series-cache identity.
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Loc:
|
|
11
|
+
line: int
|
|
12
|
+
col: int
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Expr:
|
|
16
|
+
"""Base class for all expression nodes (the Rust Expr enum)."""
|
|
17
|
+
|
|
18
|
+
# The TS `kind` string (used by the fingerprint fallback). Each subclass
|
|
19
|
+
# sets _KIND; kind_str() keeps the Rust method name.
|
|
20
|
+
_KIND = ""
|
|
21
|
+
|
|
22
|
+
def kind_str(self) -> str:
|
|
23
|
+
return self._KIND
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class KeywordArg:
|
|
28
|
+
name: str
|
|
29
|
+
value: Expr
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class NumberLiteral(Expr):
|
|
34
|
+
value: float
|
|
35
|
+
loc: Loc
|
|
36
|
+
_KIND = "NumberLiteral"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class StringLiteral(Expr):
|
|
41
|
+
value: str
|
|
42
|
+
loc: Loc
|
|
43
|
+
_KIND = "StringLiteral"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class BoolLiteral(Expr):
|
|
48
|
+
value: bool
|
|
49
|
+
loc: Loc
|
|
50
|
+
_KIND = "BoolLiteral"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class NaLiteral(Expr):
|
|
55
|
+
loc: Loc
|
|
56
|
+
_KIND = "NaLiteral"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class ColorLiteral(Expr):
|
|
61
|
+
r: float
|
|
62
|
+
g: float
|
|
63
|
+
b: float
|
|
64
|
+
a: float
|
|
65
|
+
loc: Loc
|
|
66
|
+
_KIND = "ColorLiteral"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class Identifier(Expr):
|
|
71
|
+
name: str
|
|
72
|
+
loc: Loc
|
|
73
|
+
_KIND = "Identifier"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class Binary(Expr):
|
|
78
|
+
op: str
|
|
79
|
+
left: Expr
|
|
80
|
+
right: Expr
|
|
81
|
+
loc: Loc
|
|
82
|
+
_KIND = "BinaryExpr"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class Unary(Expr):
|
|
87
|
+
op: str
|
|
88
|
+
operand: Expr
|
|
89
|
+
loc: Loc
|
|
90
|
+
_KIND = "UnaryExpr"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class Call(Expr):
|
|
95
|
+
callee: str
|
|
96
|
+
args: List[Expr]
|
|
97
|
+
kwargs: List[KeywordArg]
|
|
98
|
+
loc: Loc
|
|
99
|
+
_KIND = "CallExpr"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class History(Expr):
|
|
104
|
+
source: Expr
|
|
105
|
+
offset: Expr
|
|
106
|
+
loc: Loc
|
|
107
|
+
_KIND = "HistoryExpr"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class Ternary(Expr):
|
|
112
|
+
condition: Expr
|
|
113
|
+
if_true: Expr
|
|
114
|
+
if_false: Expr
|
|
115
|
+
loc: Loc
|
|
116
|
+
_KIND = "TernaryExpr"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class ArrayLiteral(Expr):
|
|
121
|
+
elements: List[Expr]
|
|
122
|
+
loc: Loc
|
|
123
|
+
_KIND = "ArrayLiteral"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class MapLiteral(Expr):
|
|
128
|
+
entries: List[Tuple[Expr, Expr]]
|
|
129
|
+
loc: Loc
|
|
130
|
+
_KIND = "MapLiteral"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class Member(Expr):
|
|
135
|
+
object: Expr
|
|
136
|
+
member: str
|
|
137
|
+
loc: Loc
|
|
138
|
+
_KIND = "MemberExpr"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class MethodCall(Expr):
|
|
143
|
+
object: Expr
|
|
144
|
+
method: str
|
|
145
|
+
args: List[Expr]
|
|
146
|
+
loc: Loc
|
|
147
|
+
_KIND = "MethodCallExpr"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class FmtPart:
|
|
151
|
+
"""The Rust FmtPart enum; variants are the nested classes
|
|
152
|
+
FmtPart.Text / FmtPart.Expr so call sites keep the Rust paths."""
|
|
153
|
+
|
|
154
|
+
@dataclass
|
|
155
|
+
class Text:
|
|
156
|
+
value: str
|
|
157
|
+
|
|
158
|
+
@dataclass
|
|
159
|
+
class Expr:
|
|
160
|
+
expr: "Expr"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass
|
|
164
|
+
class FormatString(Expr):
|
|
165
|
+
parts: List[object] # FmtPart.Text | FmtPart.Expr
|
|
166
|
+
specs: List[Optional[str]]
|
|
167
|
+
loc: Loc
|
|
168
|
+
_KIND = "FormatStringExpr"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass
|
|
172
|
+
class FunctionParam:
|
|
173
|
+
name: str
|
|
174
|
+
default_value: Optional[Expr]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class TypeField:
|
|
179
|
+
name: str
|
|
180
|
+
field_type: str
|
|
181
|
+
default_value: Optional[Expr]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dataclass
|
|
185
|
+
class DeclarationStmt:
|
|
186
|
+
args: List[Expr]
|
|
187
|
+
kwargs: List[KeywordArg]
|
|
188
|
+
symbol: Optional[str]
|
|
189
|
+
timeframe: Optional[str]
|
|
190
|
+
execution_mode: str # "backtest" | "execute"
|
|
191
|
+
broker: Optional[str]
|
|
192
|
+
account: Optional[str]
|
|
193
|
+
password: Optional[str]
|
|
194
|
+
loc: Loc
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class UseStmt:
|
|
199
|
+
canonical: str
|
|
200
|
+
timeframe: Optional[str]
|
|
201
|
+
alias: str
|
|
202
|
+
alias_explicit: bool
|
|
203
|
+
is_dataset: bool
|
|
204
|
+
loc: Loc
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class Stmt:
|
|
208
|
+
"""Base class for all statement nodes (the Rust Stmt enum)."""
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass
|
|
212
|
+
class Assign(Stmt):
|
|
213
|
+
target: str
|
|
214
|
+
op: str
|
|
215
|
+
value: Expr
|
|
216
|
+
loc: Loc
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class Persist(Stmt):
|
|
221
|
+
target: str
|
|
222
|
+
value: Expr
|
|
223
|
+
loc: Loc
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@dataclass
|
|
227
|
+
class Const(Stmt):
|
|
228
|
+
target: str
|
|
229
|
+
value: Expr
|
|
230
|
+
loc: Loc
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class If(Stmt):
|
|
235
|
+
condition: Expr
|
|
236
|
+
body: List[Stmt]
|
|
237
|
+
elifs: List[Tuple[Expr, List[Stmt]]]
|
|
238
|
+
else_body: List[Stmt]
|
|
239
|
+
loc: Loc
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@dataclass
|
|
243
|
+
class For(Stmt):
|
|
244
|
+
variable: str
|
|
245
|
+
iterable: Expr
|
|
246
|
+
body: List[Stmt]
|
|
247
|
+
loc: Loc
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@dataclass
|
|
251
|
+
class While(Stmt):
|
|
252
|
+
condition: Expr
|
|
253
|
+
body: List[Stmt]
|
|
254
|
+
loc: Loc
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass
|
|
258
|
+
class Break(Stmt):
|
|
259
|
+
loc: Loc
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass
|
|
263
|
+
class Continue(Stmt):
|
|
264
|
+
loc: Loc
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@dataclass
|
|
268
|
+
class FunctionDef(Stmt):
|
|
269
|
+
name: str
|
|
270
|
+
params: List[FunctionParam]
|
|
271
|
+
body: List[Stmt]
|
|
272
|
+
loc: Loc
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@dataclass
|
|
276
|
+
class Return(Stmt):
|
|
277
|
+
value: Optional[Expr]
|
|
278
|
+
loc: Loc
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@dataclass
|
|
282
|
+
class ExprStmt(Stmt):
|
|
283
|
+
expr: Expr
|
|
284
|
+
loc: Loc
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@dataclass
|
|
288
|
+
class MemberAssign(Stmt):
|
|
289
|
+
target: Expr
|
|
290
|
+
value: Expr
|
|
291
|
+
loc: Loc
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dataclass
|
|
295
|
+
class MultiAssign(Stmt):
|
|
296
|
+
targets: List[str]
|
|
297
|
+
value: Expr
|
|
298
|
+
loc: Loc
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@dataclass
|
|
302
|
+
class TypeDef(Stmt):
|
|
303
|
+
name: str
|
|
304
|
+
fields: List[TypeField]
|
|
305
|
+
loc: Loc
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@dataclass
|
|
309
|
+
class EnumDef(Stmt):
|
|
310
|
+
name: str
|
|
311
|
+
members: List[Tuple[str, Optional[str]]]
|
|
312
|
+
loc: Loc
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@dataclass
|
|
316
|
+
class Program:
|
|
317
|
+
declaration: DeclarationStmt
|
|
318
|
+
uses: List[UseStmt]
|
|
319
|
+
body: List[Stmt]
|