Begin project restructure

This commit is contained in:
Jeff 2024-03-20 04:42:13 -04:00
parent 413add3ba8
commit 9b74023ade
30 changed files with 1231 additions and 31 deletions

921
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,15 @@
[package] [workspace]
name = "dust-lang" members = ["dust-lang", "dust-shell"]
description = "General purpose programming language" resolver = "2"
version = "0.5.0"
repository = "https://git.jeffa.io/jeff/dust.git" [workspace.package]
authors = ["Jeff Anderson"]
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Jeff Anderson"] readme = "README.md"
repository = "https://git.jeffa.io/jeff/dust.git"
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 3 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"

0
README.md Normal file
View File

19
dust-lang/Cargo.toml Normal file
View 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"

View File

@ -258,7 +258,7 @@ impl PartialOrd for ValueNode {
impl Ord for ValueNode { impl Ord for ValueNode {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
use ValueNode::*; use self::ValueNode::*;
match (self, other) { match (self, other) {
(Boolean(left), Boolean(right)) => left.cmp(right), (Boolean(left), Boolean(right)) => left.cmp(right),

View File

@ -1,6 +1,6 @@
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
sync::{Arc, RwLock}, sync::{Arc, RwLock, RwLockReadGuard},
}; };
use crate::{ 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> { pub fn inherit_types_from(&self, other: &Context) -> Result<(), RwLockPoisonError> {
let mut self_data = self.inner.write()?; let mut self_data = self.inner.write()?;

View File

@ -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 chumsky::{prelude::Rich, span::Span};
use crate::{ use crate::{
@ -29,7 +29,7 @@ pub enum Error {
} }
impl 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 { let (mut builder, validation_error, error_position) = match self {
Error::Parse { expected, span } => { Error::Parse { expected, span } => {
let message = if expected.is_empty() { let message = if expected.is_empty() {
@ -184,7 +184,13 @@ impl Error {
} }
} }
let mut output = Vec::new();
builder builder
.finish()
.write_for_stdout(sources([("input", source)]), &mut output);
output
} }
} }

View File

@ -1,3 +1,5 @@
extern crate chumsky;
pub mod abstract_tree; pub mod abstract_tree;
pub mod context; pub mod context;
pub mod error; pub mod error;

17
dust-shell/Cargo.toml Normal file
View 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
View 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
}
}

View File

@ -1,10 +1,14 @@
//! Command line interface for the dust programming language. //! Command line interface for the dust programming language.
mod cli;
use ariadne::sources;
use clap::Parser; use clap::Parser;
use cli::run_shell;
use colored::Colorize; 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}; use dust_lang::{context::Context, Interpreter};
@ -39,7 +43,7 @@ fn main() {
} else if let Some(command) = args.command { } else if let Some(command) = args.command {
command command
} else { } else {
String::with_capacity(0) return run_shell(context);
}; };
let mut interpreter = Interpreter::new(context); let mut interpreter = Interpreter::new(context);
@ -54,11 +58,9 @@ fn main() {
} }
Err(errors) => { Err(errors) => {
for error in errors { for error in errors {
error let report = error.build_report(&source);
.build_report()
.finish() stderr().write_all(&report).unwrap();
.eprint(sources([("input", &source)]))
.unwrap()
} }
} }
} }