2024-02-27 00:00:42 +00:00
use std ::{ borrow ::Cow , rc ::Rc , cell ::RefCell } ;
use matrix ::{ lex ::{ Lexer , TokenData , Token } , vm ::Vm } ;
use rustyline ::{ validate ::{ Validator , ValidationResult , ValidationContext } , highlight ::Highlighter , Helper , Hinter , completion ::Completer } ;
#[ derive(Helper, Hinter) ]
pub struct MatrixHelper {
vm : Rc < RefCell < Vm > >
}
impl MatrixHelper {
pub fn new ( vm : Rc < RefCell < Vm > > ) -> Self {
Self { vm }
}
}
macro_rules ! unmatched {
( $token :expr , $matched :expr ) = > {
Ok ( ::rustyline ::validate ::ValidationResult ::Invalid ( Some ( format! ( " Token ' {:?} ' at {} : {} does not have a matching ' {:?} ' " , $token . data , $token . pos . row , $token . pos . col , $matched ) ) ) )
} ;
}
impl Validator for MatrixHelper {
fn validate ( & self , ctx : & mut ValidationContext ) -> rustyline ::Result < ValidationResult > {
let mut lexer = Lexer ::new ( ctx . input ( ) ) ;
let mut stack = Vec ::new ( ) ;
loop {
use TokenData as T ;
let token = match lexer . next_token ( ) {
Ok ( t ) if t . data = = T ::Eof = > break ,
Ok ( t ) = > t ,
Err ( err ) = > return Ok ( ValidationResult ::Invalid ( Some ( format! ( " {err} " ) ) ) )
} ;
match token . data {
T ::LeftParen |
T ::LeftBrack |
T ::LeftBrace |
T ::For |
T ::Try = > { stack . push ( token . data ) ; } ,
T ::RightParen = >
match stack . pop ( ) {
Some ( T ::LeftParen ) = > { } ,
_ = > return unmatched! ( token , T ::LeftParen )
} ,
T ::RightBrack = >
match stack . pop ( ) {
Some ( T ::LeftBrack ) = > { } ,
_ = > return unmatched! ( token , T ::LeftBrack )
} ,
T ::RightBrace = >
match stack . pop ( ) {
Some ( T ::LeftBrace ) = > { } ,
_ = > return unmatched! ( token , T ::LeftBrace )
} ,
T ::In = >
match stack . pop ( ) {
Some ( T ::For ) = > { } ,
_ = > return unmatched! ( token , T ::For )
} ,
T ::Catch = >
match stack . pop ( ) {
Some ( T ::Try ) = > { } ,
_ = > return unmatched! ( token , T ::Try )
} ,
_ = > { }
} ;
}
if stack . is_empty ( ) {
return Ok ( ValidationResult ::Valid ( None ) )
} else {
return Ok ( ValidationResult ::Incomplete )
}
}
fn validate_while_typing ( & self ) -> bool {
true
}
}
fn find_matching_bracket ( line : & str , pos : usize ) -> Option < usize > {
if pos > = line . len ( ) {
return None
}
let c = line . as_bytes ( ) [ pos ] ;
let ( target , fwd ) = match c {
b '(' = > ( b ')' , true ) ,
b ')' = > ( b '(' , false ) ,
b '[' = > ( b ']' , true ) ,
b ']' = > ( b '[' , false ) ,
b '{' = > ( b '}' , true ) ,
b '}' = > ( b '{' , false ) ,
_ = > return None ,
} ;
let mut depth = 0 ;
let mut idx = 0 ;
if fwd {
let bytes = & line . as_bytes ( ) [ pos + 1 .. ] ;
for & b in bytes {
if b = = c {
depth + = 1 ;
} else if b = = target {
if depth = = 0 {
return Some ( pos + idx + 1 )
} else {
depth - = 1 ;
}
}
idx + = 1 ;
}
} else {
let bytes = & line . as_bytes ( ) [ .. pos ] ;
for & b in bytes . iter ( ) . rev ( ) {
if b = = c {
depth + = 1 ;
} else if b = = target {
if depth = = 0 {
return Some ( pos - idx - 1 )
} else {
depth - = 1 ;
}
}
idx + = 1 ;
}
}
None
}
fn get_token_at ( line : & str , pos : usize ) -> Option < Token > {
use TokenData as T ;
let mut lexer = Lexer ::new ( line ) ;
loop {
match lexer . next_token ( ) {
Ok ( Token { data : T ::Eof , .. } ) = > return None ,
Ok ( t ) if t . bidx < = pos & & ( t . bidx + t . blen ) > pos = > return Some ( t ) ,
Err ( _ ) = > return None ,
_ = > continue
}
}
}
fn find_matching_ident ( line : & str , pos : usize ) -> Option < Rc < str > > {
use TokenData as T ;
match get_token_at ( line , pos ) {
Some ( Token { data : T ::Ident ( ident ) , .. } ) = > Some ( ident ) ,
_ = > return None
}
}
impl Highlighter for MatrixHelper {
fn highlight < ' l > ( & self , line : & ' l str , pos : usize ) -> Cow < ' l , str > {
let matching = find_matching_bracket ( line , pos ) ;
let ident = find_matching_ident ( line , pos ) ;
use TokenData as T ;
let mut lexer = Lexer ::new ( line ) ;
let mut buf = String ::new ( ) ;
let mut last = 0 ;
loop {
let token = match lexer . next_token ( ) {
Ok ( t ) if t . data = = T ::Eof = > break ,
Ok ( t ) = > t ,
Err ( _ ) = > break
} ;
let bl = token . bidx ;
let br = token . bidx + token . blen ;
buf + = & line [ last .. bl ] ;
last = br ;
let color = match token . data {
T ::LeftParen |
T ::RightParen |
T ::LeftBrack |
T ::RightBrack |
T ::LeftBrace |
T ::RightBrace
= > {
match matching {
Some ( idx ) if bl = = idx | | bl = = pos = > " \x1b [33;40m " ,
_ = > " "
}
}
T ::Int ( _ ) |
T ::True |
T ::False |
T ::Nil |
T ::Float ( _ ) |
T ::Complex ( _ )
= > " \x1b [33m " ,
T ::Ident ( tok ) = > {
match ( ident . as_ref ( ) , lexer . peek_token ( ) ) {
( Some ( ident ) , Ok ( t ) ) if t . data = = T ::LeftParen & & ident . as_ref ( ) = = tok . as_ref ( ) = > " \x1b [34;40m " ,
( Some ( ident ) , _ ) if ident . as_ref ( ) = = tok . as_ref ( ) = > " \x1b [40m " ,
( _ , Ok ( t ) ) if t . data = = T ::LeftParen = > " \x1b [34m " ,
_ = > " "
}
}
T ::Regex ( _ ) = > " \x1b [31m " ,
T ::String ( _ ) = > " \x1b [32m " ,
T ::If |
T ::Else |
T ::While |
T ::Let |
T ::Function |
T ::Continue |
T ::Break |
T ::Do |
T ::Loop |
T ::Return |
T ::For |
T ::In |
T ::Try |
T ::Catch
= > " \x1b [38;2;203;166;247m " ,
_ = > {
buf + = & token . str ;
continue ;
}
} ;
let clear = " \x1b [0m " ;
buf + = & format! ( " {color} {} {clear} " , token . str ) ;
}
buf + = & line [ last .. ] ;
Cow ::Owned ( buf )
}
fn highlight_prompt < ' b , ' s : ' b , ' p : ' b > (
& ' s self ,
prompt : & ' p str ,
default : bool ,
) -> std ::borrow ::Cow < ' b , str > {
let _ = default ;
Cow ::Owned ( format! ( " \x1b [35m {prompt} \x1b [0m " ) )
}
fn highlight_hint < ' h > ( & self , hint : & ' h str ) -> Cow < ' h , str > {
Cow ::Owned ( format! ( " \x1b [31;40m Error: \x1b [0;40m {hint} \x1b [0m " ) )
}
fn highlight_candidate < ' c > (
& self ,
candidate : & ' c str ,
completion : rustyline ::CompletionType ,
) -> std ::borrow ::Cow < ' c , str > {
let _ = completion ;
Cow ::Borrowed ( candidate )
}
fn highlight_char ( & self , line : & str , _pos : usize , forced : bool ) -> bool {
forced | | ! line . is_empty ( )
}
}
impl Completer for MatrixHelper {
type Candidate = Rc < str > ;
fn complete (
& self ,
line : & str ,
pos : usize ,
ctx : & rustyline ::Context < '_ > ,
) -> rustyline ::Result < ( usize , Vec < Self ::Candidate > ) > {
let mut idx = 0 ;
let mut buf = String ::new ( ) ;
let mut start = 0 ;
for char in line . chars ( ) {
2024-02-28 01:42:10 +00:00
if buf . is_empty ( ) & & ( char . is_alphabetic ( ) | | char = = '_' ) {
2024-02-27 00:00:42 +00:00
start = idx ;
buf . push ( char ) ;
2024-02-28 01:42:10 +00:00
} else if ! buf . is_empty ( ) & & ( char . is_alphanumeric ( ) | | char = = '_' ) {
2024-02-27 00:00:42 +00:00
buf . push ( char ) ;
} else {
if idx > = pos {
break ;
} else {
buf . clear ( ) ;
}
}
idx + = char . len_utf8 ( ) ;
}
let _ = ( line , pos , ctx ) ;
let globals = self . vm . borrow ( ) . global_names ( ) ;
let names : Vec < Rc < str > > = globals
. borrow ( )
. clone ( )
. into_iter ( )
. filter ( | n | n . starts_with ( & buf ) )
. collect ( ) ;
if buf . is_empty ( ) {
start = pos ;
}
Ok ( ( start , names ) )
}
fn update ( & self , line : & mut rustyline ::line_buffer ::LineBuffer , start : usize , elected : & str , cl : & mut rustyline ::Changeset ) {
let end = line . pos ( ) ;
line . replace ( start .. end , elected , cl ) ;
}
}