Skip to content

Commit

Permalink
Improve repl autocorrect and error handling
Browse files Browse the repository at this point in the history
* Combine comparison algorithims for autocorrect

* clear zephyr functions

* remove redundant comments because co-pilot is stupid and i probably will never try to use it again

* implement basic tab completion

* fix unused items

* Make workflow check code quality

* split code quality into its own file

* make action fail on bad formatting

* change workflow to nightly

* f it, code quality is considered breaking

* fix forgetting to set toolchain back to nightly when rewriting workflow (😔)

* Add condition for too little arguments

* run cargo fmt

* remove unneeded feature directive
  • Loading branch information
Caznix authored Dec 10, 2024
1 parent b4f3178 commit 3aba3ae
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 41 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Code Quality

on: [push, pull_request]

jobs:
code-quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: clippy, rustfmt

- name: Check formatting
run: cargo fmt -- --check

- name: Run Clippy
run: cargo clippy -- -D warnings

- name: Compilation Check
run: cargo check
5 changes: 3 additions & 2 deletions engine/src/core/repl/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use parking_lot::Mutex;

use super::COMMAND_LIST;
use crate::core::repl::exec::evaluate_command;
const MAX_RECURSION_DEPTH: usize = 500; // increasing this value WILL cause a stack overflow. attempt at your own risk -
// Caz
// increasing this value WILL cause a stack overflow
// attempt at your own risk - Caz
const MAX_RECURSION_DEPTH: usize = 500;

lazy_static! {
static ref RECURSION_DEPTH: Mutex<usize> = parking_lot::Mutex::new(0);
Expand Down
54 changes: 49 additions & 5 deletions engine/src/core/repl/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,54 @@ use log::debug;
use parking_lot::Mutex;
use regex::Regex;
use rustyline::{
error::ReadlineError, highlight::Highlighter, hint::HistoryHinter, history::DefaultHistory,
Cmd, Completer, ConditionalEventHandler, Editor, Event, EventContext, EventHandler, Helper,
Hinter, KeyEvent, RepeatCount, Validator,
completion::Completer, error::ReadlineError, highlight::Highlighter, hint::HistoryHinter,
history::DefaultHistory, Cmd, Completer, ConditionalEventHandler, Editor, Event, EventContext,
EventHandler, Helper, Hinter, KeyEvent, RepeatCount, Validator,
};

use crate::{
core::repl::{commands, Callable, COMMAND_LIST},
utils::logger::LOGGER,
};

struct CommandCompleter;
impl CommandCompleter {
fn new() -> Self {
CommandCompleter {}
}
}

impl Completer for CommandCompleter {
type Candidate = String;

fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let binding = COMMAND_LIST.commands.read();
let filtered_commands: Vec<_> = binding
.iter()
.filter(|command| command.name.starts_with(line))
.collect();

let completions: Vec<String> = filtered_commands
.iter()
.filter(|command| command.name.starts_with(&line[..pos]))
.map(|command| command.name[pos..].to_string())
.collect();
Ok((pos, completions))
}
}

#[derive(Completer, Helper, Hinter, Validator)]
struct MyHelper(#[rustyline(Hinter)] HistoryHinter);
struct MyHelper {
#[rustyline(Hinter)]
hinter: HistoryHinter,
#[rustyline(Completer)]
completer: CommandCompleter,
}

impl Highlighter for MyHelper {
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
Expand Down Expand Up @@ -142,6 +178,11 @@ fn tokenize(command: &str) -> Vec<String> {
tokens
}

pub fn parse_command(input: &str) -> anyhow::Result<Vec<String>> {
let pattern = Regex::new(r"[;|\n]").unwrap();
let commands: Vec<String> = pattern.split(input).map(|s| String::from(s)).collect();
Ok(commands)
}
pub fn evaluate_command(input: &str) -> anyhow::Result<()> {
if input.trim().is_empty() {
println!("Empty command, skipping. type 'help' for a list of commands.");
Expand Down Expand Up @@ -176,7 +217,10 @@ pub fn evaluate_command(input: &str) -> anyhow::Result<()> {

pub async fn handle_repl() -> anyhow::Result<()> {
let mut rl = Editor::<MyHelper, DefaultHistory>::new()?;
rl.set_helper(Some(MyHelper(HistoryHinter::new())));
rl.set_helper(Some(MyHelper {
hinter: HistoryHinter::new(),
completer: CommandCompleter::new(),
}));

rl.bind_sequence(
KeyEvent::from('`'),
Expand Down
96 changes: 81 additions & 15 deletions engine/src/core/repl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,22 @@ pub struct Command {
function: Callable,
pub arg_count: u8,
}

#[allow(private_interfaces)]
impl Command {
pub fn new(
name: &'static str,
description: Option<&'static str>,
function: Callable,
arg_count: Option<u8>,
) -> Self {
Command {
name,
description,
function,
arg_count: arg_count.unwrap_or(0),
}
}

pub fn execute(&self, args: Option<Vec<String>>) -> anyhow::Result<()> {
match &self.function {
Callable::Simple(f) => {
Expand Down Expand Up @@ -69,21 +83,63 @@ pub struct CommandList {
pub aliases: RwLock<HashMap<String, String>>,
}

fn hamming_distance(a: &str, b: &str) -> Option<usize> {
if a.len() != b.len() {
return None;
}
Some(
a.chars()
.zip(b.chars())
.filter(|(char_a, char_b)| char_a != char_b)
.count(),
)
}

fn edit_distance(a: &str, b: &str) -> usize {
let m = a.len();
let n = b.len();

let mut dp = vec![vec![0; n + 1]; m + 1];

for i in 0..=m {
for j in 0..=n {
if i == 0 {
dp[i][j] = j;
} else if j == 0 {
dp[i][j] = i;
} else if a.chars().nth(i - 1) == b.chars().nth(j - 1) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + dp[i - 1][j - 1].min(dp[i - 1][j]).min(dp[i][j - 1]);
}
}
}

dp[m][n]
}

fn check_similarity(target: &str, strings: &[String]) -> Option<String> {
strings
.iter()
.filter(|s| target.chars().zip(s.chars()).any(|(c1, c2)| c1 == c2))
.min_by_key(|s| {
let mut diff_count = 0;
for (c1, c2) in target.chars().zip(s.chars()) {
if c1 != c2 {
diff_count += 1;
}
let max_hamming_distance: usize = 2;
let max_edit_distance: usize = 2;
let mut best_match: Option<String> = None;
let mut best_distance = usize::MAX;

for s in strings {
if let Some(hamming_dist) = hamming_distance(target, s) {
if hamming_dist <= max_hamming_distance && hamming_dist < best_distance {
best_distance = hamming_dist;
best_match = Some(s.clone());
}
diff_count += target.len().abs_diff(s.len());
diff_count
})
.cloned()
} else {
let edit_dist = edit_distance(target, s);
if edit_dist <= max_edit_distance && edit_dist < best_distance {
best_distance = edit_dist;
best_match = Some(s.clone());
}
}
}

best_match
}

impl CommandList {
Expand Down Expand Up @@ -153,6 +209,13 @@ impl CommandList {
);
Ok(())
}
(expected, None) => {
eprintln!(
"Command: '{}' expected {} arguments but received none",
name, expected
);
Ok(())
}
(_, _) => command.execute(args),
}
} else {
Expand All @@ -172,7 +235,10 @@ impl CommandList {
eprintln!("Did you mean: '{}'?", similar.green().italic().bold());
Ok(())
}
None => Ok(()),
None => {
println!("Type 'help' for a list of commands");
Ok(())
}
}
}
}
Expand Down
1 change: 0 additions & 1 deletion engine/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#![deny(clippy::unwrap_in_result)]

use anyhow::Result;
use log::LevelFilter;
use plugin_api::plugin_imports::*;
Expand Down
1 change: 0 additions & 1 deletion engine/src/utils/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ impl DynamicLogger {
pub fn write_to_file(&self, file_path: &str) {
let file = OpenOptions::new()
.create(true)

.append(true)
.open(file_path)
.expect("Failed to open log file");
Expand Down
13 changes: 0 additions & 13 deletions subcrates/zephyr/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
15 changes: 11 additions & 4 deletions test.zensh
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
echo ping
echo pong
exec test.zensh
break









echo "Hello World"
echo "hello world"; hello

0 comments on commit 3aba3ae

Please sign in to comment.