Begin project restructure
This commit is contained in:
parent
413add3ba8
commit
9b74023ade
921
Cargo.lock
generated
921
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
@ -1,23 +1,15 @@
|
||||
[package]
|
||||
name = "dust-lang"
|
||||
description = "General purpose programming language"
|
||||
version = "0.5.0"
|
||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||
[workspace]
|
||||
members = ["dust-lang", "dust-shell"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Jeff Anderson"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = ["Jeff Anderson"]
|
||||
readme = "README.md"
|
||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[dependencies]
|
||||
ariadne = { version = "0.4.0", features = ["auto-color"] }
|
||||
chumsky = { version = "1.0.0-alpha.6", features = ["pratt", "label"] }
|
||||
clap = { version = "4.5.2", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
env_logger = "0.11.3"
|
||||
log = "0.4.21"
|
||||
rand = "0.8.5"
|
||||
stanza = "0.5.1"
|
||||
|
19
dust-lang/Cargo.toml
Normal file
19
dust-lang/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "dust-lang"
|
||||
description = "Interpreter library for the Dust programming language"
|
||||
version = "0.5.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ariadne = { version = "0.4.0", features = ["auto-color"] }
|
||||
chumsky = { version = "1.0.0-alpha.6", features = ["pratt", "label"] }
|
||||
clap = { version = "4.5.2", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
env_logger = "0.11.3"
|
||||
log = "0.4.21"
|
||||
rand = "0.8.5"
|
||||
stanza = "0.5.1"
|
@ -258,7 +258,7 @@ impl PartialOrd for ValueNode {
|
||||
|
||||
impl Ord for ValueNode {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
use ValueNode::*;
|
||||
use self::ValueNode::*;
|
||||
|
||||
match (self, other) {
|
||||
(Boolean(left), Boolean(right)) => left.cmp(right),
|
@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
sync::{Arc, RwLock},
|
||||
sync::{Arc, RwLock, RwLockReadGuard},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -28,6 +28,12 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner(
|
||||
&self,
|
||||
) -> Result<RwLockReadGuard<BTreeMap<Identifier, ValueData>>, RwLockPoisonError> {
|
||||
Ok(self.inner.read()?)
|
||||
}
|
||||
|
||||
pub fn inherit_types_from(&self, other: &Context) -> Result<(), RwLockPoisonError> {
|
||||
let mut self_data = self.inner.write()?;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{io, ops::Range, sync::PoisonError};
|
||||
use std::{io, sync::PoisonError};
|
||||
|
||||
use ariadne::{Color, Fmt, Label, Report, ReportBuilder, ReportKind};
|
||||
use ariadne::{sources, Color, Fmt, Label, Report, ReportKind};
|
||||
use chumsky::{prelude::Rich, span::Span};
|
||||
|
||||
use crate::{
|
||||
@ -29,7 +29,7 @@ pub enum Error {
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn build_report<'a>(self) -> ReportBuilder<'a, (&'a str, Range<usize>)> {
|
||||
pub fn build_report(self, source: &str) -> Vec<u8> {
|
||||
let (mut builder, validation_error, error_position) = match self {
|
||||
Error::Parse { expected, span } => {
|
||||
let message = if expected.is_empty() {
|
||||
@ -184,7 +184,13 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = Vec::new();
|
||||
|
||||
builder
|
||||
.finish()
|
||||
.write_for_stdout(sources([("input", source)]), &mut output);
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
extern crate chumsky;
|
||||
|
||||
pub mod abstract_tree;
|
||||
pub mod context;
|
||||
pub mod error;
|
17
dust-shell/Cargo.toml
Normal file
17
dust-shell/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "dust-shell"
|
||||
description = "Command line shell for the Dust programming language"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
dust-lang = { path = "../dust-lang" }
|
||||
env_logger = "0.11.3"
|
||||
nu-ansi-term = "0.50.0"
|
||||
reedline = { version = "0.30.0", features = ["sqlite", "system_clipboard"] }
|
239
dust-shell/src/cli.rs
Normal file
239
dust-shell/src/cli.rs
Normal file
@ -0,0 +1,239 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
io::{stderr, Write},
|
||||
path::PathBuf,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use dust_lang::{
|
||||
context::{Context, ValueData},
|
||||
*,
|
||||
};
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use reedline::{
|
||||
default_emacs_keybindings, ColumnarMenu, Completer, DefaultHinter, EditCommand, Emacs, KeyCode,
|
||||
KeyModifiers, MenuBuilder, Prompt, Reedline, ReedlineEvent, ReedlineMenu, Signal, Span,
|
||||
SqliteBackedHistory, Suggestion,
|
||||
};
|
||||
|
||||
pub fn run_shell(context: Context) {
|
||||
let mut interpreter = Interpreter::new(context.clone());
|
||||
let mut keybindings = default_emacs_keybindings();
|
||||
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::CONTROL,
|
||||
KeyCode::Char(' '),
|
||||
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
|
||||
);
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::NONE,
|
||||
KeyCode::Enter,
|
||||
ReedlineEvent::SubmitOrNewline,
|
||||
);
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::NONE,
|
||||
KeyCode::Tab,
|
||||
ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]),
|
||||
);
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::NONE,
|
||||
KeyCode::Tab,
|
||||
ReedlineEvent::Multiple(vec![
|
||||
ReedlineEvent::Menu("context menu".to_string()),
|
||||
ReedlineEvent::MenuNext,
|
||||
]),
|
||||
);
|
||||
|
||||
let edit_mode = Box::new(Emacs::new(keybindings));
|
||||
let history = Box::new(
|
||||
SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None)
|
||||
.expect("Error loading history."),
|
||||
);
|
||||
let hinter = Box::new(DefaultHinter::default().with_style(Style::new().dimmed()));
|
||||
let completer = DustCompleter::new(context.clone());
|
||||
|
||||
let mut line_editor = Reedline::create()
|
||||
.with_edit_mode(edit_mode)
|
||||
.with_history(history)
|
||||
.with_hinter(hinter)
|
||||
.use_kitty_keyboard_enhancement(true)
|
||||
.with_completer(Box::new(completer))
|
||||
.with_menu(ReedlineMenu::EngineCompleter(Box::new(
|
||||
ColumnarMenu::default()
|
||||
.with_name("context menu")
|
||||
.with_text_style(Style::default().fg(Color::White))
|
||||
.with_columns(1)
|
||||
.with_column_padding(10),
|
||||
)));
|
||||
let mut prompt = StarshipPrompt::new();
|
||||
|
||||
prompt.reload();
|
||||
|
||||
loop {
|
||||
let sig = line_editor.read_line(&prompt);
|
||||
|
||||
match sig {
|
||||
Ok(Signal::Success(buffer)) => {
|
||||
if buffer.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let run_result = interpreter.run(&buffer);
|
||||
|
||||
match run_result {
|
||||
Ok(Some(value)) => {
|
||||
println!("{value}")
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(errors) => {
|
||||
for error in errors {
|
||||
let report = error.build_report(&buffer);
|
||||
|
||||
stderr().write_all(&report).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prompt.reload();
|
||||
}
|
||||
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
|
||||
println!("\nLeaving the Dust shell.");
|
||||
break;
|
||||
}
|
||||
x => {
|
||||
println!("Unknown event: {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StarshipPrompt {
|
||||
left: String,
|
||||
right: String,
|
||||
}
|
||||
|
||||
impl StarshipPrompt {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
left: String::new(),
|
||||
right: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reload(&mut self) {
|
||||
let run_starship_left = Command::new("starship").arg("prompt").output();
|
||||
let run_starship_right = Command::new("starship")
|
||||
.args(["prompt", "--right"])
|
||||
.output();
|
||||
let left_prompt = if let Ok(output) = &run_starship_left {
|
||||
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||
} else {
|
||||
">".to_string()
|
||||
};
|
||||
let right_prompt = if let Ok(output) = &run_starship_right {
|
||||
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
self.left = left_prompt;
|
||||
self.right = right_prompt;
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt for StarshipPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
Cow::Borrowed(&self.left)
|
||||
}
|
||||
|
||||
fn render_prompt_right(&self) -> Cow<str> {
|
||||
Cow::Borrowed(&self.right)
|
||||
}
|
||||
|
||||
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> {
|
||||
Cow::Borrowed(" ")
|
||||
}
|
||||
|
||||
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||
Cow::Borrowed("")
|
||||
}
|
||||
|
||||
fn render_prompt_history_search_indicator(
|
||||
&self,
|
||||
_history_search: reedline::PromptHistorySearch,
|
||||
) -> Cow<str> {
|
||||
Cow::Borrowed("")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DustCompleter {
|
||||
context: Context,
|
||||
}
|
||||
|
||||
impl DustCompleter {
|
||||
fn new(context: Context) -> Self {
|
||||
DustCompleter { context }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for DustCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let mut suggestions = Vec::new();
|
||||
let last_word = if let Some(word) = line.rsplit([' ', ':']).next() {
|
||||
word
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
if let Ok(path) = PathBuf::try_from(last_word) {
|
||||
if let Ok(read_dir) = path.read_dir() {
|
||||
for entry in read_dir {
|
||||
if let Ok(entry) = entry {
|
||||
let description = if let Ok(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
"directory"
|
||||
} else if file_type.is_file() {
|
||||
"file"
|
||||
} else if file_type.is_symlink() {
|
||||
"symlink"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
} else {
|
||||
"unknown"
|
||||
};
|
||||
|
||||
suggestions.push(Suggestion {
|
||||
value: entry.path().to_string_lossy().to_string(),
|
||||
description: Some(description.to_string()),
|
||||
extra: None,
|
||||
span: Span::new(pos - last_word.len(), pos),
|
||||
append_whitespace: false,
|
||||
style: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value_data) in self.context.inner().unwrap().iter() {
|
||||
let description = match value_data {
|
||||
ValueData::Value(value) => value.to_string(),
|
||||
ValueData::Type(r#type) => r#type.to_string(),
|
||||
};
|
||||
|
||||
if key.as_str().contains(last_word) {
|
||||
suggestions.push(Suggestion {
|
||||
value: key.to_string(),
|
||||
description: Some(description),
|
||||
extra: None,
|
||||
span: Span::new(pos - last_word.len(), pos),
|
||||
append_whitespace: false,
|
||||
style: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
suggestions
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
//! Command line interface for the dust programming language.
|
||||
mod cli;
|
||||
|
||||
use ariadne::sources;
|
||||
use clap::Parser;
|
||||
use cli::run_shell;
|
||||
use colored::Colorize;
|
||||
|
||||
use std::{fs::read_to_string, io::Write};
|
||||
use std::{
|
||||
fs::read_to_string,
|
||||
io::{stderr, Write},
|
||||
};
|
||||
|
||||
use dust_lang::{context::Context, Interpreter};
|
||||
|
||||
@ -39,7 +43,7 @@ fn main() {
|
||||
} else if let Some(command) = args.command {
|
||||
command
|
||||
} else {
|
||||
String::with_capacity(0)
|
||||
return run_shell(context);
|
||||
};
|
||||
|
||||
let mut interpreter = Interpreter::new(context);
|
||||
@ -54,11 +58,9 @@ fn main() {
|
||||
}
|
||||
Err(errors) => {
|
||||
for error in errors {
|
||||
error
|
||||
.build_report()
|
||||
.finish()
|
||||
.eprint(sources([("input", &source)]))
|
||||
.unwrap()
|
||||
let report = error.build_report(&source);
|
||||
|
||||
stderr().write_all(&report).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user