1use std::cell::{Cell, RefCell};
5use std::fmt;
6use std::mem;
7
8use nifty_runtime::{DownloadCommand, Failure, FileDialog, StateId, StateSet, WindowRequest};
9
10use crate::dialog::Answer;
11use crate::download::Request;
12use crate::ffi::Str;
13use crate::program::ProgramDesc;
14use crate::value::{Slot, release};
15
16#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct AppError {
20 pub message: String,
21 pub location: Option<SourceLocation>,
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct SourceLocation {
26 pub file: String,
27 pub line: u32,
28 pub column: u32,
29}
30
31impl AppError {
32 pub(crate) fn malformed(why: &str) -> Self {
33 Self {
34 message: format!("this app was built wrong, so it can't run: {why}"),
35 location: None,
36 }
37 }
38}
39
40impl fmt::Display for AppError {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match &self.location {
43 Some(SourceLocation { file, line, column }) => {
44 write!(f, "{file}:{line}:{column}: {}", self.message)
45 }
46 None => f.write_str(&self.message),
47 }
48 }
49}
50
51pub struct Context {
53 program: &'static ProgramDesc,
54 states: Box<[Cell<Slot>]>,
55 changed: RefCell<StateSet>,
57 call_depth: Cell<usize>,
58 failed: RefCell<Option<(Failure, u32)>>,
60 downloads: RefCell<Vec<Request>>,
62 download_commands: RefCell<Vec<(DownloadCommand, String)>>,
64 dialogs: RefCell<Vec<(FileDialog, Answer)>>,
66 window_requests: RefCell<Vec<WindowRequest>>,
68}
69
70impl Context {
71 pub(crate) fn new(program: &'static ProgramDesc) -> Result<Self, AppError> {
73 let states = program.states.items();
74 let context = Self {
75 program,
76 states: states.iter().map(|_| Cell::new(0)).collect(),
77 changed: RefCell::new(StateSet::default()),
78 call_depth: Cell::new(0),
79 failed: RefCell::new(None),
80 downloads: RefCell::new(Vec::new()),
81 download_commands: RefCell::new(Vec::new()),
82 dialogs: RefCell::new(Vec::new()),
83 window_requests: RefCell::new(Vec::new()),
84 };
85 for (slot, state) in context.states.iter().zip(states) {
86 let mut value = 0;
87 if !unsafe { (state.init)(&raw const context, &raw mut value) } {
89 return Err(context.take_error());
90 }
91 slot.set(value);
92 }
93 Ok(context)
94 }
95
96 pub(crate) fn state(&self, state: usize) -> Slot {
97 self.states[state].get()
98 }
99
100 pub(crate) fn state_mut(&self, state: usize) -> *mut Slot {
102 self.changed.borrow_mut().insert(StateId(state));
103 self.states[state].as_ptr()
104 }
105
106 pub(crate) fn take_changed(&self) -> StateSet {
107 mem::take(&mut self.changed.borrow_mut())
108 }
109
110 pub(crate) fn fail(&self, failure: Failure, span: u32) {
111 *self.failed.borrow_mut() = Some((failure, span));
112 }
113
114 pub(crate) fn enter(&self, function: u32, span: u32) -> bool {
116 let depth = self.call_depth.get();
117 if depth >= nifty_runtime::MAX_CALL_DEPTH {
118 let name = usize::try_from(function)
119 .ok()
120 .and_then(|function| self.program.functions.items().get(function))
121 .map_or_else(|| format!("function {function}"), Str::lossy);
122 self.fail(Failure::TooDeep { function: name }, span);
123 return false;
124 }
125 self.call_depth.set(depth + 1);
126 true
127 }
128
129 pub(crate) fn leave(&self) {
130 self.call_depth.set(self.call_depth.get().saturating_sub(1));
131 }
132
133 pub(crate) fn request_download(&self, request: Request) {
134 self.downloads.borrow_mut().push(request);
135 }
136
137 pub(crate) fn take_downloads(&self) -> Vec<Request> {
139 mem::take(&mut self.downloads.borrow_mut())
140 }
141
142 pub(crate) fn request_download_command(&self, command: DownloadCommand, id: String) {
143 self.download_commands.borrow_mut().push((command, id));
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 ask_dialog(&self, dialog: FileDialog, answer: Answer) {
152 self.dialogs.borrow_mut().push((dialog, answer));
153 }
154
155 pub(crate) fn request_window(&self, request: WindowRequest) {
156 self.window_requests.borrow_mut().push(request);
157 }
158
159 pub(crate) fn take_window_requests(&self) -> Vec<WindowRequest> {
161 mem::take(&mut self.window_requests.borrow_mut())
162 }
163
164 pub(crate) fn take_dialogs(&self) -> Vec<(FileDialog, Answer)> {
166 mem::take(&mut self.dialogs.borrow_mut())
167 }
168
169 pub(crate) fn take_error(&self) -> AppError {
175 let (failure, span) = self
176 .failed
177 .borrow_mut()
178 .take()
179 .expect("compiled code fails only after the runtime records why");
180 self.error(&failure, span)
181 }
182
183 pub(crate) fn error(&self, failure: &Failure, span: u32) -> AppError {
184 AppError {
185 message: failure.to_string(),
186 location: self.location(span),
187 }
188 }
189
190 fn location(&self, span: u32) -> Option<SourceLocation> {
191 let span = self
192 .program
193 .spans
194 .items()
195 .get(usize::try_from(span).ok()?)?;
196 Some(SourceLocation {
197 file: span.file.lossy(),
198 line: span.line,
199 column: span.column,
200 })
201 }
202}
203
204impl Drop for Context {
205 fn drop(&mut self) {
206 for (slot, state) in self.states.iter().zip(self.program.states.items()) {
207 release(slot.get(), state.ty);
208 }
209 }
210}
211
212impl fmt::Debug for Context {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 f.debug_struct("Context")
215 .field("states", &self.states.len())
216 .field("call_depth", &self.call_depth.get())
217 .finish_non_exhaustive()
218 }
219}