1use crate::error::{Result, ShapeError};
4use crate::parser::pair_location;
5use crate::parser::string_literals::parse_string_literal;
6use pest::iterators::Pair;
7
8use super::Rule;
9use crate::ast::{TimeReference, TimeUnit, TimeWindow};
10
11pub fn parse_time_window(pair: Pair<Rule>) -> Result<TimeWindow> {
15 let pair_loc = pair_location(&pair);
16 let inner = pair
17 .into_inner()
18 .next()
19 .ok_or_else(|| ShapeError::ParseError {
20 message: "expected time window specification".to_string(),
21 location: Some(
22 pair_loc
23 .clone()
24 .with_hint("use 'last N bars', 'last N days', 'between start and end', etc."),
25 ),
26 })?;
27
28 match inner.as_rule() {
29 Rule::last_window => parse_last_window(inner),
30 Rule::between_window => parse_between_window(inner),
31 Rule::window_range => parse_window_range(inner),
32 Rule::session_window => parse_session_window(inner),
33 _ => Err(ShapeError::ParseError {
34 message: format!("unexpected time window type: {:?}", inner.as_rule()),
35 location: Some(pair_location(&inner)),
36 }),
37 }
38}
39
40fn parse_last_window(pair: Pair<Rule>) -> Result<TimeWindow> {
41 let pair_loc = pair_location(&pair);
42 let mut inner = pair.into_inner();
43
44 let amount_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
45 message: "expected amount in 'last' window".to_string(),
46 location: Some(
47 pair_loc
48 .clone()
49 .with_hint("specify an amount, e.g., 'last 100 bars'"),
50 ),
51 })?;
52
53 let amount: i32 = amount_pair
54 .as_str()
55 .parse()
56 .map_err(|e| ShapeError::ParseError {
57 message: format!("invalid number in time window: {}", e),
58 location: Some(
59 pair_location(&amount_pair).with_hint("amount must be a positive integer"),
60 ),
61 })?;
62
63 let unit_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
64 message: "expected time unit after amount".to_string(),
65 location: Some(pair_loc.with_hint("add a time unit like 'bars', 'days', 'hours', 'weeks'")),
66 })?;
67 let unit = parse_time_unit(unit_pair)?;
68
69 Ok(TimeWindow::Last { amount, unit })
70}
71
72fn parse_between_window(pair: Pair<Rule>) -> Result<TimeWindow> {
73 let pair_loc = pair_location(&pair);
74 let mut inner = pair.into_inner();
75
76 let start_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
77 message: "expected start time in 'between' window".to_string(),
78 location: Some(pair_loc.clone().with_hint(
79 "use 'between @yesterday and @today' or 'between \"2023-01-01\" and \"2023-12-31\"'",
80 )),
81 })?;
82 let start = parse_time_ref(start_pair)?;
83
84 let end_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
85 message: "expected end time in 'between' window".to_string(),
86 location: Some(pair_loc.with_hint("add 'and <end_time>' after start time")),
87 })?;
88 let end = parse_time_ref(end_pair)?;
89
90 Ok(TimeWindow::Between { start, end })
91}
92
93fn parse_window_range(pair: Pair<Rule>) -> Result<TimeWindow> {
94 let pair_loc = pair_location(&pair);
95 let mut inner = pair.into_inner();
96
97 inner.next();
99
100 let args = inner.next().ok_or_else(|| ShapeError::ParseError {
101 message: "expected window arguments".to_string(),
102 location: Some(
103 pair_loc
104 .clone()
105 .with_hint("use 'window(start, end)' with row indices or time references"),
106 ),
107 })?;
108
109 let args_loc = pair_location(&args);
110 let mut args_inner = args.into_inner();
111
112 let first = args_inner.next().ok_or_else(|| ShapeError::ParseError {
113 message: "expected first window argument".to_string(),
114 location: Some(args_loc.clone()),
115 })?;
116
117 match first.as_rule() {
118 Rule::number => {
119 let start: i32 = first.as_str().parse().map_err(|e| ShapeError::ParseError {
120 message: format!("invalid start index in window: {}", e),
121 location: Some(pair_location(&first)),
122 })?;
123
124 let end = args_inner
125 .next()
126 .map(|p| {
127 p.as_str()
128 .parse::<i32>()
129 .map_err(|e| ShapeError::ParseError {
130 message: format!("invalid end index in window: {}", e),
131 location: Some(pair_location(&p)),
132 })
133 })
134 .transpose()?;
135
136 Ok(TimeWindow::Window { start, end })
137 }
138 Rule::timeframe => Err(ShapeError::ParseError {
139 message: "timeframe windows not yet implemented".to_string(),
140 location: Some(
141 pair_location(&first).with_hint("use row indices or time references instead"),
142 ),
143 }),
144 Rule::time_ref => {
145 let start = parse_time_ref(first)?;
146 let end_pair = args_inner.next().ok_or_else(|| ShapeError::ParseError {
147 message: "expected end time reference in window".to_string(),
148 location: Some(
149 args_loc.with_hint("provide two time references: window(@start, @end)"),
150 ),
151 })?;
152 let end = parse_time_ref(end_pair)?;
153 Ok(TimeWindow::Between { start, end })
154 }
155 _ => Err(ShapeError::ParseError {
156 message: format!("unexpected window argument type: {:?}", first.as_rule()),
157 location: Some(pair_location(&first)),
158 }),
159 }
160}
161
162fn parse_session_window(pair: Pair<Rule>) -> Result<TimeWindow> {
163 let pair_loc = pair_location(&pair);
164 let mut inner = pair.into_inner();
165
166 inner.next();
168
169 let start_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
170 message: "expected start time string in session window".to_string(),
171 location: Some(
172 pair_loc
173 .clone()
174 .with_hint("use 'session \"09:30\" to \"16:00\"'"),
175 ),
176 })?;
177 let start = parse_string_literal(start_pair.as_str())?;
178
179 let end_pair = inner.next().ok_or_else(|| ShapeError::ParseError {
180 message: "expected end time string in session window".to_string(),
181 location: Some(pair_loc.with_hint("add 'to \"HH:MM\"' after start time")),
182 })?;
183 let end = parse_string_literal(end_pair.as_str())?;
184
185 Ok(TimeWindow::Session { start, end })
186}
187
188pub fn parse_time_unit(pair: Pair<Rule>) -> Result<TimeUnit> {
190 let unit_str = pair.as_str();
191
192 match unit_str {
193 "sample" | "samples" | "record" | "records" => Ok(TimeUnit::Samples),
194 "minute" | "minutes" => Ok(TimeUnit::Minutes),
195 "hour" | "hours" => Ok(TimeUnit::Hours),
196 "day" | "days" => Ok(TimeUnit::Days),
197 "week" | "weeks" => Ok(TimeUnit::Weeks),
198 "month" | "months" => Ok(TimeUnit::Months),
199 _ => {
200 Err(ShapeError::ParseError {
201 message: format!("unknown time unit: '{}'", unit_str),
202 location: Some(pair_location(&pair).with_hint(
203 "valid units: samples, records, minutes, hours, days, weeks, months",
204 )),
205 })
206 }
207 }
208}
209
210fn parse_time_ref(pair: Pair<Rule>) -> Result<TimeReference> {
211 crate::parser::expressions::temporal::parse_time_ref(pair)
212}