s_crap/commands/eval/
mod.rs

1mod console;
2
3use crate::{
4	arg::output,
5	cli::{Result, CLI},
6	format,
7	iter::*,
8	print::*,
9	prompt,
10	spawn::{self, *},
11	Downcast,
12};
13use atty::Stream;
14use clap::{App, ArgMatches};
15use colored::*;
16use console::*;
17use rustyline::Editor;
18use scdlang::Transpiler;
19use scdlang_smcat as smcat;
20use scdlang_xstate as xstate;
21use std::fs;
22use which::which;
23
24pub struct Eval;
25impl<'c> CLI<'c> for Eval {
26	const NAME: &'c str = "eval";
27	const USAGE: &'c str = "
28	-i --interactive 'Prints result on each expression'
29	--strict 'Exit immediately if an error occurred'
30	";
31
32	fn additional_usage<'s>(cmd: App<'s, 'c>) -> App<'s, 'c> {
33		cmd.visible_alias("repl")
34			.about("Evaluate scdlang expression in interactive manner")
35			.args(&[
36				output::dist().long_help(
37					"The output depend if it's directory or file:
38If directory => It will output a numbered file in sequence everytime the REPL produce output.
39	Useful if the input is from stdin.
40If file => It will be overwriten everytime the REPL produce output, especially if `--interactive` is set.
41	Useful if combined with live preview",
42				),
43				output::target(),
44				output::format(),
45			])
46	}
47
48	fn invoke(args: &ArgMatches) -> Result<()> {
49		let output_format = args.value_of(output::FORMAT).unwrap_or_default();
50		let target = args.value_of(output::TARGET).unwrap_or_default();
51		let mut repl: REPL = Editor::with_config(prompt::CONFIG());
52
53		let mut machine: Box<dyn Transpiler> = match target {
54			"xstate" => Box::new(xstate::Machine::new()),
55			"smcat" | "graph" => {
56				let mut machine = Box::new(smcat::Machine::new());
57				let config = machine.configure();
58				match output_format {
59					"ascii" | "boxart" => config.with_err_semantic(true),
60					_ => config.with_err_semantic(false),
61				};
62				machine
63			}
64			_ => unreachable!("{} --format {:?}", Self::NAME, args.value_of(output::TARGET)),
65		};
66
67		let (print_mode, eprint_mode) = if atty::is(Stream::Stdin) || !args.is_present("interactive") {
68			(Mode::REPL, Mode::MultiLine)
69		} else {
70			(Mode::Debug, Mode::Error)
71		};
72		let print = PRINTER(output_format).change(print_mode);
73		let eprint = PRINTER("haskell").change(eprint_mode);
74
75		#[rustfmt::skip]
76		let pprint = |string, header: &str| Console {
77			header,
78			printer: &print,
79			fallback: Ok(&|s| println!("{}\n", s))
80		}.print(string);
81
82		#[rustfmt::skip]
83		let epprint = |string, header: &str| Console {
84			header: &header.red().to_string(),
85			printer: &eprint,
86			fallback: Err(&|s| eprintln!("{}\n", s))
87		}.print(string);
88
89		let hook = |input: String| -> Result<Vec<u8>> {
90			use format::ext;
91			if which("smcat").is_ok() && target.one_of(&["smcat", "graph"]) {
92				let smcat = spawn::smcat(if target == "graph" { "dot" } else { output_format })?;
93				let result = match target {
94					"smcat" => smcat.output_from(input)?.into(),
95					"graph" if which("dot").is_ok() && output_format.one_of(&ext::DOT) => {
96						let input = (input, smcat.downcast()?);
97						spawn::dot(output_format).output_from(input)?
98					}
99					"graph" if which("graph-easy").is_ok() && output_format.one_of(&ext::GRAPH_EASY) => {
100						let input = format::into_legacy_dot(&smcat.output_from(input)?);
101						spawn::graph_easy(output_format)?.output_from(input)?.into()
102					}
103					_ => unreachable!("--format {}", target),
104				};
105				Ok(result)
106			} else {
107				Ok(input.into())
108			}
109		};
110
111		let output = |input: String, header: Option<String>| -> Result<()> {
112			let result = hook(input)?;
113			match args.value_of(output::DIST) {
114				Some(dist) => fs::write(dist, result)?,
115				None => pprint(String::from_utf8(result)?, &header.unwrap_or_default())?,
116			};
117			Ok(())
118		};
119
120		if atty::is(Stream::Stdin) {
121			if !args.is_present("interactive") {
122				println!("Press Ctrl-D / Ctrl-C to exit and print the final results");
123				println!("> type `print` show the result");
124			}
125			println!("> type `exit` to close this session\n");
126		}
127
128		let mut loc = 0;
129		let mut last_line = String::new();
130		while let Ok(line) = repl.readline(&format!("{} ", prompt::REPL.bold())) {
131			machine.configure().with_err_line(loc);
132			let line = line.as_str().trim();
133			match line {
134				"exit" => break,
135				"print" if !args.is_present("interactive") => output(machine.to_string(), None)?,
136				_ if !line.trim().is_empty() && !line.trim().starts_with("//") => match machine.insert_parse(line) {
137					Ok(_) => {
138						if args.is_present("interactive") {
139							// TODO: refactor console.rs
140							let header = format!(
141								"{} {}",
142								if atty::is(Stream::Stdout) {
143									format!("{}:", loc + 1).bright_black().to_string()
144								} else {
145									format!("{}:", loc + 1)
146								},
147								line
148							);
149							output(machine.to_string(), Some(header))?;
150						}
151					}
152					Err(err) => {
153						if args.is_present("strict") {
154							return Err(err);
155						} else {
156							epprint(err.to_string(), "")?;
157						}
158					}
159				},
160				_ => last_line = line.to_string(),
161			};
162			loc += 1;
163		}
164
165		if !args.is_present("interactive") && last_line == "print" {
166			output(machine.to_string(), None)?;
167		}
168
169		Ok(())
170	}
171}
172
173type REPL = Editor<()>;