cl-repl: restructure for future improvements. Replaces temporary handrolled argument parser with external dependency argh.

TODO: Rewrite `argh` in Conlang :^P
This commit is contained in:
John 2024-03-01 02:35:58 -06:00
parent c71f68eb55
commit d7604ba039
4 changed files with 186 additions and 247 deletions

View File

@ -10,13 +10,13 @@ publish.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
conlang = { path = "../libconlang" }
cl-ast = { path = "../cl-ast" } cl-ast = { path = "../cl-ast" }
cl-lexer = { path = "../cl-lexer" } cl-lexer = { path = "../cl-lexer" }
cl-token = { path = "../cl-token" } cl-token = { path = "../cl-token" }
cl-parser = { path = "../cl-parser" } cl-parser = { path = "../cl-parser" }
cl-interpret = { path = "../cl-interpret" } cl-interpret = { path = "../cl-interpret" }
crossterm = "0.27.0" crossterm = "0.27.0"
argh = "0.1.12"
[dev-dependencies] [dev-dependencies]
cl-structures = { path = "../cl-structures" } cl-structures = { path = "../cl-structures" }

View File

@ -0,0 +1,6 @@
use cl_repl::cli::run;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
run(argh::from_env())
}

View File

@ -5,66 +5,76 @@
//! - [ ] Raw mode? //! - [ ] Raw mode?
#![warn(clippy::all)] #![warn(clippy::all)]
pub mod ansi {
// ANSI color escape sequences
pub const ANSI_RED: &str = "\x1b[31m";
// const ANSI_GREEN: &str = "\x1b[32m"; // the color of type checker mode
pub const ANSI_CYAN: &str = "\x1b[36m";
pub const ANSI_BRIGHT_GREEN: &str = "\x1b[92m";
pub const ANSI_BRIGHT_BLUE: &str = "\x1b[94m";
pub const ANSI_BRIGHT_MAGENTA: &str = "\x1b[95m";
// const ANSI_BRIGHT_CYAN: &str = "\x1b[96m";
pub const ANSI_RESET: &str = "\x1b[0m";
pub const ANSI_OUTPUT: &str = "\x1b[38;5;117m";
pub const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J";
}
pub mod args { pub mod args {
use crate::cli::Mode; use argh::FromArgs;
use std::{ use std::{path::PathBuf, str::FromStr};
io::{stdin, IsTerminal},
ops::Deref,
path::{Path, PathBuf},
};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] /// The Conlang prototype debug interface
#[derive(Clone, Debug, FromArgs, PartialEq, Eq, PartialOrd, Ord)]
pub struct Args { pub struct Args {
pub path: Option<PathBuf>, // defaults None /// the main source file
pub repl: bool, // defaults true if stdin is terminal #[argh(positional)]
pub mode: Mode, // defaults Interpret pub file: Option<PathBuf>,
}
const HELP: &str = "[( --repl | --no-repl )] [--mode (tokens | pretty | type | run)] [( -f | --file ) <filename>]";
impl Args { /// files to include
pub fn new() -> Self { #[argh(option, short = 'I')]
Args { path: None, repl: stdin().is_terminal(), mode: Mode::Interpret } pub include: Vec<PathBuf>,
}
pub fn parse(mut self) -> Option<Self> { /// the Repl mode to start in
let mut args = std::env::args(); #[argh(option, short = 'm', default = "Default::default()")]
let name = args.next().unwrap_or_default(); pub mode: Mode,
let mut unknown = false;
while let Some(arg) = args.next() { /// whether to start the repl
match arg.deref() { #[argh(switch, short = 'r')]
"--repl" => self.repl = true, pub no_repl: bool,
"--no-repl" => self.repl = false, }
"-f" | "--file" => self.path = args.next().map(PathBuf::from),
"-m" | "--mode" => { /// The CLI's operating mode
self.mode = args.next().unwrap_or_default().parse().unwrap_or_default() #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
} pub enum Mode {
arg => { Tokenize,
eprintln!("Unknown argument: {arg}"); Beautify,
unknown = true; #[default]
} Interpret,
} }
impl Mode {
pub fn ansi_color(self) -> &'static str {
use super::ansi::*;
match self {
Mode::Tokenize => ANSI_BRIGHT_BLUE,
Mode::Beautify => ANSI_BRIGHT_MAGENTA,
// Mode::Resolve => ANSI_GREEN,
Mode::Interpret => ANSI_CYAN,
} }
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 { impl FromStr for Mode {
Self::new() type Err = &'static str;
fn from_str(s: &str) -> Result<Self, &'static str> {
Ok(match s {
"i" | "interpret" | "r" | "run" => Mode::Interpret,
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
// "r" | "resolve" | "typecheck" | "type" => Mode::Resolve,
"t" | "tokenize" | "token" => Mode::Tokenize,
_ => Err("Recognized modes are: 'r' \"run\", 'p' \"pretty\", 't' \"token\"")?,
})
} }
} }
} }
@ -77,7 +87,7 @@ pub mod program {
use cl_ast::{self as ast, ast_impl::format::Pretty}; use cl_ast::{self as ast, ast_impl::format::Pretty};
use cl_lexer::Lexer; use cl_lexer::Lexer;
use cl_parser::{error::PResult, Parser}; use cl_parser::{error::PResult, Parser};
use conlang::resolver::{error::TyResult, Resolver}; // use conlang::resolver::{error::TyResult, Resolver};
use std::{fmt::Display, io::Write}; use std::{fmt::Display, io::Write};
pub struct Parsable; pub struct Parsable;
@ -134,10 +144,6 @@ pub mod program {
// println!("{self}") // println!("{self}")
} }
pub fn resolve(&mut self, resolver: &mut Resolver) -> TyResult<()> {
todo!("Program::resolve(\n{self},\n{resolver:?}\n)")
}
pub fn run(&self, env: &mut Environment) -> IResult<ConValue> { pub fn run(&self, env: &mut Environment) -> IResult<ConValue> {
match &self.data { match &self.data {
Parsed::File(v) => v.interpret(env), Parsed::File(v) => v.interpret(env),
@ -153,18 +159,6 @@ pub mod program {
// } // }
// .map(|ty| println!("{ty}")) // .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<'t> Display for Program<'t, Parsed> { impl<'t> Display for Program<'t, Parsed> {
@ -176,173 +170,102 @@ pub mod program {
} }
} }
} }
// impl PrettyPrintable for Program<Parsed> {
// fn visit<W: Write>(&self, p: &mut Printer<W>) -> IOResult<()> {
// match &self.data {
// Parsed::Program(value) => value.visit(p),
// Parsed::Expr(value) => value.visit(p),
// }
// }
// }
} }
pub mod cli { pub mod cli {
//! Implement's the command line interface
use crate::{ use crate::{
args::Args, args::{Args, Mode},
program::{Parsable, Parsed, Program}, program::{Parsable, Program},
}; repl::Repl,
use cl_interpret::env::Environment; tools::print_token,
use cl_token::Token;
use conlang::resolver::Resolver;
use std::{
convert::Infallible,
error::Error,
path::{Path, PathBuf},
str::FromStr,
}; };
use cl_interpret::{env::Environment, temp_type_impl::ConValue};
use cl_lexer::Lexer;
use cl_parser::Parser;
use std::{error::Error, path::Path};
// ANSI color escape sequences /// Run the command line interface
const ANSI_RED: &str = "\x1b[31m"; pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
const ANSI_GREEN: &str = "\x1b[32m"; let Args { file, include, mode, no_repl } = args;
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";
const ANSI_CLEAR_LINES: &str = "\x1b[G\x1b[J"; let mut env = Environment::new();
for path in include {
#[derive(Clone, Debug)] load_file(&mut env, path)?;
pub enum CLI {
Repl(Repl),
File { mode: Mode, path: PathBuf },
Stdin { mode: Mode },
}
impl From<Args> for CLI {
fn from(value: Args) -> Self {
let Args { path, repl, mode } = value;
match (repl, path) {
(true, Some(path)) => {
let prog = std::fs::read_to_string(path).unwrap();
let code = cl_parser::Parser::new(cl_lexer::Lexer::new(&prog))
.file()
.unwrap();
let mut env = cl_interpret::env::Environment::new();
env.eval(&code).unwrap();
env.call("dump", &[])
.expect("calling dump in the environment shouldn't fail");
Self::Repl(Repl { mode, env, ..Default::default() })
}
(_, Some(path)) => Self::File { mode, path },
(true, None) => Self::Repl(Repl { mode, ..Default::default() }),
(false, None) => Self::Stdin { mode },
}
} }
}
impl CLI { if no_repl {
pub fn run(&mut self) -> Result<(), Box<dyn Error>> { let code = match &file {
use std::{fs, io}; Some(file) => std::fs::read_to_string(file)?,
match self { None => std::io::read_to_string(std::io::stdin())?,
CLI::Repl(repl) => repl.repl(), };
CLI::File { mode, ref path } => { let code = Program::new(&code);
// 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 { match mode {
Mode::Tokenize => { Mode::Tokenize => tokenize(code, file),
for token in program.lex() { Mode::Beautify => beautify(code),
if let Some(path) = path { Mode::Interpret => interpret(code, &mut env),
print!("{}:", path.display()); }?;
} } else {
match token { if let Some(file) = file {
Ok(token) => print_token(&token), load_file(&mut env, file)?;
Err(e) => println!("{e}"),
}
}
}
Mode::Beautify => Self::beautify(program),
Mode::Resolve => Self::resolve(program, Default::default()),
Mode::Interpret => Self::interpret(program, Environment::new()),
}
}
fn beautify(program: Program<Parsable>) {
match program.parse() {
Ok(program) => program.print(),
Err(e) => eprintln!("{e}"),
};
}
fn resolve(program: Program<Parsable>, 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<Parsable>, 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}");
} }
Repl::with_env(mode, env).repl()
} }
Ok(())
} }
/// The CLI's operating mode fn load_file(
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] env: &mut Environment,
pub enum Mode { path: impl AsRef<Path>,
Tokenize, ) -> Result<ConValue, Box<dyn Error>> {
Beautify, let file = std::fs::read_to_string(path)?;
Resolve, let code = Parser::new(Lexer::new(&file)).file()?;
#[default] env.eval(&code).map_err(Into::into)
Interpret,
} }
impl Mode {
pub fn ansi_color(self) -> &'static str { fn tokenize(
match self { code: Program<Parsable>,
Mode::Tokenize => ANSI_BRIGHT_BLUE, path: Option<impl AsRef<Path>>,
Mode::Beautify => ANSI_BRIGHT_MAGENTA, ) -> Result<(), Box<dyn Error>> {
Mode::Resolve => ANSI_GREEN, for token in code.lex() {
Mode::Interpret => ANSI_CYAN, if let Some(ref path) = path {
print!("{}:", path.as_ref().display());
}
match token {
Ok(token) => print_token(&token),
Err(e) => println!("{e}"),
} }
} }
Ok(())
} }
impl FromStr for Mode {
type Err = Infallible; fn beautify(code: Program<Parsable>) -> Result<(), Box<dyn Error>> {
fn from_str(s: &str) -> Result<Self, Infallible> { code.parse()?.print();
Ok(match s { Ok(())
"i" | "interpret" | "run" => Mode::Interpret, }
"b" | "beautify" | "p" | "pretty" => Mode::Beautify,
"r" | "resolve" | "typecheck" | "type" => Mode::Resolve, fn interpret(code: Program<Parsable>, env: &mut Environment) -> Result<(), Box<dyn Error>> {
"t" | "tokenize" | "tokens" => Mode::Tokenize, match code.parse()?.run(env)? {
_ => Mode::Interpret, ConValue::Empty => {}
}) ret => println!("{ret}"),
} }
if env.get("main").is_ok() {
println!("-> {}", env.call("main", &[])?);
}
Ok(())
} }
}
pub mod repl {
use crate::{
ansi::*,
args::Mode,
program::{Parsable, Parsed, Program},
tools::print_token,
};
use cl_interpret::{env::Environment, temp_type_impl::ConValue};
use std::fmt::Display;
/// Implements the interactive interpreter /// Implements the interactive interpreter
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -350,8 +273,8 @@ pub mod cli {
prompt_again: &'static str, // " ?>" prompt_again: &'static str, // " ?>"
prompt_begin: &'static str, // "cl>" prompt_begin: &'static str, // "cl>"
prompt_error: &'static str, // "! >" prompt_error: &'static str, // "! >"
prompt_succs: &'static str, // " ->"
env: Environment, env: Environment,
resolver: Resolver,
mode: Mode, mode: Mode,
} }
@ -361,8 +284,8 @@ pub mod cli {
prompt_begin: "cl>", prompt_begin: "cl>",
prompt_again: " ?>", prompt_again: " ?>",
prompt_error: "! >", prompt_error: "! >",
prompt_succs: " =>",
env: Default::default(), env: Default::default(),
resolver: Default::default(),
mode: Default::default(), mode: Default::default(),
} }
} }
@ -370,9 +293,19 @@ pub mod cli {
/// Prompt functions /// Prompt functions
impl Repl { impl Repl {
pub fn prompt_error(&self, err: &impl Error) { pub fn prompt_result<T: Display, E: Display>(&self, res: Result<T, E>) {
match &res {
Ok(v) => self.prompt_succs(v),
Err(e) => self.prompt_error(e),
}
}
pub fn prompt_error(&self, err: &impl Display) {
let Self { prompt_error: prompt, .. } = self; let Self { prompt_error: prompt, .. } = self;
println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}",) println!("{ANSI_CLEAR_LINES}{ANSI_RED}{prompt} {err}{ANSI_RESET}")
}
pub fn prompt_succs(&self, value: &impl Display) {
let Self { prompt_succs: prompt, .. } = self;
println!("{ANSI_BRIGHT_GREEN}{prompt}{ANSI_RESET} {value}")
} }
/// Resets the cursor to the start of the line, clears the terminal, /// Resets the cursor to the start of the line, clears the terminal,
/// and sets the output color /// and sets the output color
@ -387,6 +320,10 @@ pub mod cli {
pub fn new(mode: Mode) -> Self { pub fn new(mode: Mode) -> Self {
Self { mode, ..Default::default() } Self { mode, ..Default::default() }
} }
/// Constructs a new [Repl] with the provided [Mode] and [Environment]
pub fn with_env(mode: Mode, env: Environment) -> Self {
Self { mode, env, ..Default::default() }
}
/// Runs the main REPL loop /// Runs the main REPL loop
pub fn repl(&mut self) { pub fn repl(&mut self) {
use crate::repline::{error::Error, Repline}; use crate::repline::{error::Error, Repline};
@ -455,23 +392,26 @@ pub mod cli {
); );
} }
fn command(&mut self, line: &str) -> bool { fn command(&mut self, line: &str) -> bool {
match line.trim() { let Some(line) = line.trim().strip_prefix('$') else {
"$pretty" => self.mode = Mode::Beautify, return false;
"$tokens" => self.mode = Mode::Tokenize, };
"$type" => self.mode = Mode::Resolve, if let Ok(mode) = line.parse() {
"$run" => self.mode = Mode::Interpret, self.mode = mode;
"$mode" => println!("{:?} Mode", self.mode), } else {
"$help" => self.help(), match line {
_ => return false, "$run" => self.mode = Mode::Interpret,
"mode" => println!("{:?} Mode", self.mode),
"help" => self.help(),
_ => return false,
}
} }
true true
} }
/// Dispatches calls to repl functions based on the program /// Dispatches calls to repl functions based on the program
fn dispatch(&mut self, code: &mut Program<Parsed>) { fn dispatch(&mut self, code: &mut Program<Parsed>) {
match self.mode { match self.mode {
Mode::Tokenize => (), Mode::Tokenize => {}
Mode::Beautify => self.beautify(code), Mode::Beautify => self.beautify(code),
Mode::Resolve => self.typecheck(code),
Mode::Interpret => self.interpret(code), Mode::Interpret => self.interpret(code),
} }
} }
@ -484,21 +424,20 @@ pub mod cli {
} }
} }
fn interpret(&mut self, code: &Program<Parsed>) { fn interpret(&mut self, code: &Program<Parsed>) {
if let Err(e) = code.run(&mut self.env) { match code.run(&mut self.env) {
self.prompt_error(&e) Ok(ConValue::Empty) => {}
} res => self.prompt_result(res),
}
fn typecheck(&mut self, code: &mut Program<Parsed>) {
if let Err(e) = code.resolve(&mut self.resolver) {
self.prompt_error(&e)
} }
} }
fn beautify(&mut self, code: &Program<Parsed>) { fn beautify(&mut self, code: &Program<Parsed>) {
code.print() code.print()
} }
} }
}
fn print_token(t: &Token) { pub mod tools {
use cl_token::Token;
pub fn print_token(t: &Token) {
println!( println!(
"{:02}:{:02}: {:#19} │{}│", "{:02}:{:02}: {:#19} │{}│",
t.line(), t.line(),

View File

@ -1,6 +0,0 @@
use cl_repl::{args::Args, cli::CLI};
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
CLI::from(Args::new().parse().unwrap_or_default()).run()
}