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

View File

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

View File

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

View File

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

View File

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