2024-01-04 08:18:09 +00:00
//! Utilities for cl-frontend
2024-01-21 11:32:18 +00:00
//!
//! # TODO
//! - [ ] Readline-like line editing
//! - [ ] Raw mode?
2024-03-01 03:04:45 +00:00
#![ warn(clippy::all) ]
2024-01-04 08:18:09 +00:00
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 < PathBuf > , // defaults None
pub repl : bool , // defaults true if stdin is terminal
pub mode : Mode , // defaults Interpret
}
2024-01-21 11:32:18 +00:00
const HELP : & str = " [( --repl | --no-repl )] [--mode (tokens | pretty | type | run)] [( -f | --file ) <filename>] " ;
2024-01-04 08:18:09 +00:00
impl Args {
pub fn new ( ) -> Self {
Args { path : None , repl : stdin ( ) . is_terminal ( ) , mode : Mode ::Interpret }
}
pub fn parse ( mut self ) -> Option < Self > {
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 {
2024-02-29 23:51:38 +00:00
use cl_interpret ::{
env ::Environment , error ::IResult , interpret ::Interpret , temp_type_impl ::ConValue ,
} ;
2024-03-01 01:49:50 +00:00
use cl_ast ::{ self as ast , ast_impl ::format ::Pretty } ;
2024-03-01 02:58:50 +00:00
use cl_lexer ::Lexer ;
2024-03-01 02:41:07 +00:00
use cl_parser ::{ error ::PResult , Parser } ;
2024-03-01 02:58:50 +00:00
use conlang ::resolver ::{ error ::TyResult , Resolver } ;
2024-02-29 23:51:38 +00:00
use std ::{ fmt ::Display , io ::Write } ;
2024-01-04 08:18:09 +00:00
2024-01-21 11:32:18 +00:00
pub struct Parsable ;
2024-01-04 08:18:09 +00:00
pub enum Parsed {
2024-01-21 11:32:18 +00:00
File ( ast ::File ) ,
Stmt ( ast ::Stmt ) ,
Expr ( ast ::Expr ) ,
2024-01-04 08:18:09 +00:00
}
2024-01-21 11:32:18 +00:00
pub struct Program < ' t , Variant > {
text : & ' t str ,
data : Variant ,
}
impl < ' t , V > Program < ' t , V > {
pub fn lex ( & self ) -> Lexer {
Lexer ::new ( self . text )
2024-01-04 08:18:09 +00:00
}
}
2024-01-21 11:32:18 +00:00
impl < ' t > Program < ' t , Parsable > {
pub fn new ( text : & ' t str ) -> Self {
Self { text , data : Parsable }
}
pub fn parse ( self ) -> PResult < Program < ' t , Parsed > > {
self . parse_file ( ) . or_else ( | _ | self . parse_stmt ( ) )
}
pub fn parse_expr ( & self ) -> PResult < Program < ' t , Parsed > > {
Ok ( Program { data : Parsed ::Expr ( Parser ::new ( self . lex ( ) ) . expr ( ) ? ) , text : self . text } )
}
pub fn parse_stmt ( & self ) -> PResult < Program < ' t , Parsed > > {
Ok ( Program { data : Parsed ::Stmt ( Parser ::new ( self . lex ( ) ) . stmt ( ) ? ) , text : self . text } )
}
pub fn parse_file ( & self ) -> PResult < Program < ' t , Parsed > > {
Ok ( Program { data : Parsed ::File ( Parser ::new ( self . lex ( ) ) . file ( ) ? ) , text : self . text } )
}
2024-01-04 08:18:09 +00:00
}
2024-01-21 11:32:18 +00:00
impl < ' t > Program < ' t , Parsed > {
pub fn debug ( & self ) {
match & self . data {
Parsed ::File ( v ) = > eprintln! ( " {v:?} " ) ,
Parsed ::Stmt ( v ) = > eprintln! ( " {v:?} " ) ,
Parsed ::Expr ( v ) = > eprintln! ( " {v:?} " ) ,
2024-01-04 08:18:09 +00:00
}
}
2024-01-21 11:32:18 +00:00
pub fn print ( & self ) {
let mut f = std ::io ::stdout ( ) . pretty ( ) ;
let _ = match & self . data {
Parsed ::File ( v ) = > writeln! ( f , " {v} " ) ,
Parsed ::Stmt ( v ) = > writeln! ( f , " {v} " ) ,
Parsed ::Expr ( v ) = > writeln! ( f , " {v} " ) ,
} ;
// println!("{self}")
2024-01-04 08:18:09 +00:00
}
pub fn resolve ( & mut self , resolver : & mut Resolver ) -> TyResult < ( ) > {
2024-01-21 11:32:18 +00:00
todo! ( " Program::resolve( \n {self} , \n {resolver:?} \n ) " )
2024-01-04 08:18:09 +00:00
}
2024-01-21 11:32:18 +00:00
pub fn run ( & self , env : & mut Environment ) -> IResult < ConValue > {
match & self . data {
Parsed ::File ( v ) = > v . interpret ( env ) ,
Parsed ::Stmt ( v ) = > v . interpret ( env ) ,
Parsed ::Expr ( v ) = > v . interpret ( env ) ,
}
2024-01-04 08:18:09 +00:00
}
2024-01-21 11:32:18 +00:00
// 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(())
// }
2024-01-04 08:18:09 +00:00
}
2024-01-21 11:32:18 +00:00
impl < ' t > Display for Program < ' t , Parsed > {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
2024-01-04 08:18:09 +00:00
match & self . data {
2024-01-21 11:32:18 +00:00
Parsed ::File ( v ) = > write! ( f , " {v} " ) ,
Parsed ::Stmt ( v ) = > write! ( f , " {v} " ) ,
Parsed ::Expr ( v ) = > write! ( f , " {v} " ) ,
2024-01-04 08:18:09 +00:00
}
}
}
2024-01-21 11:32:18 +00:00
// 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),
// }
// }
// }
2024-01-04 08:18:09 +00:00
}
pub mod cli {
use crate ::{
args ::Args ,
2024-01-21 11:32:18 +00:00
program ::{ Parsable , Parsed , Program } ,
2024-01-04 08:18:09 +00:00
} ;
2024-02-29 23:51:38 +00:00
use cl_interpret ::env ::Environment ;
2024-03-01 01:36:06 +00:00
use cl_token ::Token ;
use conlang ::resolver ::Resolver ;
2024-01-04 08:18:09 +00:00
use std ::{
convert ::Infallible ,
error ::Error ,
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 " ;
2024-02-28 07:21:50 +00:00
const ANSI_CLEAR_LINES : & str = " \x1b [G \x1b [J " ;
2024-01-04 08:18:09 +00:00
#[ derive(Clone, Debug) ]
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 ) {
2024-02-28 12:02:00 +00:00
( true , Some ( path ) ) = > {
let prog = std ::fs ::read_to_string ( path ) . unwrap ( ) ;
2024-03-01 02:58:50 +00:00
let code = cl_parser ::Parser ::new ( cl_lexer ::Lexer ::new ( & prog ) )
2024-02-28 12:02:00 +00:00
. file ( )
. unwrap ( ) ;
2024-02-29 23:51:38 +00:00
let mut env = cl_interpret ::env ::Environment ::new ( ) ;
2024-02-28 12:02:00 +00:00
env . eval ( & code ) . unwrap ( ) ;
env . call ( " dump " , & [ ] )
. expect ( " calling dump in the environment shouldn't fail " ) ;
Self ::Repl ( Repl { mode , env , .. Default ::default ( ) } )
}
2024-01-04 08:18:09 +00:00
( _ , 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 < dyn Error > > {
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 ) ;
2024-01-21 11:32:18 +00:00
match mode {
Mode ::Tokenize = > {
for token in program . lex ( ) {
2024-01-04 08:18:09 +00:00
if let Some ( path ) = path {
print! ( " {} : " , path . display ( ) ) ;
}
2024-01-21 11:32:18 +00:00
match token {
Ok ( token ) = > print_token ( & token ) ,
Err ( e ) = > println! ( " {e} " ) ,
2024-01-04 08:18:09 +00:00
}
}
}
2024-01-21 11:32:18 +00:00
Mode ::Beautify = > Self ::beautify ( program ) ,
Mode ::Resolve = > Self ::resolve ( program , Default ::default ( ) ) ,
Mode ::Interpret = > Self ::interpret ( program , Environment ::new ( ) ) ,
2024-01-04 08:18:09 +00:00
}
}
2024-01-21 11:32:18 +00:00
fn beautify ( program : Program < Parsable > ) {
2024-01-04 08:18:09 +00:00
match program . parse ( ) {
Ok ( program ) = > program . print ( ) ,
Err ( e ) = > eprintln! ( " {e} " ) ,
} ;
}
2024-01-21 11:32:18 +00:00
fn resolve ( program : Program < Parsable > , mut resolver : Resolver ) {
2024-01-04 08:18:09 +00:00
let mut program = match program . parse ( ) {
Ok ( program ) = > program ,
Err ( e ) = > {
eprintln! ( " {e} " ) ;
return ;
}
} ;
if let Err ( e ) = program . resolve ( & mut resolver ) {
eprintln! ( " {e} " ) ;
}
}
2024-01-21 11:32:18 +00:00
fn interpret ( program : Program < Parsable > , mut interpreter : Environment ) {
2024-01-04 08:18:09 +00:00
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 < Self , Infallible > {
Ok ( match s {
2024-01-21 11:32:18 +00:00
" i " | " interpret " | " run " = > Mode ::Interpret ,
2024-01-04 08:18:09 +00:00
" b " | " beautify " | " p " | " pretty " = > Mode ::Beautify ,
2024-01-21 11:32:18 +00:00
" r " | " resolve " | " typecheck " | " type " = > Mode ::Resolve ,
" t " | " tokenize " | " tokens " = > Mode ::Tokenize ,
2024-01-04 08:18:09 +00:00
_ = > 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 , // "! >"
2024-01-05 23:48:19 +00:00
env : Environment ,
2024-01-04 08:18:09 +00:00
resolver : Resolver ,
mode : Mode ,
}
impl Default for Repl {
fn default ( ) -> Self {
Self {
prompt_begin : " cl> " ,
prompt_again : " ?> " ,
prompt_error : " ! > " ,
2024-01-05 23:48:19 +00:00
env : Default ::default ( ) ,
2024-01-04 08:18:09 +00:00
resolver : Default ::default ( ) ,
mode : Default ::default ( ) ,
}
}
}
/// Prompt functions
impl Repl {
pub fn prompt_error ( & self , err : & impl Error ) {
2024-02-28 07:21:50 +00:00
let Self { prompt_error : prompt , .. } = self ;
println! ( " {ANSI_CLEAR_LINES} {ANSI_RED} {prompt} {err} {ANSI_RESET} " , )
2024-01-04 08:18:09 +00:00
}
2024-02-28 07:21:50 +00:00
/// Resets the cursor to the start of the line, clears the terminal,
/// and sets the output color
2024-01-04 08:18:09 +00:00
pub fn begin_output ( & self ) {
2024-02-28 07:21:50 +00:00
print! ( " {ANSI_CLEAR_LINES} {ANSI_OUTPUT} " )
2024-01-04 08:18:09 +00:00
}
2024-02-28 07:21:50 +00:00
pub fn clear_line ( & self ) { }
2024-01-04 08:18:09 +00:00
}
/// 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 ) {
2024-02-28 07:21:50 +00:00
use crate ::repline ::{ error ::Error , Repline } ;
2024-02-28 11:11:06 +00:00
let mut rl = Repline ::new ( self . mode . ansi_color ( ) , self . prompt_begin , self . prompt_again ) ;
2024-02-28 07:21:50 +00:00
fn clear_line ( ) {
print! ( " \x1b [G \x1b [J " ) ;
}
loop {
let buf = match rl . read ( ) {
Ok ( buf ) = > buf ,
// Ctrl-C: break if current line is empty
Err ( Error ::CtrlC ( buf ) ) = > {
if buf . is_empty ( ) | | buf . ends_with ( '\n' ) {
return ;
}
2024-02-28 11:11:06 +00:00
rl . accept ( ) ;
println! ( " Cancelled. (Press Ctrl+C again to quit.) " ) ;
2024-02-28 07:21:50 +00:00
continue ;
}
// Ctrl-D: reset input, and parse it for errors
Err ( Error ::CtrlD ( buf ) ) = > {
2024-02-28 11:11:06 +00:00
rl . deny ( ) ;
2024-02-28 07:21:50 +00:00
if let Err ( e ) = Program ::new ( & buf ) . parse ( ) {
clear_line ( ) ;
self . prompt_error ( & e ) ;
}
2024-01-04 08:18:09 +00:00
continue ;
}
2024-02-28 07:21:50 +00:00
Err ( e ) = > {
self . prompt_error ( & e ) ;
return ;
}
2024-01-04 08:18:09 +00:00
} ;
2024-02-28 07:21:50 +00:00
self . begin_output ( ) ;
if self . command ( & buf ) {
rl . deny ( ) ;
rl . set_color ( self . mode . ansi_color ( ) ) ;
continue ;
}
let code = Program ::new ( & buf ) ;
2024-01-04 08:18:09 +00:00
if self . mode = = Mode ::Tokenize {
self . tokenize ( & code ) ;
2024-02-26 21:15:34 +00:00
rl . deny ( ) ;
2024-01-04 08:18:09 +00:00
continue ;
}
2024-02-28 07:21:50 +00:00
match code . lex ( ) . into_iter ( ) . find ( | l | l . is_err ( ) ) {
None = > { }
Some ( Ok ( _ ) ) = > unreachable! ( ) ,
Some ( Err ( error ) ) = > {
rl . deny ( ) ;
2024-02-28 11:11:06 +00:00
self . prompt_error ( & error ) ;
2024-01-04 08:18:09 +00:00
continue ;
}
}
2024-02-28 07:21:50 +00:00
if let Ok ( mut code ) = code . parse ( ) {
rl . accept ( ) ;
self . dispatch ( & mut code ) ;
}
2024-01-04 08:18:09 +00:00
}
}
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 ,
2024-02-28 07:21:50 +00:00
" $mode " = > println! ( " {:?} Mode " , self . mode ) ,
2024-01-04 08:18:09 +00:00
" $help " = > self . help ( ) ,
_ = > return false ,
}
true
}
/// Dispatches calls to repl functions based on the program
fn dispatch ( & mut self , code : & mut Program < Parsed > ) {
match self . mode {
Mode ::Tokenize = > ( ) ,
Mode ::Beautify = > self . beautify ( code ) ,
Mode ::Resolve = > self . typecheck ( code ) ,
Mode ::Interpret = > self . interpret ( code ) ,
}
}
2024-01-21 11:32:18 +00:00
fn tokenize ( & mut self , code : & Program < Parsable > ) {
for token in code . lex ( ) {
match token {
Ok ( token ) = > print_token ( & token ) ,
Err ( e ) = > println! ( " {e} " ) ,
}
2024-01-04 08:18:09 +00:00
}
}
fn interpret ( & mut self , code : & Program < Parsed > ) {
2024-01-05 23:48:19 +00:00
if let Err ( e ) = code . run ( & mut self . env ) {
2024-01-04 08:18:09 +00:00
self . prompt_error ( & e )
}
}
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 > ) {
code . print ( )
}
}
fn print_token ( t : & Token ) {
println! (
" {:02}:{:02}: {:#19} │{}│ " ,
t . line ( ) ,
t . col ( ) ,
t . ty ( ) ,
t . data ( ) ,
)
}
}
2024-02-26 21:15:34 +00:00
pub mod repline ;