//! Utilities for cl-frontend pub mod args { use crate::cli::Mode; use std::{ io::{stdin, IsTerminal}, ops::Deref, path::{Path, PathBuf}, }; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Args { pub path: Option, // defaults None pub repl: bool, // defaults true if stdin is terminal pub mode: Mode, // defaults Interpret } const HELP: &str = "[( --repl | --no-repl )] [( -f | --file ) ]"; impl Args { pub fn new() -> Self { Args { path: None, repl: stdin().is_terminal(), mode: Mode::Interpret } } pub fn parse(mut self) -> Option { let mut args = std::env::args(); let name = args.next().unwrap_or_default(); let mut unknown = false; while let Some(arg) = args.next() { match arg.deref() { "--repl" => self.repl = true, "--no-repl" => self.repl = false, "-f" | "--file" => self.path = args.next().map(PathBuf::from), "-m" | "--mode" => { self.mode = args.next().unwrap_or_default().parse().unwrap_or_default() } arg => { eprintln!("Unknown argument: {arg}"); unknown = true; } } } if unknown { println!("Usage: {name} {HELP}"); None? } Some(self) } /// Returns the path to a file, if one was specified pub fn path(&self) -> Option<&Path> { self.path.as_deref() } /// Returns whether to start a REPL session or not pub fn repl(&self) -> bool { self.repl } /// Returns the repl Mode pub fn mode(&self) -> Mode { self.mode } } impl Default for Args { fn default() -> Self { Self::new() } } } pub mod program { use std::io::{Result as IOResult, Write}; use conlang::{ ast::preamble::{expression::Expr, *}, interpreter::{env::Environment, error::IResult}, lexer::Lexer, parser::{error::PResult, Parser}, pretty_printer::{PrettyPrintable, Printer}, resolver::{error::TyResult, Resolve, Resolver}, token::Token, }; pub struct Tokenized { tokens: Vec, } pub enum Parsed { Program(Start), Expr(Expr), } impl TryFrom for Parsed { type Error = conlang::parser::error::Error; fn try_from(value: Tokenized) -> Result { let mut parser = Parser::new(value.tokens); let ast = parser.parse()?; //if let Ok(ast) = ast { //return Ok(Self::Program(ast)) //} //Ok(Self::Expr(parser.reset().parse_expr()?)) } } pub struct Program { data: Variant, } impl Program { pub fn new(input: &str) -> Result> { let mut tokens = vec![]; let mut errors = vec![]; for token in Lexer::new(input) { match token { Ok(token) => tokens.push(token), Err(error) => errors.push(error), } } if errors.is_empty() { Ok(Self { data: Tokenized { tokens } }) } else { Err(errors) } } pub fn tokens(&self) -> &[Token] { &self.data.tokens } pub fn parse(self) -> PResult> { Ok(Program { data: Parsed::try_from(self.data)? }) } } impl Program { pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> { match &mut self.data { Parsed::Program(start) => start.resolve(resolver), Parsed::Expr(expr) => expr.resolve(resolver), } .map(|ty| println!("{ty}")) } /// Runs the [Program] in the specified [Environment] pub fn run(&self, env: &mut Environment) -> IResult<()> { println!( "{}", match &self.data { Parsed::Program(start) => env.eval(start)?, Parsed::Expr(expr) => env.eval(expr)?, } ); Ok(()) } } impl PrettyPrintable for Program { fn visit(&self, p: &mut Printer) -> IOResult<()> { match &self.data { Parsed::Program(value) => value.visit(p), Parsed::Expr(value) => value.visit(p), } } } } pub mod cli { use conlang::{ interpreter::env::Environment, pretty_printer::PrettyPrintable, resolver::Resolver, token::Token, }; use crate::{ args::Args, program::{Parsed, Program, Tokenized}, }; use std::{ convert::Infallible, error::Error, io::{stdin, stdout, Write}, path::{Path, PathBuf}, str::FromStr, }; // ANSI color escape sequences const ANSI_RED: &str = "\x1b[31m"; const ANSI_GREEN: &str = "\x1b[32m"; const ANSI_CYAN: &str = "\x1b[36m"; const ANSI_BRIGHT_BLUE: &str = "\x1b[94m"; const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m"; // const ANSI_BRIGHT_CYAN: &str = "\x1b[96m"; const ANSI_RESET: &str = "\x1b[0m"; const ANSI_OUTPUT: &str = "\x1b[38;5;117m"; #[derive(Clone, Debug)] pub enum CLI { Repl(Repl), File { mode: Mode, path: PathBuf }, Stdin { mode: Mode }, } impl From for CLI { fn from(value: Args) -> Self { let Args { path, repl, mode } = value; match (repl, path) { (_, Some(path)) => Self::File { mode, path }, (true, None) => Self::Repl(Repl { mode, ..Default::default() }), (false, None) => Self::Stdin { mode }, } } } impl CLI { pub fn run(&mut self) -> Result<(), Box> { use std::{fs, io}; match self { CLI::Repl(repl) => repl.repl(), CLI::File { mode, ref path } => { // read file Self::no_repl(*mode, Some(path), &fs::read_to_string(path)?) } CLI::Stdin { mode } => { Self::no_repl(*mode, None, &io::read_to_string(io::stdin())?) } } Ok(()) } fn no_repl(mode: Mode, path: Option<&Path>, code: &str) { let program = Program::new(code); match (mode, program) { (Mode::Tokenize, Ok(program)) => { for token in program.tokens() { if let Some(path) = path { print!("{}:", path.display()); } print_token(token) } } (Mode::Beautify, Ok(program)) => Self::beautify(program), (Mode::Resolve, Ok(program)) => Self::resolve(program, Default::default()), (Mode::Interpret, Ok(program)) => Self::interpret(program, Environment::new()), (_, Err(errors)) => { for error in errors { if let Some(path) = path { eprint!("{}:", path.display()); } eprintln!("{error}") } } } } fn beautify(program: Program) { match program.parse() { Ok(program) => program.print(), Err(e) => eprintln!("{e}"), }; } fn resolve(program: Program, mut resolver: Resolver) { let mut program = match program.parse() { Ok(program) => program, Err(e) => { eprintln!("{e}"); return; } }; if let Err(e) = program.resolve(&mut resolver) { eprintln!("{e}"); } } fn interpret(program: Program, mut interpreter: Environment) { let program = match program.parse() { Ok(program) => program, Err(e) => { eprintln!("{e}"); return; } }; if let Err(e) = program.run(&mut interpreter) { eprintln!("{e}"); return; } if let Err(e) = interpreter.call("main", &[]) { eprintln!("{e}"); } } } /// The CLI's operating mode #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub enum Mode { Tokenize, Beautify, Resolve, #[default] Interpret, } impl Mode { pub fn ansi_color(self) -> &'static str { match self { Mode::Tokenize => ANSI_BRIGHT_BLUE, Mode::Beautify => ANSI_BRIGHT_MAGENTA, Mode::Resolve => ANSI_GREEN, Mode::Interpret => ANSI_CYAN, } } } impl FromStr for Mode { type Err = Infallible; fn from_str(s: &str) -> Result { Ok(match s { "i" | "interpret" => Mode::Interpret, "b" | "beautify" | "p" | "pretty" => Mode::Beautify, "r" | "resolve" | "typecheck" => Mode::Resolve, "t" | "tokenize" => Mode::Tokenize, _ => Mode::Interpret, }) } } /// Implements the interactive interpreter #[derive(Clone, Debug)] pub struct Repl { prompt_again: &'static str, // " ?>" prompt_begin: &'static str, // "cl>" prompt_error: &'static str, // "! >" env: Environment, resolver: Resolver, mode: Mode, } impl Default for Repl { fn default() -> Self { Self { prompt_begin: "cl>", prompt_again: " ?>", prompt_error: "! >", env: Default::default(), resolver: Default::default(), mode: Default::default(), } } } /// Prompt functions impl Repl { pub fn prompt_begin(&self) { print!( "{}{} {ANSI_RESET}", self.mode.ansi_color(), self.prompt_begin ); let _ = stdout().flush(); } pub fn prompt_again(&self) { print!( "{}{} {ANSI_RESET}", self.mode.ansi_color(), self.prompt_again ); let _ = stdout().flush(); } pub fn prompt_error(&self, err: &impl Error) { println!("{ANSI_RED}{} {err}{ANSI_RESET}", self.prompt_error) } pub fn begin_output(&self) { print!("{ANSI_OUTPUT}") } fn reprompt(&self, buf: &mut String) { self.prompt_begin(); buf.clear(); } } /// The actual REPL impl Repl { /// Constructs a new [Repl] with the provided [Mode] pub fn new(mode: Mode) -> Self { Self { mode, ..Default::default() } } /// Runs the main REPL loop pub fn repl(&mut self) { let mut buf = String::new(); self.prompt_begin(); while let Ok(len) = stdin().read_line(&mut buf) { // Exit the loop if len == 0 { println!(); break; } self.begin_output(); // Process mode-change commands if self.command(&buf) { self.reprompt(&mut buf); continue; } // Lex the buffer, or reset and output the error let code = match Program::new(&buf) { Ok(code) => code, Err(e) => { for error in e { eprintln!("{error}"); } self.reprompt(&mut buf); continue; } }; // Tokenize mode doesn't require valid parse, so it gets processed first if self.mode == Mode::Tokenize { self.tokenize(&code); self.reprompt(&mut buf); continue; } // Parse code and dispatch to the proper function match (len, code.parse()) { // If the code is OK, run it and print any errors (_, Ok(mut code)) => { self.dispatch(&mut code); buf.clear(); } // If the user types two newlines, print syntax errors (1, Err(e)) => { self.prompt_error(&e); buf.clear(); } // Otherwise, ask for more input _ => { self.prompt_again(); continue; } } self.prompt_begin() } } fn help(&self) { println!( "Commands:\n- $tokens\n Tokenize Mode:\n Outputs information derived by the Lexer\n- $pretty\n Beautify Mode:\n Pretty-prints the input\n- $type\n Resolve Mode:\n Attempts variable resolution and type-checking on the input\n- $run\n Interpret Mode:\n Interprets the input using Conlang\'s work-in-progress interpreter\n- $mode\n Prints the current mode\n- $help\n Prints this help message" ); } fn command(&mut self, line: &str) -> bool { match line.trim() { "$pretty" => self.mode = Mode::Beautify, "$tokens" => self.mode = Mode::Tokenize, "$type" => self.mode = Mode::Resolve, "$run" => self.mode = Mode::Interpret, "$mode" => print!("{:?} Mode", self.mode), "$help" => self.help(), _ => return false, } println!(); true } /// Dispatches calls to repl functions based on the program fn dispatch(&mut self, code: &mut Program) { match self.mode { Mode::Tokenize => (), Mode::Beautify => self.beautify(code), Mode::Resolve => self.typecheck(code), Mode::Interpret => self.interpret(code), } } fn tokenize(&mut self, code: &Program) { for token in code.tokens() { print_token(token); } } fn interpret(&mut self, code: &Program) { if let Err(e) = code.run(&mut self.env) { self.prompt_error(&e) } } fn typecheck(&mut self, code: &mut Program) { if let Err(e) = code.resolve(&mut self.resolver) { self.prompt_error(&e) } } fn beautify(&mut self, code: &Program) { code.print() } } fn print_token(t: &Token) { println!( "{:02}:{:02}: {:#19} │{}│", t.line(), t.col(), t.ty(), t.data(), ) } }