1mod carry;
4mod place;
5mod stmt;
6mod walk;
7
8use std::cell::{Cell, RefCell};
9use std::fmt::{self, Write as _};
10use std::mem;
11use std::rc::Rc;
12
13use nifty_check::hir::{
14 BinaryOp, Dialog, DialogKind, Download, DownloadCommand, DownloadField, Expr, ExprKind, FnId,
15 LocalId, Program, StateSet,
16};
17use nifty_download::{DownloadId, Report, Written, follow_id};
18use nifty_runtime::{Failure, FileDialog, MAX_CALL_DEPTH, Picked, WindowRequest};
19use nifty_syntax::Span;
20
21use crate::value::Value;
22pub(crate) use place::Location;
23pub(crate) use stmt::Flow;
24pub(crate) use walk::rounds;
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct RuntimeError {
29 pub message: String,
30 pub span: Span,
31}
32
33impl RuntimeError {
34 fn new(failure: &Failure, span: Span) -> Self {
35 Self {
36 message: failure.to_string(),
37 span,
38 }
39 }
40}
41
42impl fmt::Display for RuntimeError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 f.write_str(&self.message)
45 }
46}
47
48pub(crate) type Result<T> = std::result::Result<T, RuntimeError>;
49
50#[derive(Debug)]
52pub(crate) struct Targets {
53 pub id: DownloadId,
54 pub bindings: Vec<(DownloadField, Location)>,
55 pub span: Span,
57}
58
59pub(crate) type Request = nifty_download::Request<Targets>;
60
61#[derive(Debug)]
63pub(crate) struct Answer {
64 pub place: Location,
65 pub kind: DialogKind,
66 pub span: Span,
68}
69
70#[derive(Clone, Debug)]
73pub(crate) enum Slot {
74 Empty,
75 Value(Value),
76 Alias(Location),
77}
78
79pub(crate) type Frame = Vec<Slot>;
80
81pub(crate) fn new_frame(locals: usize) -> Frame {
82 vec![Slot::Empty; locals]
83}
84
85pub(crate) struct Machine {
86 program: Rc<Program>,
87 states: RefCell<Vec<Value>>,
88 changed: RefCell<StateSet>,
90 downloads: RefCell<Vec<Request>>,
92 download_commands: RefCell<Vec<(DownloadCommand, String)>>,
94 dialogs: RefCell<Vec<(FileDialog, Answer)>>,
96 window_requests: RefCell<Vec<WindowRequest>>,
98 call_depth: Cell<usize>,
99}
100
101impl Machine {
102 pub(crate) fn new(program: Rc<Program>, previous: Option<&Machine>) -> Result<Self> {
105 let machine = Self {
106 program,
107 states: RefCell::new(Vec::new()),
108 changed: RefCell::new(StateSet::default()),
109 downloads: RefCell::new(Vec::new()),
110 download_commands: RefCell::new(Vec::new()),
111 dialogs: RefCell::new(Vec::new()),
112 window_requests: RefCell::new(Vec::new()),
113 call_depth: Cell::new(0),
114 };
115 let program = Rc::clone(&machine.program);
116 for state in &program.states {
117 let kept = previous.and_then(|previous| carry::kept_value(previous, state, &program));
118 let value = match kept {
119 Some(value) => value,
120 None => machine.eval(&state.init, &mut Frame::new())?,
121 };
122 machine.states.borrow_mut().push(value);
123 }
124 Ok(machine)
125 }
126
127 pub(crate) fn program(&self) -> &Rc<Program> {
128 &self.program
129 }
130
131 #[cfg(test)]
132 pub(crate) fn state(&self, state: nifty_check::hir::StateId) -> Value {
133 self.states.borrow()[state.0].clone()
134 }
135
136 pub(crate) fn take_changed(&self) -> StateSet {
138 mem::take(&mut self.changed.borrow_mut())
139 }
140
141 pub(crate) fn take_downloads(&self) -> Vec<Request> {
143 mem::take(&mut self.downloads.borrow_mut())
144 }
145
146 pub(crate) fn take_download_commands(&self) -> Vec<(DownloadCommand, String)> {
148 mem::take(&mut self.download_commands.borrow_mut())
149 }
150
151 pub(crate) fn take_dialogs(&self) -> Vec<(FileDialog, Answer)> {
153 mem::take(&mut self.dialogs.borrow_mut())
154 }
155
156 pub(crate) fn take_window_requests(&self) -> Vec<WindowRequest> {
158 mem::take(&mut self.window_requests.borrow_mut())
159 }
160
161 pub(crate) fn answer_dialog(&self, answer: &Answer, picked: Picked) -> Result<()> {
164 let paths = match picked {
165 Picked::Chosen(paths) => paths,
166 Picked::Cancelled => return Ok(()),
167 Picked::Failed(reason) => {
168 return Err(RuntimeError::new(&Failure::Dialog(reason), answer.span));
169 }
170 };
171 let value = if answer.kind.picks_many() {
172 Value::strings(paths)
173 } else {
174 let path = paths
175 .into_iter()
176 .next()
177 .expect("backends answer a one-path dialog with exactly one path");
178 Value::String(Rc::from(path))
179 };
180 self.write_state(&answer.place, value, answer.span)
181 }
182
183 fn ask_dialog(&self, dialog: &Dialog, frame: &mut Frame, span: Span) -> Result<()> {
185 let title = self.eval(&dialog.title, frame)?.as_str().to_owned();
186 let types = self.eval(&dialog.types, frame)?;
187 let folder = self.eval(&dialog.folder, frame)?;
188 let name = self.eval(&dialog.name, frame)?.as_str().to_owned();
189 let place = self.locate(&dialog.answer, frame)?;
190 if !place.is_in_state() {
191 return Err(RuntimeError::new(&Failure::DialogIntoVariable, span));
192 }
193 let file_dialog =
194 FileDialog::new(dialog.kind, title, &types.as_strs(), folder.as_str(), name)
195 .map_err(|error| RuntimeError::new(&Failure::Dialog(error.to_string()), span))?;
196 self.dialogs.borrow_mut().push((
197 file_dialog,
198 Answer {
199 place,
200 kind: dialog.kind,
201 span,
202 },
203 ));
204 Ok(())
205 }
206
207 pub(crate) fn report_download(&self, targets: &Targets, report: &Report) -> Result<()> {
209 for (field, written) in report.writes() {
210 let bound = targets.bindings.iter().find(|(bound, _)| *bound == field);
211 if let Some((_, location)) = bound {
212 self.write_state(location, written_value(written), targets.span)?;
213 }
214 }
215 Ok(())
216 }
217
218 pub(crate) fn follow_download(&self, targets: &mut Targets) -> bool {
221 follow_id(self, &mut targets.bindings, &targets.id.to_string())
222 }
223
224 fn request_download(&self, download: &Download, frame: &mut Frame, span: Span) -> Result<()> {
227 let url = self.eval(&download.url, frame)?.as_str().to_owned();
228 let folder = self.eval(&download.folder, frame)?.as_str().to_owned();
229 let name = self.eval(&download.name, frame)?.as_str().to_owned();
230 let connections = self.eval(&download.connections, frame)?.as_int();
231 let headers = self.eval(&download.headers, frame)?;
232 let headers = headers.as_strs().into_iter().map(str::to_owned).collect();
233 let retries = self.eval(&download.retries, frame)?.as_int();
234 let mut bindings = Vec::with_capacity(download.bindings.len());
235 for (field, place) in &download.bindings {
236 let location = self.locate(place, frame)?;
237 if !location.is_in_state() {
238 return Err(RuntimeError::new(&Failure::DownloadIntoVariable, span));
239 }
240 bindings.push((*field, location));
241 }
242 let id = DownloadId::next();
243 let targets = Targets { id, bindings, span };
244 self.report_download(&targets, &Report::Started(id))?;
245 self.downloads.borrow_mut().push(Request {
246 id,
247 url,
248 folder,
249 name,
250 connections,
251 headers,
252 retries,
253 targets,
254 });
255 Ok(())
256 }
257
258 pub(crate) fn eval(&self, expr: &Expr, frame: &mut Frame) -> Result<Value> {
259 let span = expr.span;
260 Ok(match &expr.kind {
261 ExprKind::Int(value) => Value::Int(*value),
262 ExprKind::Float(value) => Value::Float(*value),
263 ExprKind::Bool(value) => Value::Bool(*value),
264 ExprKind::String(text) => Value::String(Rc::clone(text)),
265 ExprKind::Interpolate(segments) => {
266 let mut text = String::new();
267 for segment in segments {
268 match segment {
269 nifty_check::hir::Segment::Text(part) => text.push_str(part),
270 nifty_check::hir::Segment::Value(value) => {
271 let value = self.eval(value, frame)?;
272 write!(text, "{value}").expect("writing to a String cannot fail");
273 }
274 }
275 }
276 Value::String(Rc::from(text))
277 }
278 ExprKind::State(state) => self.states.borrow()[state.0].clone(),
279 ExprKind::Local(local) => self.read_local(*local, frame, span)?,
280 ExprKind::List(items) => Value::List(Rc::new(self.eval_all(items, frame)?)),
281 ExprKind::Construct(_, fields) => Value::Struct(Rc::new(self.eval_all(fields, frame)?)),
282 ExprKind::Field(base, index) => match self.eval(base, frame)? {
283 Value::Struct(fields) => fields[*index].clone(),
284 other => panic!("checked code read a field of {other:?}"),
285 },
286 ExprKind::Index(base, index) => {
287 let list = self.eval(base, frame)?;
288 let position = position(self.eval(index, frame)?.as_int(), span)?;
289 item(&list, position, span)?.clone()
290 }
291 ExprKind::Not(operand) => Value::Bool(!self.eval(operand, frame)?.as_bool()),
292 ExprKind::Negate(operand) => match self.eval(operand, frame)? {
293 Value::Int(value) => Value::Int(
294 value
295 .checked_neg()
296 .ok_or_else(|| RuntimeError::new(&Failure::TooLarge, span))?,
297 ),
298 Value::Float(value) => Value::Float(-value),
299 other => panic!("checked code negated {other:?}"),
300 },
301 ExprKind::Binary(op, lhs, rhs) => self.binary_expr(*op, lhs, rhs, frame, span)?,
302 ExprKind::Call(function, args) => self.call(*function, args, frame, span)?,
303 ExprKind::Len(value) => Value::Int(length(&self.eval(value, frame)?, span)?),
304 ExprKind::IsEmpty(value) => Value::Bool(length(&self.eval(value, frame)?, span)? == 0),
305 ExprKind::Push(place, value) => {
306 let value = self.eval(value, frame)?;
307 let location = self.locate(place, frame)?;
308 self.modify(&location, frame, span, |list| {
309 list_items(list).push(value);
310 Ok(())
311 })?;
312 Value::Void
313 }
314 ExprKind::Remove(place, index) => {
315 let position = position(self.eval(index, frame)?.as_int(), span)?;
316 let location = self.locate(place, frame)?;
317 self.modify(&location, frame, span, |list| {
318 let items = list_items(list);
319 if position >= items.len() {
320 return Err(out_of_range(position, items.len(), span));
321 }
322 items.remove(position);
323 Ok(())
324 })?;
325 Value::Void
326 }
327 ExprKind::Download(download) => {
328 self.request_download(download, frame, span)?;
329 Value::Void
330 }
331 ExprKind::DownloadCommand(command, id) => {
332 let id = self.eval(id, frame)?.as_str().to_owned();
333 self.download_commands.borrow_mut().push((*command, id));
334 Value::Void
335 }
336 ExprKind::FormatBytes(bytes) => Value::String(Rc::from(nifty_download::format_bytes(
337 self.eval(bytes, frame)?.as_int(),
338 ))),
339 ExprKind::Dialog(dialog) => {
340 self.ask_dialog(dialog, frame, span)?;
341 Value::Void
342 }
343 ExprKind::Window(action, window) => {
344 self.window_requests.borrow_mut().push(WindowRequest {
345 action: *action,
346 window: window.0,
347 });
348 Value::Void
349 }
350 ExprKind::Error => panic!("programs with errors are never run"),
351 })
352 }
353
354 fn eval_all(&self, exprs: &[Expr], frame: &mut Frame) -> Result<Vec<Value>> {
355 exprs.iter().map(|expr| self.eval(expr, frame)).collect()
356 }
357
358 fn read_local(&self, local: LocalId, frame: &Frame, span: Span) -> Result<Value> {
359 match &frame[local.0] {
360 Slot::Value(value) => Ok(value.clone()),
361 Slot::Alias(location) => self.read(location, frame, span),
362 Slot::Empty => panic!("the checker only allows reading locals after they are set"),
363 }
364 }
365
366 fn binary_expr(
367 &self,
368 op: BinaryOp,
369 lhs: &Expr,
370 rhs: &Expr,
371 frame: &mut Frame,
372 span: Span,
373 ) -> Result<Value> {
374 match op {
375 BinaryOp::And => Ok(Value::Bool(
376 self.eval(lhs, frame)?.as_bool() && self.eval(rhs, frame)?.as_bool(),
377 )),
378 BinaryOp::Or => Ok(Value::Bool(
379 self.eval(lhs, frame)?.as_bool() || self.eval(rhs, frame)?.as_bool(),
380 )),
381 _ => {
382 let lhs = self.eval(lhs, frame)?;
383 let rhs = self.eval(rhs, frame)?;
384 binary(op, &lhs, &rhs, span)
385 }
386 }
387 }
388
389 pub(crate) fn call(
390 &self,
391 function: FnId,
392 args: &[Expr],
393 frame: &mut Frame,
394 span: Span,
395 ) -> Result<Value> {
396 let program = Rc::clone(&self.program);
397 let definition = &program.functions[function.0];
398 let mut callee = new_frame(definition.locals.len());
399 for (slot, arg) in callee.iter_mut().zip(args) {
400 *slot = Slot::Value(self.eval(arg, frame)?);
401 }
402 let depth = self.call_depth.get();
403 if depth >= MAX_CALL_DEPTH {
404 let function = definition.name.clone();
405 return Err(RuntimeError::new(&Failure::TooDeep { function }, span));
406 }
407 self.call_depth.set(depth + 1);
408 let flow = self.exec_block(&definition.body, &mut callee);
409 self.call_depth.set(depth);
410 Ok(match flow? {
411 Flow::Return(value) => value,
412 Flow::Normal => Value::Void,
413 })
414 }
415}
416
417pub(crate) fn binary(op: BinaryOp, lhs: &Value, rhs: &Value, span: Span) -> Result<Value> {
419 use Value::{Bool, Float, Int, String};
420 let too_large = || RuntimeError::new(&Failure::TooLarge, span);
421 Ok(match (op, lhs, rhs) {
422 (BinaryOp::Eq, _, _) => Bool(lhs == rhs),
423 (BinaryOp::NotEq, _, _) => Bool(lhs != rhs),
424 (BinaryOp::Add, Int(a), Int(b)) => Int(a.checked_add(*b).ok_or_else(too_large)?),
425 (BinaryOp::Sub, Int(a), Int(b)) => Int(a.checked_sub(*b).ok_or_else(too_large)?),
426 (BinaryOp::Mul, Int(a), Int(b)) => Int(a.checked_mul(*b).ok_or_else(too_large)?),
427 (BinaryOp::Div | BinaryOp::Rem, Int(_), Int(0)) => {
428 return Err(RuntimeError::new(&Failure::DivideByZero, span));
429 }
430 (BinaryOp::Div, Int(a), Int(b)) => Int(a.checked_div(*b).ok_or_else(too_large)?),
431 (BinaryOp::Rem, Int(a), Int(b)) => Int(a.checked_rem(*b).ok_or_else(too_large)?),
432 (BinaryOp::Add, Float(a), Float(b)) => Float(a + b),
433 (BinaryOp::Sub, Float(a), Float(b)) => Float(a - b),
434 (BinaryOp::Mul, Float(a), Float(b)) => Float(a * b),
435 (BinaryOp::Div, Float(a), Float(b)) => Float(a / b),
436 (BinaryOp::Rem, Float(a), Float(b)) => Float(a % b),
437 (BinaryOp::Add, String(a), String(b)) => String(Rc::from(format!("{a}{b}"))),
438 (BinaryOp::Lt | BinaryOp::LtEq | BinaryOp::Gt | BinaryOp::GtEq, _, _) => {
439 let ordering = match (lhs, rhs) {
440 (Int(a), Int(b)) => a.partial_cmp(b),
441 (Float(a), Float(b)) => a.partial_cmp(b),
442 (String(a), String(b)) => a.partial_cmp(b),
443 _ => panic!("checked code compared {lhs:?} and {rhs:?}"),
444 };
445 Bool(ordering.is_some_and(|ordering| match op {
446 BinaryOp::Lt => ordering.is_lt(),
447 BinaryOp::LtEq => ordering.is_le(),
448 BinaryOp::Gt => ordering.is_gt(),
449 _ => ordering.is_ge(),
450 }))
451 }
452 _ => panic!("checked code applied {op:?} to {lhs:?} and {rhs:?}"),
453 })
454}
455
456fn written_value(written: Written) -> Value {
458 match written {
459 Written::Int(number) => Value::Int(number),
460 Written::Text(text) => Value::String(Rc::from(text)),
461 }
462}
463
464fn position(index: i64, span: Span) -> Result<usize> {
466 usize::try_from(index).map_err(|_| RuntimeError::new(&Failure::NegativePosition(index), span))
467}
468
469fn item(list: &Value, position: usize, span: Span) -> Result<&Value> {
470 let Value::List(items) = list else {
471 panic!("checked code indexed into {list:?}");
472 };
473 items
474 .get(position)
475 .ok_or_else(|| out_of_range(position, items.len(), span))
476}
477
478fn list_items(list: &mut Value) -> &mut Vec<Value> {
479 match list {
480 Value::List(items) => Rc::make_mut(items),
481 other => panic!("checked code changed {other:?} as a list"),
482 }
483}
484
485fn length(value: &Value, span: Span) -> Result<i64> {
486 let length = match value {
487 Value::List(items) => items.len(),
488 Value::String(text) => text.chars().count(),
489 other => panic!("checked code measured {other:?}"),
490 };
491 i64::try_from(length).map_err(|_| RuntimeError::new(&Failure::TooLong, span))
492}
493
494fn out_of_range(position: usize, len: usize, span: Span) -> RuntimeError {
495 RuntimeError::new(&Failure::NoItem { position, len }, span)
496}