1#[cfg(test)]
7#[path = "./runner_test.rs"]
8mod runner_test;
9
10use crate::expansion::{self, ExpandedValue};
11use crate::parser;
12use crate::types::command::{CommandInvocationContext, CommandResult, Commands, GoToValue};
13use crate::types::env::Env;
14use crate::types::error::ScriptError;
15use crate::types::instruction::{
16 Instruction, InstructionMetaInfo, InstructionType, ScriptInstruction,
17};
18use crate::types::runtime::{Context, Runtime, StateValue};
19use std::collections::HashMap;
20use std::io::stdin;
21use std::sync::atomic::Ordering;
22
23#[derive(Debug)]
24enum EndReason {
25 ExitCalled,
26 ReachedEnd,
27 Crash(ScriptError),
28 Halted,
29}
30
31pub fn run_script(text: &str, context: Context, env: Option<Env>) -> Result<Context, ScriptError> {
33 match parser::parse_text(text) {
34 Ok(instructions) => run(instructions, context, env),
35 Err(error) => Err(error),
36 }
37}
38
39pub fn run_script_file(
41 file: &str,
42 context: Context,
43 env: Option<Env>,
44) -> Result<Context, ScriptError> {
45 match parser::parse_file(file) {
46 Ok(instructions) => run(instructions, context, env),
47 Err(error) => Err(error),
48 }
49}
50
51pub fn repl(mut context: Context) -> Result<Context, ScriptError> {
53 let mut text = String::new();
54 let mut instructions = vec![];
55
56 loop {
57 text.clear();
58
59 match stdin().read_line(&mut text) {
60 Ok(_) => {
61 match parser::parse_text(&text) {
62 Ok(mut new_instructions) => {
63 let start = instructions.len();
65
66 instructions.append(&mut new_instructions);
68 let runtime = create_runtime(instructions.clone(), context, None);
69
70 let (updated_context, end_reason) = run_instructions(runtime, start, true)?;
71
72 context = updated_context;
73
74 match end_reason {
75 EndReason::ExitCalled => return Ok(context),
76 EndReason::Crash(error) => println!("{}", &error.to_string()),
77 _ => (),
78 };
79 }
80 Err(error) => return Err(error),
81 }
82 }
83 Err(error) => {
84 return Err(ScriptError::Runtime(
85 error.to_string(),
86 Some(InstructionMetaInfo::new()),
87 ));
88 }
89 };
90 }
91}
92
93fn run(
94 instructions: Vec<Instruction>,
95 context: Context,
96 env: Option<Env>,
97) -> Result<Context, ScriptError> {
98 let runtime = create_runtime(instructions, context, env);
99
100 match run_instructions(runtime, 0, false) {
101 Ok((context, _)) => Ok(context),
102 Err(error) => Err(error),
103 }
104}
105
106fn create_runtime(instructions: Vec<Instruction>, context: Context, env: Option<Env>) -> Runtime {
107 let mut runtime = Runtime::new(context, env);
108
109 let mut line = 0;
110 for instruction in &instructions {
111 if let InstructionType::Script(ref value) = &instruction.instruction_type {
112 if let Some(ref label) = value.label {
113 runtime.label_to_line.insert(label.to_string(), line);
114 };
115 };
116
117 line += 1;
118 }
119
120 runtime.instructions = Some(instructions);
121
122 runtime
123}
124
125fn run_instructions(
126 mut runtime: Runtime,
127 start_at: usize,
128 repl_mode: bool,
129) -> Result<(Context, EndReason), ScriptError> {
130 let mut line = start_at;
131 let mut state = runtime.context.state.clone();
132
133 let instructions = match runtime.instructions {
134 Some(ref instructions) => instructions,
135 None => return Ok((runtime.context, EndReason::ReachedEnd)),
136 };
137
138 let mut end_reason = EndReason::ReachedEnd;
139 loop {
140 if runtime.env.halt.load(Ordering::SeqCst) {
141 end_reason = EndReason::Halted;
142 break;
143 }
144
145 let (instruction, meta_info) = if instructions.len() > line {
146 let instruction = instructions[line].clone();
147 let meta_info = instruction.meta_info.clone();
148 (instruction, meta_info)
149 } else {
150 break;
151 };
152
153 let (command_result, output_variable) = run_instruction(
154 &mut runtime.context.commands,
155 &mut runtime.context.variables,
156 &mut state,
157 instructions,
158 instruction,
159 line,
160 &mut runtime.env,
161 );
162
163 match command_result {
164 CommandResult::Exit(output) => {
165 update_output(
166 &mut runtime.context.variables,
167 output_variable,
168 output.clone(),
169 );
170 end_reason = EndReason::ExitCalled;
171
172 if repl_mode {
173 return Ok((runtime.context, end_reason));
174 }
175
176 if let Some(exit_code_str) = output {
177 if let Ok(exit_code) = exit_code_str.parse::<i32>() {
178 if exit_code != 0 {
179 return Err(ScriptError::Runtime(
180 format!("Exit with error code: {}", exit_code).to_string(),
181 Some(meta_info.clone()),
182 ));
183 }
184 }
185 }
186
187 break;
188 }
189 CommandResult::Error(error) => {
190 update_output(
191 &mut runtime.context.variables,
192 output_variable,
193 Some("false".to_string()),
194 );
195
196 let post_error_line = line + 1;
197
198 if let Err(error) = run_on_error_instruction(
199 &mut runtime.context.commands,
200 &mut runtime.context.variables,
201 &mut state,
202 instructions,
203 error,
204 meta_info.clone(),
205 &mut runtime.env,
206 ) {
207 return Err(ScriptError::Runtime(error, Some(meta_info.clone())));
208 };
209
210 line = post_error_line;
211 }
212 CommandResult::Crash(error) => {
213 let script_error = ScriptError::Runtime(error, Some(meta_info));
214
215 if repl_mode {
216 return Ok((runtime.context, EndReason::Crash(script_error)));
217 }
218
219 return Err(script_error);
220 }
221 CommandResult::Continue(output) => {
222 update_output(&mut runtime.context.variables, output_variable, output);
223
224 line += 1;
225 }
226 CommandResult::GoTo(output, goto_value) => {
227 update_output(&mut runtime.context.variables, output_variable, output);
228
229 match goto_value {
230 GoToValue::Label(label) => match runtime.label_to_line.get(&label) {
231 Some(value) => line = *value,
232 None => {
233 return Err(ScriptError::Runtime(
234 format!("Label: {} not found.", label),
235 Some(meta_info),
236 ));
237 }
238 },
239 GoToValue::Line(line_number) => line = line_number,
240 }
241 }
242 };
243 }
244
245 runtime.context.state = state;
246
247 Ok((runtime.context, end_reason))
248}
249
250fn update_output(
251 variables: &mut HashMap<String, String>,
252 output_variable: Option<String>,
253 output: Option<String>,
254) {
255 if output_variable.is_some() {
256 match output {
257 Some(value) => variables.insert(output_variable.unwrap(), value),
258 None => variables.remove(&output_variable.unwrap()),
259 };
260 }
261}
262
263fn run_on_error_instruction(
264 commands: &mut Commands,
265 variables: &mut HashMap<String, String>,
266 state: &mut HashMap<String, StateValue>,
267 instructions: &Vec<Instruction>,
268 error: String,
269 meta_info: InstructionMetaInfo,
270 env: &mut Env,
271) -> Result<(), String> {
272 if commands.exists("on_error") {
273 let mut script_instruction = ScriptInstruction::new();
274 script_instruction.command = Some("on_error".to_string());
275 script_instruction.arguments = Some(vec![
276 error,
277 meta_info.line.unwrap_or(0).to_string(),
278 meta_info.source.unwrap_or("".to_string()),
279 ]);
280 let instruction = Instruction {
281 meta_info: InstructionMetaInfo::new(),
282 instruction_type: InstructionType::Script(script_instruction),
283 };
284
285 let (command_result, output_variable) = run_instruction(
286 commands,
287 variables,
288 state,
289 instructions,
290 instruction,
291 0,
292 env,
293 );
294
295 match command_result {
296 CommandResult::Exit(output) => {
297 update_output(variables, output_variable, output);
298
299 Err("Exiting Script.".to_string())
300 }
301 CommandResult::Crash(error) => Err(error),
302 _ => Ok(()),
303 }
304 } else {
305 Ok(())
306 }
307}
308
309pub fn run_instruction(
311 commands: &mut Commands,
312 variables: &mut HashMap<String, String>,
313 state: &mut HashMap<String, StateValue>,
314 instructions: &Vec<Instruction>,
315 instruction: Instruction,
316 line: usize,
317 env: &mut Env,
318) -> (CommandResult, Option<String>) {
319 let mut output_variable = None;
320 let command_result = match instruction.instruction_type {
321 InstructionType::Empty => CommandResult::Continue(None),
322 InstructionType::PreProcess(_) => CommandResult::Continue(None),
323 InstructionType::Script(ref script_instruction) => {
324 output_variable = script_instruction.output.clone();
325
326 match script_instruction.command {
327 Some(ref command) => match commands.get_for_use(command) {
328 Some(command_instance) => {
329 let command_arguments = bind_command_arguments(
330 variables,
331 script_instruction,
332 &instruction.meta_info,
333 );
334
335 let command_args = CommandInvocationContext {
336 arguments: command_arguments,
337 state,
338 variables,
339 output_variable: output_variable.clone(),
340 instructions,
341 commands,
342 line,
343 env,
344 };
345 command_instance.run(command_args)
346 }
347 None => CommandResult::Crash(format!("Command: {} not found.", &command)),
348 },
349 None => CommandResult::Continue(None),
350 }
351 }
352 };
353
354 (command_result, output_variable)
355}
356
357fn bind_command_arguments(
358 variables: &HashMap<String, String>,
359 instruction: &ScriptInstruction,
360 meta_info: &InstructionMetaInfo,
361) -> Vec<String> {
362 let mut arguments = vec![];
363
364 match instruction.arguments {
365 Some(ref arguments_ref) => {
366 for argument in arguments_ref {
367 match expansion::expand_by_wrapper(&argument, meta_info, variables) {
368 ExpandedValue::Single(value) => arguments.push(value),
369 ExpandedValue::Multi(values) => {
370 for value in values {
371 arguments.push(value)
372 }
373 }
374 ExpandedValue::None => arguments.push("".to_string()),
375 }
376 }
377 }
378 None => (),
379 };
380
381 arguments
382}