Implement basic input/output TUI
This commit is contained in:
parent
71b052e343
commit
43d472a5e4
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -331,6 +331,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"toml",
|
||||
"tree-sitter",
|
||||
"tui-textarea",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1465,6 +1466,17 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "tui-textarea"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e38ced1f941a9cfc923fbf2fe6858443c42cc5220bfd35bdd3648371e7bd8e"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"ratatui",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.13"
|
||||
|
@ -28,6 +28,7 @@ serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
toml = "0.8.8"
|
||||
tree-sitter = "0.20.10"
|
||||
tui-textarea = "0.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
|
@ -20,6 +20,8 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Error {
|
||||
NoUserInput,
|
||||
|
||||
WithContext {
|
||||
error: Box<Error>,
|
||||
location: Point,
|
||||
@ -412,6 +414,7 @@ impl fmt::Display for Error {
|
||||
source,
|
||||
} => write!(f, "{error} Occured at {location}: \"{source}\""),
|
||||
SerdeJson(message) => write!(f, "JSON processing error: {message}"),
|
||||
NoUserInput => write!(f, "No input was provided."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,11 @@
|
||||
//!
|
||||
//! You can use this library externally by calling either of the "eval"
|
||||
//! functions or by constructing your own Evaluator.
|
||||
use std::cell::RefCell;
|
||||
|
||||
use tree_sitter::{Parser, Tree as TSTree};
|
||||
|
||||
use crate::{language, AbstractTree, Map, Result, Root, Value};
|
||||
use crate::{language, AbstractTree, Error, Map, Result, Root, Value};
|
||||
|
||||
/// Evaluate the given source code.
|
||||
///
|
||||
@ -43,28 +45,37 @@ pub fn evaluate(source: &str) -> Result<Value> {
|
||||
/// );
|
||||
/// ```
|
||||
pub fn evaluate_with_context(source: &str, context: &mut Map) -> Result<Value> {
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(language()).unwrap();
|
||||
|
||||
Interpreter::parse(parser, context, source)?.run()
|
||||
Interpreter::parse(context, &RefCell::new(source.to_string()))?.run()
|
||||
}
|
||||
|
||||
/// A source code interpreter for the Dust language.
|
||||
pub struct Interpreter<'c, 's> {
|
||||
_parser: Parser,
|
||||
parser: Parser,
|
||||
context: &'c mut Map,
|
||||
source: &'s str,
|
||||
syntax_tree: TSTree,
|
||||
abstract_tree: Root,
|
||||
source: &'s RefCell<String>,
|
||||
syntax_tree: Option<TSTree>,
|
||||
abstract_tree: Option<Root>,
|
||||
}
|
||||
|
||||
impl<'c, 's> Interpreter<'c, 's> {
|
||||
pub fn parse(mut parser: Parser, context: &'c mut Map, source: &'s str) -> Result<Self> {
|
||||
let syntax_tree = parser.parse(source, None).unwrap();
|
||||
let abstract_tree = Root::from_syntax_node(source, syntax_tree.root_node(), context)?;
|
||||
pub fn parse(context: &'c mut Map, source: &'s RefCell<String>) -> Result<Self> {
|
||||
let mut parser = Parser::new();
|
||||
|
||||
parser.set_language(language()).unwrap();
|
||||
|
||||
let syntax_tree = parser.parse(source.borrow().as_str(), None);
|
||||
let abstract_tree = if let Some(syntax_tree) = &syntax_tree {
|
||||
Some(Root::from_syntax_node(
|
||||
source.borrow().as_str(),
|
||||
syntax_tree.root_node(),
|
||||
context,
|
||||
)?)
|
||||
} else {
|
||||
panic!()
|
||||
};
|
||||
|
||||
Ok(Interpreter {
|
||||
_parser: parser,
|
||||
parser,
|
||||
context,
|
||||
source,
|
||||
syntax_tree,
|
||||
@ -72,11 +83,38 @@ impl<'c, 's> Interpreter<'c, 's> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self) -> Result<()> {
|
||||
let source = self.source.borrow();
|
||||
let syntax_tree = self.parser.parse(source.as_str(), None);
|
||||
let abstract_tree = if let Some(syntax_tree) = &syntax_tree {
|
||||
Some(Root::from_syntax_node(
|
||||
source.as_str(),
|
||||
syntax_tree.root_node(),
|
||||
self.context,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.syntax_tree = syntax_tree;
|
||||
self.abstract_tree = abstract_tree;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<Value> {
|
||||
self.abstract_tree.run(self.source, self.context)
|
||||
if let Some(abstract_tree) = &self.abstract_tree {
|
||||
abstract_tree.run(self.source.borrow().as_ref(), self.context)
|
||||
} else {
|
||||
Err(Error::NoUserInput)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn syntax_tree(&self) -> String {
|
||||
self.syntax_tree.root_node().to_sexp()
|
||||
if let Some(syntax_tree) = &self.syntax_tree {
|
||||
syntax_tree.root_node().to_sexp()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
82
src/main.rs
82
src/main.rs
@ -1,16 +1,19 @@
|
||||
//! Command line interface for the dust programming language.
|
||||
use clap::Parser;
|
||||
use crossterm::{
|
||||
event::{self, KeyCode, KeyEventKind},
|
||||
event::{poll, read, Event, KeyCode},
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use ratatui::{prelude::CrosstermBackend, style::Stylize, widgets::Paragraph, Terminal};
|
||||
use tree_sitter::Parser as TSParser;
|
||||
use ratatui::{
|
||||
prelude::{CrosstermBackend, Rect},
|
||||
widgets::Paragraph,
|
||||
Terminal,
|
||||
};
|
||||
use std::{cell::RefCell, fs::read_to_string, io::stdout, time::Duration};
|
||||
use tui_textarea::TextArea;
|
||||
|
||||
use std::{fs::read_to_string, io::stdout};
|
||||
|
||||
use dust_lang::{language, Interpreter, Map, Result, Value};
|
||||
use dust_lang::{Interpreter, Map, Result, Value};
|
||||
|
||||
/// Command-line arguments to be parsed.
|
||||
#[derive(Parser, Debug)]
|
||||
@ -44,16 +47,19 @@ fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
if args.path.is_none() && args.command.is_none() {
|
||||
return run_cli_shell().unwrap();
|
||||
run_tui().unwrap();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let source = if let Some(path) = &args.path {
|
||||
let source_input = if let Some(path) = &args.path {
|
||||
read_to_string(path).unwrap()
|
||||
} else if let Some(command) = &args.command {
|
||||
command.clone()
|
||||
} else {
|
||||
"".to_string()
|
||||
"(output 'Hello dust!')".to_string()
|
||||
};
|
||||
let source = RefCell::new(source_input);
|
||||
|
||||
let mut context = Map::new();
|
||||
|
||||
@ -71,10 +77,7 @@ fn main() {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let mut parser = TSParser::new();
|
||||
parser.set_language(language()).unwrap();
|
||||
|
||||
let mut interpreter = Interpreter::parse(parser, &mut context, &source).unwrap();
|
||||
let mut interpreter = Interpreter::parse(&mut context, &source).unwrap();
|
||||
|
||||
if args.interactive {
|
||||
loop {
|
||||
@ -100,30 +103,59 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_cli_shell() -> Result<()> {
|
||||
let mut _context = Map::new();
|
||||
fn run_tui() -> Result<()> {
|
||||
let user_input = RefCell::new("(output 'Hello dust!')".to_string());
|
||||
let mut context = Map::new();
|
||||
let mut interpreter = Interpreter::parse(&mut context, &user_input)?;
|
||||
|
||||
interpreter.update()?;
|
||||
|
||||
let mut interpreter_output = interpreter.run();
|
||||
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
enable_raw_mode()?;
|
||||
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||
let mut textarea = TextArea::default();
|
||||
|
||||
loop {
|
||||
terminal.draw(|frame| {
|
||||
let area = frame.size();
|
||||
frame.render_widget(
|
||||
Paragraph::new("Hello Ratatui! (press 'q' to quit)")
|
||||
.white()
|
||||
.on_blue(),
|
||||
area,
|
||||
);
|
||||
let input_area = Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: frame.size().width,
|
||||
height: frame.size().height / 2,
|
||||
};
|
||||
|
||||
frame.render_widget(textarea.widget(), input_area);
|
||||
|
||||
let output_area = Rect {
|
||||
x: input_area.left(),
|
||||
y: input_area.bottom(),
|
||||
width: frame.size().width,
|
||||
height: frame.size().height / 2,
|
||||
};
|
||||
|
||||
match &interpreter_output {
|
||||
Ok(value) => frame.render_widget(Paragraph::new(value.to_string()), output_area),
|
||||
Err(error) => frame.render_widget(Paragraph::new(error.to_string()), output_area),
|
||||
}
|
||||
})?;
|
||||
|
||||
if event::poll(std::time::Duration::from_millis(16))? {
|
||||
if let event::Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
||||
if poll(Duration::from_millis(16))? {
|
||||
if let Event::Key(key) = read()? {
|
||||
if key.code == KeyCode::Esc {
|
||||
break;
|
||||
}
|
||||
if key.code == KeyCode::Enter {
|
||||
let input = textarea.lines().join("\n");
|
||||
|
||||
user_input.replace(input);
|
||||
interpreter.update()?;
|
||||
interpreter_output = interpreter.run();
|
||||
}
|
||||
|
||||
textarea.input(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user