s_crap/commands/eval/
mod.rs1mod 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 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<()>;