1
0

Implement basic input/output TUI

This commit is contained in:
Jeff 2023-12-27 07:50:09 -05:00
parent 71b052e343
commit 43d472a5e4
5 changed files with 126 additions and 40 deletions

12
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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."),
}
}
}

View File

@ -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()
}
}
}

View File

@ -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);
}
}
}