use crate::ExecutionResult::{CompilationError, ExecutionError, ExecutionOk};
use crate::GlowdustCommand::{Exit, GdString, Log, Reset, Unknown};
use glowdust::compiler::parser::lexer::TokenStream;
use glowdust::runtime::vm::VirtualMachine;
use std::io::Error;
use std::process;
use glowdust::compiler::parser::ast_walker::walk;
use glowdust::compiler::parser::parse_state::ParseState;
use glowdust::compiler::parser::parser::Parser;
use glowdust::compiler::parser::CompileError;
use glowdust::compiler::value::parameters::Variable;
use glowdust::runtime::sink::PrintingSink;
use glowdust::store::memory::MemoryStore;
use rustyline::error::ReadlineError;
use rustyline::history::DefaultHistory;
use rustyline::validate::MatchingBracketValidator;
use rustyline::Editor;
use rustyline::Result;
use rustyline::{Completer, Helper, Highlighter, Hinter, Validator};
use tracing::warn;
use tracing_subscriber::filter::Filtered;
use tracing_subscriber::fmt::Layer;
use tracing_subscriber::reload::Handle;
use tracing_subscriber::{fmt, prelude::*, reload, EnvFilter, Registry};
struct GlowdustData {
vm: VirtualMachine,
state: ParseState,
store: MemoryStore,
log_reloader: Handle<Filtered<Layer<Registry>, EnvFilter, Registry>, Registry>,
buffer: String,
global_variables: Vec<Variable>,
}
enum ExecutionResult {
ExecutionOk,
CompilationError(Box<Vec<CompileError>>),
ExecutionError(String),
}
enum GlowdustCommand {
GdString,
Reset, Exit,
Log,
Unknown,
}
struct GlowdustExecutableCommand {
command: GlowdustCommand,
arguments: Option<String>,
}
impl GlowdustExecutableCommand {
fn execute(&self, state: &mut GlowdustData) -> ExecutionResult {
match self.command {
GdString => {
let mut parser = Parser::new(TokenStream::from(&state.buffer));
state.state.script_symbols.variables = state.global_variables.clone();
parser.parse(&mut state.state);
if !state.state.errors.is_empty() {
return CompilationError(state.state.errors.clone());
}
let compiler_result = walk(&state.state, &mut state.store);
if let Err(err) = compiler_result {
return ExecutionError(err.to_string());
}
let executable = compiler_result.unwrap();
let sink = &mut PrintingSink::default();
let run_result = state.vm.run(&executable, &mut state.store, sink);
if let Err(err) = run_result {
return ExecutionError(err.to_string());
}
for possibly_new_var in executable.script_variables {
let mut found = false;
for existing_var in &state.global_variables {
if existing_var.name == possibly_new_var.name {
found = true;
break;
}
}
if !found {
state.global_variables.push(possibly_new_var);
}
}
}
Log => match &self.arguments {
None => {}
Some(arg) => {
match match arg.trim() {
"trace" => state.log_reloader.modify(|layer| {
*layer.filter_mut() = EnvFilter::new("rustyline=ERROR,glowdust=TRACE")
}),
"debug" => state.log_reloader.modify(|layer| {
*layer.filter_mut() = EnvFilter::new("rustyline=ERROR,glowdust=DEBUG")
}),
"warn" => state.log_reloader.modify(|layer| {
*layer.filter_mut() = EnvFilter::new("rustyline=ERROR,glowdust=WARN")
}),
_ => {
return ExecutionError(
"Valid log levels are trace and debug".to_string(),
);
}
} {
Ok(_) => {
println!("Changed level ok");
}
Err(_) => {
println!("Changing level didn't work at all");
}
}
}
},
Reset => {
state.state = ParseState::default();
}
Exit => {
process::exit(0);
}
Unknown => {
let mut message = String::from("Unknown command: ");
match &self.arguments {
Some(unknown_command) => {
message.push_str(unknown_command);
}
None => message.push_str("could not parse input"),
}
message.push_str(". Valid commands are :show, :log, :exit and :reset");
warn!("{message}");
}
}
ExecutionOk
}
}
fn exit() -> ! {
process::exit(0);
}
#[derive(Completer, Helper, Highlighter, Hinter, Validator)]
struct InputValidator {
#[rustyline(Validator)]
brackets: MatchingBracketValidator,
}
fn handle_input(
editor: &mut Editor<InputValidator, DefaultHistory>,
data: &mut GlowdustData,
) -> Result<GlowdustExecutableCommand> {
let readline = editor.readline(">> ");
match readline {
Ok(line) => {
editor.add_history_entry(&line)?;
data.buffer = line.clone();
Ok(parse_command(&line))
}
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
exit();
}
Err(err) => Err(ReadlineError::from(Error::other(err))),
}
}
fn parse_command(input: &str) -> GlowdustExecutableCommand {
let trimmed = input.trim();
return if let Some(stripped) = trimmed.strip_prefix(':') {
match stripped.trim().split_once(' ') {
Some(("log", args)) => GlowdustExecutableCommand {
command: Log,
arguments: Some(args.to_string()),
},
Some((_, _)) => GlowdustExecutableCommand {
command: Unknown,
arguments: Some(trimmed.to_string()),
},
None => match stripped.trim() {
"log" => GlowdustExecutableCommand {
command: Log,
arguments: Some("show".to_string()),
},
"exit" | "quit" => GlowdustExecutableCommand {
command: Exit,
arguments: None,
},
"reset" => GlowdustExecutableCommand {
command: Reset,
arguments: None,
},
c => GlowdustExecutableCommand {
command: Unknown,
arguments: Some(c.to_string()),
},
},
}
} else {
GlowdustExecutableCommand {
command: GdString,
arguments: Some(trimmed.to_string()),
}
};
}
fn main() -> Result<()> {
let env_filter = EnvFilter::new("rustyline=ERROR,glowdust=ERROR");
let layer = fmt::layer().with_filter(env_filter);
let (filtered_layer, reload_handle) = reload::Layer::new(layer);
tracing_subscriber::registry().with(filtered_layer).init();
let mut state = GlowdustData {
vm: VirtualMachine::new(),
state: ParseState::default(),
store: MemoryStore::new(),
log_reloader: reload_handle,
buffer: String::new(),
global_variables: vec![],
};
let h = InputValidator {
brackets: MatchingBracketValidator::new(),
};
let mut rl = Editor::new().unwrap();
rl.set_helper(Some(h));
loop {
let command = handle_input(&mut rl, &mut state)?;
match command.execute(&mut state) {
ExecutionOk => {
println!();
}
CompilationError(error) => {
eprintln!("Compilation error:");
for err in *error {
eprintln!("At {}: {}", err.span, err.message)
}
}
ExecutionError(message) => {
eprintln!("Encountered error {}", message);
}
}
state.state.reset();
state.vm.reset();
}
}