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]
|
[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"
|
|
||||||
|
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 {
|
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),
|
@ -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()?;
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
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.
|
//! 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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user