Early implementation of scripting elements

This commit is contained in:
2025-03-05 01:36:58 -05:00
parent 5dd9ee96ad
commit 1b3e2c0887
28 changed files with 1384 additions and 371 deletions

View File

@@ -1,19 +1,22 @@
use rustyline::highlight::Highlighter;
use sys::get_bin_path;
use crate::prelude::*;
use crate::{parse::lex::KEYWORDS, prelude::*};
use super::readline::SynHelper;
impl<'a> Highlighter for SynHelper<'a> {
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
let mut shenv_clone = self.shenv.clone();
shenv_clone.new_input(line);
let mut result = String::new();
let mut tokens = Lexer::new(Rc::new(line.to_string())).lex().into_iter();
let mut tokens = Lexer::new(line.to_string(),&mut shenv_clone).lex().into_iter();
let mut is_command = true;
let mut in_array = false;
while let Some(token) = tokens.next() {
let raw = token.to_string();
let raw = token.as_raw(&mut shenv_clone);
match token.rule() {
TkRule::Comment => {
let styled = &raw.styled(Style::BrightBlack);
@@ -35,7 +38,7 @@ impl<'a> Highlighter for SynHelper<'a> {
let rebuilt = format!("{styled}()");
result.push_str(&rebuilt);
}
TkRule::Keyword => {
_ if KEYWORDS.contains(&token.rule()) => {
if &raw == "for" {
in_array = true;
}
@@ -76,7 +79,7 @@ impl<'a> Highlighter for SynHelper<'a> {
result.push_str(&raw);
} else if is_command {
if get_bin_path(&token.to_string(), self.shenv).is_some() ||
if get_bin_path(&token.as_raw(&mut shenv_clone), self.shenv).is_some() ||
self.shenv.logic().get_alias(&raw).is_some() ||
self.shenv.logic().get_function(&raw).is_some() ||
BUILTINS.contains(&raw.as_str()) {

View File

@@ -4,6 +4,7 @@ use rustyline::{config::Configurer, history::{DefaultHistory, History}, ColorMod
pub mod readline;
pub mod highlight;
pub mod validate;
fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
let hist_path = std::env::var("FERN_HIST").unwrap_or_default();
@@ -25,7 +26,7 @@ fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
editor
}
pub fn read_line<'a>(shenv: &'a mut ShEnv) -> ShResult<String> {
pub fn read_line(shenv: &mut ShEnv) -> ShResult<String> {
log!(TRACE, "Entering prompt");
let prompt = "$ ".styled(Style::Green | Style::Bold);
let mut editor = init_rl(shenv);

View File

@@ -33,11 +33,6 @@ impl<'a> SynHelper<'a> {
}
}
impl<'a> Validator for SynHelper<'a> {
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
Ok(ValidationResult::Valid(None))
}
}
impl<'a> Completer for SynHelper<'a> {

115
src/prompt/validate.rs Normal file
View File

@@ -0,0 +1,115 @@
use rustyline::validate::{ValidationResult, Validator};
use crate::prelude::*;
use super::readline::SynHelper;
pub fn check_delims(line: &str) -> bool {
let mut delim_stack = vec![];
let mut chars = line.chars();
let mut in_quote = None; // Tracks which quote type is open (`'` or `"`)
while let Some(ch) = chars.next() {
match ch {
'{' | '(' | '[' if in_quote.is_none() => delim_stack.push(ch),
'}' if in_quote.is_none() && delim_stack.pop() != Some('{') => return false,
')' if in_quote.is_none() && delim_stack.pop() != Some('(') => return false,
']' if in_quote.is_none() && delim_stack.pop() != Some('[') => return false,
'"' | '\'' => {
if in_quote == Some(ch) {
in_quote = None;
} else if in_quote.is_none() {
in_quote = Some(ch);
}
}
'\\' => { chars.next(); } // Skip next character if escaped
_ => {}
}
}
delim_stack.is_empty() && in_quote.is_none()
}
pub fn check_keywords(line: &str, shenv: &mut ShEnv) -> bool {
use TkRule::*;
let mut expecting: Vec<Vec<TkRule>> = vec![];
let mut tokens = Lexer::new(line.to_string(),shenv).lex().into_iter();
while let Some(token) = tokens.next() {
match token.rule() {
If => {
expecting.push(vec![Then]);
}
Then => {
if let Some(frame) = expecting.pop() {
if frame.contains(&Then) {
expecting.push(vec![Elif, Else, Fi])
} else { return false }
} else { return false }
}
Elif => {
if let Some(frame) = expecting.pop() {
if frame.contains(&Elif) {
expecting.push(vec![Then])
} else { return false }
} else { return false }
}
Else => {
if let Some(frame) = expecting.pop() {
if frame.contains(&Else) {
expecting.push(vec![Fi])
} else { return false }
} else { return false }
}
Fi => {
if let Some(frame) = expecting.pop() {
if frame.contains(&Fi) {
/* Do nothing */
} else { return false }
} else { return false }
}
While | Until | For | Select => {
expecting.push(vec![Do])
}
Do => {
if let Some(frame) = expecting.pop() {
if frame.contains(&Do) {
expecting.push(vec![Done])
} else { return false }
} else { return false }
}
Done => {
if let Some(frame) = expecting.pop() {
if frame.contains(&Done) {
/* Do nothing */
} else { return false }
} else { return false }
}
Case => {
expecting.push(vec![Esac])
}
Esac => {
if let Some(frame) = expecting.pop() {
if frame.contains(&Esac) {
/* Do nothing */
} else { return false }
} else { return false }
}
_ => { /* Do nothing */ }
}
}
expecting.is_empty()
}
impl<'a> Validator for SynHelper<'a> {
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
let input = ctx.input();
let mut shenv_clone = self.shenv.clone();
match check_delims(input) && check_keywords(input, &mut shenv_clone) {
true => Ok(ValidationResult::Valid(None)),
false => Ok(ValidationResult::Incomplete),
}
}
}