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
2024-03-01 08:35:58 +00:00
pub mod ansi {
// ANSI color escape sequences
pub const ANSI_RED : & str = " \x1b [31m " ;
2024-03-01 11:33:35 +00:00
pub const ANSI_GREEN : & str = " \x1b [32m " ; // the color of type checker mode
2024-03-01 08:35:58 +00:00
pub const ANSI_CYAN : & str = " \x1b [36m " ;
2024-03-01 11:33:35 +00:00
// pub const ANSI_BRIGHT_GREEN: &str = "\x1b[92m";
2024-03-01 08:35:58 +00:00
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 " ;
}
2024-01-04 08:18:09 +00:00
pub mod args {
2024-03-01 11:33:35 +00:00
use crate ::tools ::is_terminal ;
2024-03-01 08:35:58 +00:00
use argh ::FromArgs ;
use std ::{ path ::PathBuf , str ::FromStr } ;
2024-01-04 08:18:09 +00:00
2024-03-01 08:35:58 +00:00
/// The Conlang prototype debug interface
#[ derive(Clone, Debug, FromArgs, PartialEq, Eq, PartialOrd, Ord) ]
2024-01-04 08:18:09 +00:00
pub struct Args {
2024-03-01 08:35:58 +00:00
/// the main source file
#[ argh(positional) ]
pub file : Option < PathBuf > ,
/// files to include
#[ argh(option, short = 'I') ]
pub include : Vec < PathBuf > ,
/// the Repl mode to start in
#[ argh(option, short = 'm', default = " Default::default() " ) ]
pub mode : Mode ,
2024-03-01 11:33:35 +00:00
/// whether to start the repl (`true` or `false`)
#[ argh(option, short = 'r', default = " is_terminal() " ) ]
pub repl : bool ,
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
/// The CLI's operating mode
#[ derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord) ]
pub enum Mode {
Tokenize ,
Beautify ,
#[ 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 ::Interpret = > ANSI_CYAN ,
2024-01-04 08:18:09 +00:00
}
}
}
2024-03-01 08:35:58 +00:00
impl FromStr for Mode {
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 ,
" t " | " tokenize " | " token " = > Mode ::Tokenize ,
_ = > Err ( " Recognized modes are: 'r' \" run \" , 'p' \" pretty \" , 't' \" token \" " ) ? ,
} )
2024-01-04 08:18:09 +00:00
}
}
}
pub mod program {
2024-04-19 08:01:24 +00:00
use cl_ast ::ast ;
2024-02-29 23:51:38 +00:00
use cl_interpret ::{
env ::Environment , error ::IResult , interpret ::Interpret , temp_type_impl ::ConValue ,
} ;
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-04-19 01:47:28 +00:00
use std ::fmt ::Display ;
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 ) ,
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_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:?} " ) ,
2024-01-04 08:18:09 +00:00
}
}
2024-01-21 11:32:18 +00:00
pub fn print ( & self ) {
2024-04-19 01:47:28 +00:00
match & self . data {
Parsed ::File ( v ) = > println! ( " {v} " ) ,
Parsed ::Stmt ( v ) = > println! ( " {v} " ) ,
2024-01-21 11:32:18 +00:00
} ;
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 ) ,
}
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} " ) ,
2024-01-04 08:18:09 +00:00
}
}
}
}
pub mod cli {
2024-03-01 08:35:58 +00:00
//! Implement's the command line interface
2024-01-04 08:18:09 +00:00
use crate ::{
2024-03-01 08:35:58 +00:00
args ::{ Args , Mode } ,
program ::{ Parsable , Program } ,
repl ::Repl ,
tools ::print_token ,
2024-01-04 08:18:09 +00:00
} ;
2024-03-01 08:35:58 +00:00
use cl_interpret ::{ env ::Environment , temp_type_impl ::ConValue } ;
use cl_lexer ::Lexer ;
use cl_parser ::Parser ;
use std ::{ error ::Error , path ::Path } ;
2024-01-04 08:18:09 +00:00
2024-03-01 08:35:58 +00:00
/// Run the command line interface
pub fn run ( args : Args ) -> Result < ( ) , Box < dyn Error > > {
2024-03-01 11:33:35 +00:00
let Args { file , include , mode , repl } = args ;
2024-02-28 07:21:50 +00:00
2024-03-01 08:35:58 +00:00
let mut env = Environment ::new ( ) ;
for path in include {
load_file ( & mut env , path ) ? ;
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
2024-03-01 11:33:35 +00:00
if repl {
if let Some ( file ) = file {
load_file ( & mut env , file ) ? ;
}
Repl ::with_env ( mode , env ) . repl ( )
} else {
2024-03-01 08:35:58 +00:00
let code = match & file {
Some ( file ) = > std ::fs ::read_to_string ( file ) ? ,
None = > std ::io ::read_to_string ( std ::io ::stdin ( ) ) ? ,
2024-01-04 08:18:09 +00:00
} ;
2024-03-01 08:35:58 +00:00
let code = Program ::new ( & code ) ;
match mode {
Mode ::Tokenize = > tokenize ( code , file ) ,
Mode ::Beautify = > beautify ( code ) ,
Mode ::Interpret = > interpret ( code , & mut env ) ,
} ? ;
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
Ok ( ( ) )
}
fn load_file (
env : & mut Environment ,
path : impl AsRef < Path > ,
) -> Result < ConValue , Box < dyn Error > > {
let file = std ::fs ::read_to_string ( path ) ? ;
let code = Parser ::new ( Lexer ::new ( & file ) ) . file ( ) ? ;
env . eval ( & code ) . map_err ( Into ::into )
}
fn tokenize (
code : Program < Parsable > ,
path : Option < impl AsRef < Path > > ,
) -> Result < ( ) , Box < dyn Error > > {
for token in code . lex ( ) {
if let Some ( ref path ) = path {
print! ( " {} : " , path . as_ref ( ) . display ( ) ) ;
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
match token {
Ok ( token ) = > print_token ( & token ) ,
Err ( e ) = > println! ( " {e} " ) ,
2024-01-04 08:18:09 +00:00
}
}
2024-03-01 08:35:58 +00:00
Ok ( ( ) )
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
fn beautify ( code : Program < Parsable > ) -> Result < ( ) , Box < dyn Error > > {
code . parse ( ) ? . print ( ) ;
Ok ( ( ) )
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
fn interpret ( code : Program < Parsable > , env : & mut Environment ) -> Result < ( ) , Box < dyn Error > > {
match code . parse ( ) ? . run ( env ) ? {
ConValue ::Empty = > { }
ret = > println! ( " {ret} " ) ,
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
if env . get ( " main " ) . is_ok ( ) {
2024-03-01 11:33:35 +00:00
match env . call ( " main " , & [ ] ) ? {
ConValue ::Empty = > { }
ret = > println! ( " {ret} " ) ,
}
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
Ok ( ( ) )
2024-01-04 08:18:09 +00:00
}
2024-03-01 08:35:58 +00:00
}
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 ;
2024-01-04 08:18:09 +00:00
/// Implements the interactive interpreter
#[ derive(Clone, Debug) ]
pub struct Repl {
prompt_again : & 'static str , // " ?>"
prompt_begin : & 'static str , // "cl>"
prompt_error : & 'static str , // "! >"
2024-03-01 08:35:58 +00:00
prompt_succs : & 'static str , // " ->"
2024-01-05 23:48:19 +00:00
env : Environment ,
2024-01-04 08:18:09 +00:00
mode : Mode ,
}
impl Default for Repl {
fn default ( ) -> Self {
Self {
prompt_begin : " cl> " ,
prompt_again : " ?> " ,
prompt_error : " ! > " ,
2024-03-01 08:35:58 +00:00
prompt_succs : " => " ,
2024-01-05 23:48:19 +00:00
env : Default ::default ( ) ,
2024-01-04 08:18:09 +00:00
mode : Default ::default ( ) ,
}
}
}
/// Prompt functions
impl Repl {
2024-03-01 08:35:58 +00:00
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 ) {
2024-02-28 07:21:50 +00:00
let Self { prompt_error : prompt , .. } = self ;
2024-03-01 08:35:58 +00:00
println! ( " {ANSI_CLEAR_LINES} {ANSI_RED} {prompt} {err} {ANSI_RESET} " )
}
pub fn prompt_succs ( & self , value : & impl Display ) {
2024-03-01 11:33:35 +00:00
let Self { prompt_succs : _prompt , .. } = self ;
println! ( " {ANSI_GREEN} {value} {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 ( ) }
}
2024-03-01 08:35:58 +00:00
/// Constructs a new [Repl] with the provided [Mode] and [Environment]
pub fn with_env ( mode : Mode , env : Environment ) -> Self {
Self { mode , env , .. Default ::default ( ) }
}
2024-01-04 08:18:09 +00:00
/// 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-03-01 11:33:35 +00:00
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! (
2024-04-19 08:01:24 +00:00
" Commands: \n - $tokens \n Tokenize Mode: \n Outputs information derived by the Lexer \n - $pretty \n Beautify Mode: \n Pretty-prints 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 "
2024-01-04 08:18:09 +00:00
) ;
}
fn command ( & mut self , line : & str ) -> bool {
2024-03-01 08:35:58 +00:00
let Some ( line ) = line . trim ( ) . strip_prefix ( '$' ) else {
return false ;
} ;
if let Ok ( mode ) = line . parse ( ) {
self . mode = mode ;
} else {
match line {
" $run " = > self . mode = Mode ::Interpret ,
" mode " = > println! ( " {:?} Mode " , self . mode ) ,
" help " = > self . help ( ) ,
_ = > return false ,
}
2024-01-04 08:18:09 +00:00
}
true
}
/// Dispatches calls to repl functions based on the program
fn dispatch ( & mut self , code : & mut Program < Parsed > ) {
match self . mode {
2024-03-01 08:35:58 +00:00
Mode ::Tokenize = > { }
2024-01-04 08:18:09 +00:00
Mode ::Beautify = > self . beautify ( 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-03-01 08:35:58 +00:00
match code . run ( & mut self . env ) {
Ok ( ConValue ::Empty ) = > { }
res = > self . prompt_result ( res ) ,
2024-01-04 08:18:09 +00:00
}
}
fn beautify ( & mut self , code : & Program < Parsed > ) {
code . print ( )
}
}
2024-03-01 08:35:58 +00:00
}
2024-01-04 08:18:09 +00:00
2024-03-01 08:35:58 +00:00
pub mod tools {
use cl_token ::Token ;
2024-03-01 11:33:35 +00:00
use std ::io ::IsTerminal ;
/// Prints a token in the particular way cl-repl does
2024-03-01 08:35:58 +00:00
pub fn print_token ( t : & Token ) {
2024-01-04 08:18:09 +00:00
println! (
" {:02}:{:02}: {:#19} │{}│ " ,
t . line ( ) ,
t . col ( ) ,
t . ty ( ) ,
t . data ( ) ,
)
}
2024-03-01 11:33:35 +00:00
/// gets whether stdin AND stdout are a terminal, for pipelining
pub fn is_terminal ( ) -> bool {
std ::io ::stdin ( ) . is_terminal ( ) & & std ::io ::stdout ( ) . is_terminal ( )
}
2024-01-04 08:18:09 +00:00
}
2024-02-26 21:15:34 +00:00
pub mod repline ;