Last active
February 4, 2025 02:05
-
-
Save ek0/d2c48b88fa05751c1131342b88c3a169 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use mlua::Lua; | |
use rustyline::{ | |
CompletionType, Config, EditMode, Editor, | |
completion::{Completer, Pair, extract_word}, | |
error::ReadlineError, | |
history::FileHistory, | |
}; | |
use rustyline_derive::{Helper, Highlighter, Hinter, Validator}; | |
struct MyObject { | |
value: u32, | |
} | |
impl MyObject { | |
fn new(value: u32) -> Self { | |
Self { value } | |
} | |
} | |
struct LuaContext { | |
lua: Lua, | |
} | |
impl LuaContext { | |
fn new() -> Self { | |
let lua = Lua::new(); | |
Self { lua } | |
} | |
fn exec(&self, line: String) -> mlua::Result<()> { | |
match self.lua.load(line).exec() { | |
Ok(_) => {} | |
Err(err) => { | |
println!("Error: {}", err); | |
} | |
} | |
Ok(()) | |
} | |
fn get_lua(&self) -> &Lua { | |
&self.lua | |
} | |
} | |
const DEFAULT_BREAK_CHARS: [u8; 3] = [b' ', b'\t', b'\n']; | |
const LUA_STATEMENTS: [&'static str; 11] = [ | |
"local", "then", "if", "do", "end", "while", "repeat", "until", "for", "break", "function", | |
]; | |
#[derive(Helper, Hinter, Validator, Highlighter)] | |
struct LuaInterpreterHelper<'a> { | |
lua: &'a Lua, | |
} | |
impl<'a> LuaInterpreterHelper<'a> { | |
// We need a reference to the Lua context. | |
fn new(lua: &'a Lua) -> Self { | |
Self { lua } | |
} | |
} | |
impl<'a> Completer for LuaInterpreterHelper<'a> { | |
type Candidate = Pair; | |
fn complete( | |
&self, // FIXME should be `&mut self` | |
line: &str, | |
pos: usize, | |
_ctx: &rustyline::Context<'_>, | |
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> { | |
let mut is_nested_context = false; | |
let (start, word) = extract_word(line, pos, None, |c| { | |
let result = if c == ' ' { | |
true | |
} else if c == '.' { | |
true | |
} else if c == '\t' { | |
true | |
} else if c == '\n' { | |
true | |
} else if c == '(' { | |
true | |
} else { | |
false | |
}; | |
result | |
}); | |
// TODO(_eko): For nested contexts (e.g: table members). We need to rebuild the whole dependency chain from the global context. | |
if start > 0 { | |
if line.chars().nth(start-1).unwrap() == '.' { | |
// We are calling an object method. We gotta find last global symbol in the chain. | |
// This should be the first word encountered that isn't preceeded by a '.', a ')' or a ']' | |
} | |
} | |
let mut matches: Vec<Pair> = LUA_STATEMENTS | |
.iter() | |
.filter_map(|hint| { | |
if hint.starts_with(word) { | |
let replacement = hint; | |
Some(Pair { | |
display: hint.to_string(), | |
replacement: String::from(line[..start].trim()) + replacement, | |
}) | |
} else { | |
None | |
} | |
}) | |
.collect(); | |
// Iterate over every global symbol | |
let globals = self.lua.globals(); | |
for pair in globals.pairs::<mlua::Value, mlua::Value>() { | |
let (value, object) = match pair { | |
Ok((value, object)) => (value, object), | |
Err(e) => panic!("Error: {}", e), | |
}; | |
let name = match value.to_string() { | |
Ok(name) => name, | |
Err(e) => panic!("Error: {}", e), | |
}; | |
if name.starts_with(word) { | |
let mut replacement = String::from(line[..start].trim()) + name.as_str(); | |
if object.is_function() { | |
replacement.push('('); | |
} | |
matches.push(Pair { | |
display: name, | |
replacement: replacement, | |
}); | |
} | |
} | |
Ok((0, matches)) | |
} | |
} | |
fn main() -> rustyline::Result<()> { | |
let ctx = LuaContext::new(); | |
let config = Config::builder() | |
.history_ignore_space(true) | |
.auto_add_history(true) | |
.completion_type(CompletionType::List) | |
.edit_mode(EditMode::Emacs) | |
.build(); | |
let mut rl: Editor<LuaInterpreterHelper, FileHistory> = Editor::with_config(config)?; | |
let helper = LuaInterpreterHelper::new(ctx.get_lua()); | |
rl.set_helper(Some(helper)); | |
loop { | |
let readline = rl.readline(">>> "); | |
match readline { | |
Ok(line) => { | |
ctx.exec(line).unwrap(); | |
} | |
Err(err) => match err { | |
ReadlineError::Interrupted => break, | |
_ => { | |
println!("{}", err); | |
} | |
}, | |
} | |
} | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment