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",
|
"serde_json",
|
||||||
"toml",
|
"toml",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
|
"tui-textarea",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1465,6 +1466,17 @@ version = "0.2.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
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]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
|
@ -28,6 +28,7 @@ serde = { version = "1.0.188", features = ["derive"] }
|
|||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
toml = "0.8.8"
|
toml = "0.8.8"
|
||||||
tree-sitter = "0.20.10"
|
tree-sitter = "0.20.10"
|
||||||
|
tui-textarea = "0.4.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = "1.0"
|
cc = "1.0"
|
||||||
|
@ -20,6 +20,8 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
NoUserInput,
|
||||||
|
|
||||||
WithContext {
|
WithContext {
|
||||||
error: Box<Error>,
|
error: Box<Error>,
|
||||||
location: Point,
|
location: Point,
|
||||||
@ -412,6 +414,7 @@ impl fmt::Display for Error {
|
|||||||
source,
|
source,
|
||||||
} => write!(f, "{error} Occured at {location}: \"{source}\""),
|
} => write!(f, "{error} Occured at {location}: \"{source}\""),
|
||||||
SerdeJson(message) => write!(f, "JSON processing error: {message}"),
|
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"
|
//! You can use this library externally by calling either of the "eval"
|
||||||
//! functions or by constructing your own Evaluator.
|
//! functions or by constructing your own Evaluator.
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use tree_sitter::{Parser, Tree as TSTree};
|
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.
|
/// 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> {
|
pub fn evaluate_with_context(source: &str, context: &mut Map) -> Result<Value> {
|
||||||
let mut parser = Parser::new();
|
Interpreter::parse(context, &RefCell::new(source.to_string()))?.run()
|
||||||
parser.set_language(language()).unwrap();
|
|
||||||
|
|
||||||
Interpreter::parse(parser, context, source)?.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A source code interpreter for the Dust language.
|
/// A source code interpreter for the Dust language.
|
||||||
pub struct Interpreter<'c, 's> {
|
pub struct Interpreter<'c, 's> {
|
||||||
_parser: Parser,
|
parser: Parser,
|
||||||
context: &'c mut Map,
|
context: &'c mut Map,
|
||||||
source: &'s str,
|
source: &'s RefCell<String>,
|
||||||
syntax_tree: TSTree,
|
syntax_tree: Option<TSTree>,
|
||||||
abstract_tree: Root,
|
abstract_tree: Option<Root>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'c, 's> Interpreter<'c, 's> {
|
impl<'c, 's> Interpreter<'c, 's> {
|
||||||
pub fn parse(mut parser: Parser, context: &'c mut Map, source: &'s str) -> Result<Self> {
|
pub fn parse(context: &'c mut Map, source: &'s RefCell<String>) -> Result<Self> {
|
||||||
let syntax_tree = parser.parse(source, None).unwrap();
|
let mut parser = Parser::new();
|
||||||
let abstract_tree = Root::from_syntax_node(source, syntax_tree.root_node(), context)?;
|
|
||||||
|
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 {
|
Ok(Interpreter {
|
||||||
_parser: parser,
|
parser,
|
||||||
context,
|
context,
|
||||||
source,
|
source,
|
||||||
syntax_tree,
|
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> {
|
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 {
|
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.
|
//! Command line interface for the dust programming language.
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, KeyCode, KeyEventKind},
|
event::{poll, read, Event, KeyCode},
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
ExecutableCommand,
|
ExecutableCommand,
|
||||||
};
|
};
|
||||||
use ratatui::{prelude::CrosstermBackend, style::Stylize, widgets::Paragraph, Terminal};
|
use ratatui::{
|
||||||
use tree_sitter::Parser as TSParser;
|
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::{Interpreter, Map, Result, Value};
|
||||||
|
|
||||||
use dust_lang::{language, Interpreter, Map, Result, Value};
|
|
||||||
|
|
||||||
/// Command-line arguments to be parsed.
|
/// Command-line arguments to be parsed.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@ -44,16 +47,19 @@ fn main() {
|
|||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
if args.path.is_none() && args.command.is_none() {
|
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()
|
read_to_string(path).unwrap()
|
||||||
} else if let Some(command) = &args.command {
|
} else if let Some(command) = &args.command {
|
||||||
command.clone()
|
command.clone()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"(output 'Hello dust!')".to_string()
|
||||||
};
|
};
|
||||||
|
let source = RefCell::new(source_input);
|
||||||
|
|
||||||
let mut context = Map::new();
|
let mut context = Map::new();
|
||||||
|
|
||||||
@ -71,10 +77,7 @@ fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut parser = TSParser::new();
|
let mut interpreter = Interpreter::parse(&mut context, &source).unwrap();
|
||||||
parser.set_language(language()).unwrap();
|
|
||||||
|
|
||||||
let mut interpreter = Interpreter::parse(parser, &mut context, &source).unwrap();
|
|
||||||
|
|
||||||
if args.interactive {
|
if args.interactive {
|
||||||
loop {
|
loop {
|
||||||
@ -100,30 +103,59 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_cli_shell() -> Result<()> {
|
fn run_tui() -> Result<()> {
|
||||||
let mut _context = Map::new();
|
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)?;
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
|
||||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||||
|
let mut textarea = TextArea::default();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
let area = frame.size();
|
let input_area = Rect {
|
||||||
frame.render_widget(
|
x: 0,
|
||||||
Paragraph::new("Hello Ratatui! (press 'q' to quit)")
|
y: 0,
|
||||||
.white()
|
width: frame.size().width,
|
||||||
.on_blue(),
|
height: frame.size().height / 2,
|
||||||
area,
|
};
|
||||||
);
|
|
||||||
|
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 poll(Duration::from_millis(16))? {
|
||||||
if let event::Event::Key(key) = event::read()? {
|
if let Event::Key(key) = read()? {
|
||||||
if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
|
if key.code == KeyCode::Esc {
|
||||||
break;
|
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