summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2025-12-07 14:23:22 -0500
committerMica White <botahamec@outlook.com>2025-12-07 14:23:22 -0500
commit610e575043bfc75feafcce5bddaf7e1a436e5d02 (patch)
tree15149b937984f73feb7bb63be662882094f27abe /src
First commitHEADmain
Diffstat (limited to 'src')
-rw-r--r--src/builtins.rs19
-rw-r--r--src/builtins/delsh.rs136
-rw-r--r--src/builtins/dit.rs788
-rw-r--r--src/builtins/doer.rs31
-rw-r--r--src/file.rs31
-rw-r--r--src/main.rs20
-rw-r--r--src/pipe.rs157
-rw-r--r--src/processes.rs78
8 files changed, 1260 insertions, 0 deletions
diff --git a/src/builtins.rs b/src/builtins.rs
new file mode 100644
index 0000000..0a74265
--- /dev/null
+++ b/src/builtins.rs
@@ -0,0 +1,19 @@
+use std::sync::mpsc::Receiver;
+
+use happylock::ThreadKey;
+
+use crate::pipe::Message;
+
+pub type BuiltinProgram = fn(ThreadKey, Receiver<Message>);
+pub static BUILTINS: &[Option<BuiltinProgram>] =
+ &[None, Some(hello), Some(delsh::delsh), Some(dit::dit)];
+
+mod delsh;
+mod dit;
+mod doer;
+
+fn hello(key: ThreadKey, channel: Receiver<Message>) {
+ println!("Hello, world!");
+ drop(channel);
+ drop(key);
+}
diff --git a/src/builtins/delsh.rs b/src/builtins/delsh.rs
new file mode 100644
index 0000000..6c9883c
--- /dev/null
+++ b/src/builtins/delsh.rs
@@ -0,0 +1,136 @@
+use std::io::Write;
+use std::panic::{RefUnwindSafe, catch_unwind};
+use std::sync::Arc;
+use std::sync::mpsc::Receiver;
+
+use delsh::{builtins, interpreter::Interpreter, interpreter::Value};
+use happylock::ThreadKey;
+use uuid::Uuid;
+
+use crate::pipe::{Message, MessageField};
+use crate::processes::send_message;
+
+macro_rules! add_builtins {
+ ($interpreter: expr => $($name: ident)*) => {
+ $(add_builtin!($interpreter, $name);)*
+ };
+}
+
+macro_rules! add_builtin {
+ ($interpreter: expr, $name: ident) => {
+ add_builtin!($interpreter, stringify!($name), $name);
+ };
+ ($interpreter: expr, $name: expr, $builtin: ident) => {
+ $interpreter.set_atom(
+ $name.to_uppercase().into(),
+ Arc::new(Value::RustFn(builtins::$builtin)),
+ );
+ };
+}
+
+fn panic_handler(f: impl Fn() + RefUnwindSafe) {
+ loop {
+ let result = catch_unwind(&f).unwrap_err();
+ eprintln!("{result:?}");
+ }
+}
+
+fn delsh_loop() {
+ let mut interpreter = Interpreter::new();
+ interpreter.set_atom("T".into(), delsh::builtins::T.clone());
+ interpreter.set_atom("F".into(), delsh::builtins::F.clone());
+ interpreter.set_atom("NIL".into(), delsh::builtins::NIL.clone());
+ add_builtin!(interpreter, "atom?", is_atom);
+ add_builtin!(interpreter, "equal?", is_equal);
+ add_builtin!(interpreter, "==", is_equal);
+ add_builtin!(interpreter, "nil?", is_nil);
+ add_builtin!(interpreter, "member?", is_member);
+ add_builtin!(interpreter, "less?", is_less);
+ add_builtin!(interpreter, "<", is_less);
+ add_builtin!(interpreter, "greater?", is_greater);
+ add_builtin!(interpreter, ">", is_greater);
+ add_builtin!(interpreter, "zero?", is_zero);
+ add_builtin!(interpreter, "one?", is_one);
+ add_builtin!(interpreter, "negative?", is_negative);
+ add_builtin!(interpreter, "number?", is_number);
+ add_builtin!(interpreter, "int?", is_int);
+ add_builtin!(interpreter, "+", plus);
+ add_builtin!(interpreter, "add", plus);
+ add_builtin!(interpreter, "-", minus);
+ add_builtin!(interpreter, "sub", minus);
+ add_builtin!(interpreter, "subtract", minus);
+ add_builtin!(interpreter, "difference", minus);
+ add_builtin!(interpreter, "*", times);
+ add_builtin!(interpreter, "mul", times);
+ add_builtin!(interpreter, "multiply", times);
+ add_builtin!(interpreter, "/", divide);
+ add_builtin!(interpreter, "%", remainder);
+ add_builtin!(interpreter, "**", expt);
+ add_builtin!(interpreter, "if", if_function);
+ add_builtin!(interpreter, "while", while_function);
+ add_builtin!(interpreter, "loop", loop_function);
+ add_builtin!(interpreter, "do", do_function);
+ add_builtins!(interpreter => car cdr cons);
+ add_builtins!(interpreter => car cdr cons ff );
+ add_builtins!(interpreter => cadr cdar caar cddr);
+ add_builtins!(interpreter => caaar caadr cadar caddr cdaar cdadr cddar cdddr);
+ add_builtins!(interpreter => first second third fourth fifth sixth seventh eighth ninth tenth);
+ add_builtins!(interpreter => and or not);
+ add_builtins!(interpreter => pair assoc subst sublis);
+ add_builtins!(interpreter => list reverse append length efface intersection union);
+ add_builtins!(interpreter => defun lambda maplist);
+ add_builtins!(interpreter => plus minus negate times add1 sub1 max min recip quotient remainder divide expt sqrt);
+ add_builtins!(interpreter => quote set eval apply);
+
+ interpreter.set_atom(
+ "SEND-MESSAGE".into(),
+ Arc::new(Value::RustFn(|interpreter, args| {
+ let mut key = ThreadKey::get().unwrap();
+ let program = args[0].string().unwrap().parse::<Uuid>().unwrap();
+ let fields = args
+ .iter()
+ .skip(1)
+ .map(|arg| match &*interpreter.eval(arg.clone()) {
+ Value::Identifier(_)
+ | Value::RustFn(_)
+ | Value::DelshFn { .. }
+ | Value::Pair(..) => MessageField::Empty,
+ Value::Number(number) => {
+ MessageField::Bytes(number.to_string().into_bytes().into())
+ }
+ Value::String(string) => MessageField::Bytes(string.as_bytes().into()),
+ });
+ let message = Message::new(&mut key, fields.collect::<Box<_>>());
+ let response = send_message(&mut key, program, message);
+ let response = response.wait();
+ match response.unwrap() {
+ MessageField::Empty => delsh::builtins::NIL.clone(),
+ MessageField::Bytes(bytes) => {
+ Arc::new(Value::String(String::from_utf8_lossy(bytes).into()))
+ }
+ MessageField::File(_) => todo!(),
+ }
+ })),
+ );
+
+ let mut buffer = String::new();
+ let stdin = std::io::stdin();
+ loop {
+ buffer.clear();
+ print!("$ ");
+ std::io::stdout().flush().unwrap();
+ stdin.read_line(&mut buffer).unwrap();
+ let mut lexer = delsh::tokens::Lexer::new(&buffer).peekable();
+ let program = delsh::ast::parse_program(&mut lexer).unwrap();
+ for command in &*program.commands {
+ let value = interpreter.run_ast_command(command);
+ println!("{value:?}");
+ }
+ }
+}
+
+pub fn delsh(key: ThreadKey, channel: Receiver<Message>) {
+ drop(key);
+ drop(channel);
+ panic_handler(delsh_loop);
+}
diff --git a/src/builtins/dit.rs b/src/builtins/dit.rs
new file mode 100644
index 0000000..d6b1feb
--- /dev/null
+++ b/src/builtins/dit.rs
@@ -0,0 +1,788 @@
+use std::{
+ collections::{HashMap, VecDeque},
+ io::{BufRead, BufReader},
+ num::NonZeroU8,
+ ops::Range,
+ rc::Rc,
+ sync::{LazyLock, RwLock, mpsc::Receiver},
+ vec::Drain,
+};
+
+use deteregex::Regex;
+use happylock::ThreadKey;
+use uuid::Uuid;
+
+use crate::pipe::{Message, MessageField, VirtualFile};
+
+static OPEN_BUFFERS: LazyLock<RwLock<HashMap<Uuid, ProgramState>>> =
+ LazyLock::new(|| RwLock::new(HashMap::new()));
+
+#[derive(Default)]
+struct ProgramState {
+ open_files: HashMap<usize, FileBuffer>,
+ most_recent_file: Option<usize>,
+}
+
+struct FileBuffer {
+ current_line_number: usize,
+ file: Option<Box<dyn VirtualFile>>,
+ lines: Vec<LineBuffer>,
+}
+
+#[derive(Debug, Default, Clone)]
+struct LineBuffer {
+ labels: Vec<String>,
+ content: String,
+}
+
+impl ProgramState {
+ fn next_buffer_id(&mut self) -> usize {
+ let mut id = self.open_files.len();
+ while self.open_files.contains_key(&id) {
+ id = id.wrapping_add(self.open_files.len());
+ }
+
+ self.most_recent_file = Some(id);
+ id
+ }
+
+ fn buffer_mut(&mut self, buffer: Option<usize>) -> Result<&mut FileBuffer, Rc<str>> {
+ let buffer_idx = buffer.or(self.most_recent_file).ok_or("no open file")?;
+ let buffer = self
+ .open_files
+ .get_mut(&buffer_idx)
+ .ok_or("invalid buffer id")?;
+
+ self.most_recent_file = Some(buffer_idx);
+ Ok(buffer)
+ }
+
+ fn close_buffer(&mut self, buffer: Option<usize>) -> Result<(), Rc<str>> {
+ let buffer = buffer.or(self.most_recent_file).ok_or("no open file")?;
+ self.open_files.remove(&buffer).ok_or("invalid buffer id")?;
+ Ok(())
+ }
+}
+
+impl FileBuffer {
+ fn new() -> Self {
+ Self {
+ lines: vec![LineBuffer::new()],
+ current_line_number: 0,
+ file: None,
+ }
+ }
+
+ fn open(mut file: Box<dyn VirtualFile>) -> std::io::Result<Self> {
+ let mut buf = String::new();
+ file.read_to_string(&mut buf)?;
+ let lines = buf.split('\n');
+
+ Ok(Self {
+ lines: lines
+ .map(|line| -> Result<LineBuffer, std::io::Error> {
+ Ok(LineBuffer {
+ labels: Vec::new(),
+ content: line.to_string(),
+ })
+ })
+ .collect::<Result<_, _>>()?,
+ file: Some(file),
+ current_line_number: 0,
+ })
+ }
+
+ fn write(&mut self) -> Result<(), Rc<str>> {
+ let Some(file) = self.file.as_deref_mut() else {
+ return Err("A file must be opened first".into());
+ };
+
+ file.write_all(
+ self.lines
+ .iter()
+ .fold(
+ String::with_capacity(
+ self.lines.iter().map(|line| line.content.len() + 1).sum(),
+ ),
+ |mut output, line| {
+ output.push('\n');
+ output.push_str(&line.content);
+ output
+ },
+ )
+ .as_bytes(),
+ )
+ .map_err(|e| e.to_string().into())
+ }
+
+ fn get_line_index(&self, index: &LineIndex) -> Result<usize, Rc<str>> {
+ match index {
+ LineIndex::Current => Ok(self.current_line_number),
+ LineIndex::Last => (!self.lines.is_empty())
+ .then_some(self.lines.len() - 1)
+ .ok_or("file is empty".into()),
+ LineIndex::Number(i) => usize::try_from(*i).map_err(|e| e.to_string().into()),
+ LineIndex::CurrentPlus(amount) => {
+ TryInto::<usize>::try_into(self.current_line_number as i32 + amount)
+ .map_err(|e| e.to_string().into())
+ }
+ LineIndex::Bookmark(label) => self
+ .lines
+ .iter()
+ .enumerate()
+ .find(|(_i, line)| line.labels.contains(label))
+ .map(|(i, _line)| i)
+ .ok_or(format!("bookmark with label \"{label}\" does not exist").into()),
+ LineIndex::FirstRegex(regex) => self
+ .lines
+ .iter()
+ .enumerate()
+ .skip(self.current_line_number + 1)
+ .chain(
+ self.lines
+ .iter()
+ .enumerate()
+ .take(self.current_line_number + 1),
+ )
+ .find(|(_i, line)| regex.is_match(&line.content))
+ .map(|(i, _line)| i)
+ .ok_or("no match found".to_string().into()),
+ LineIndex::LastRegex(regex) => self
+ .lines
+ .iter()
+ .enumerate()
+ .take(self.current_line_number)
+ .chain(self.lines.iter().enumerate().skip(self.current_line_number))
+ .rev()
+ .find(|(_i, line)| regex.is_match(&line.content))
+ .map(|(i, _line)| i)
+ .ok_or("no match found".into()),
+ }
+ }
+
+ fn get_line_range(&self, range: &LineRange) -> Result<Range<usize>, Rc<str>> {
+ Ok(self.get_line_index(&range.start)?..self.get_line_index(&range.end)?)
+ }
+
+ fn set_file(&mut self, file: Box<dyn VirtualFile>) {
+ self.file = Some(file);
+ }
+
+ fn current_line_number(&self) -> usize {
+ self.current_line_number
+ }
+
+ fn set_current_line(&mut self, index: usize) {
+ self.current_line_number = index;
+ }
+
+ fn add_at(&mut self, index: usize, content: impl AsRef<str>) -> usize {
+ let new_lines = LineBuffer::from_str(content.as_ref()).collect::<Vec<_>>();
+ let new_lines_len = new_lines.len();
+ self.lines.splice(index..index, new_lines);
+
+ new_lines_len
+ }
+
+ fn find(&self, range: Range<usize>, regex: &Regex) -> impl Iterator<Item = usize> {
+ self.lines[range]
+ .iter()
+ .enumerate()
+ .filter_map(|(i, line)| regex.is_match(&line.content).then_some(i))
+ }
+
+ fn replace_all(&mut self, range: Range<usize>, pattern: &Regex, replacement: &str) {
+ for line in &mut self.lines[range] {
+ line.replace_all(pattern, replacement);
+ }
+ }
+
+ fn append(&mut self, index: usize, content: impl AsRef<str>) {
+ let lines_added = self.add_at(index + 1, content);
+ self.current_line_number = usize::max(index + lines_added, self.lines.len());
+ }
+
+ fn change(&mut self, range: Range<usize>, replacement: impl AsRef<str>) {
+ self.delete(range.clone());
+ self.insert(range.start, replacement);
+ }
+
+ fn delete(&mut self, range: Range<usize>) -> Drain<'_, LineBuffer> {
+ self.current_line_number = usize::max(range.start, self.lines.len() - range.len());
+ self.lines.drain(range.clone())
+ }
+
+ fn insert(&mut self, index: usize, content: impl AsRef<str>) {
+ let lines_added = self.add_at(index, content);
+ self.current_line_number = index + lines_added - 1;
+ }
+
+ fn join(&mut self, range: Range<usize>) {
+ let deleted_lines = self.delete(range.clone());
+ let line = deleted_lines.fold(
+ LineBuffer {
+ labels: Vec::new(),
+ content: String::new(),
+ },
+ |mut acc, line| {
+ acc.labels.extend(line.labels);
+ acc.content.push_str(&line.content);
+ acc
+ },
+ );
+
+ self.lines.insert(range.start, line);
+
+ if range.len() > 1 {
+ self.current_line_number = range.start;
+ }
+ }
+
+ fn bookmark(&mut self, index: usize, label: String) {
+ self.lines[index].add_label(label);
+ }
+
+ fn copy(&mut self, range: Range<usize>, index: usize) {
+ let new_lines = self.lines[range.clone()]
+ .iter()
+ .cloned()
+ .map(|mut line| {
+ line.labels.clear();
+ line
+ })
+ .collect::<Vec<_>>();
+ self.lines.splice(index..index, new_lines);
+ self.current_line_number = index + range.len();
+ }
+
+ fn move_lines(&mut self, range: Range<usize>, index: usize) {
+ let lines = self.delete(range.clone()).collect::<Vec<_>>();
+ self.lines.splice(index..index, lines);
+ self.current_line_number = index + range.len();
+ }
+
+ fn read_lines(&self, range: Range<usize>) -> impl Iterator<Item = &str> {
+ self.lines[range].iter().map(|line| line.content.as_str())
+ }
+}
+
+impl LineBuffer {
+ fn new() -> Self {
+ Self {
+ labels: Vec::new(),
+ content: String::new(),
+ }
+ }
+
+ fn from_str(content: &str) -> impl Iterator<Item = Self> {
+ content.split("\n").map(|line| Self {
+ labels: Vec::new(),
+ content: line.into(),
+ })
+ }
+
+ fn add_label(&mut self, label: String) {
+ self.labels.push(label)
+ }
+
+ fn replace_all(&mut self, pattern: &Regex, replacement: &str) {
+ self.content = pattern.replace_all(&self.content, replacement).into_owned()
+ }
+}
+
+enum LineIndex {
+ Current,
+ Last,
+ Number(u32),
+ CurrentPlus(i32),
+ Bookmark(String),
+ FirstRegex(Regex),
+ LastRegex(Regex),
+}
+
+struct LineRange {
+ start: LineIndex,
+ end: LineIndex,
+}
+
+enum Command {
+ Append {
+ position: LineIndex,
+ content: String,
+ buffer: Option<usize>,
+ },
+ Change {
+ range: LineRange,
+ content: String,
+ buffer: Option<usize>,
+ },
+ Delete {
+ range: LineRange,
+ buffer: Option<usize>,
+ },
+ Open {
+ file: Box<dyn VirtualFile>,
+ },
+ New {},
+ SetFile {
+ file: Box<dyn VirtualFile>,
+ buffer: Option<usize>,
+ },
+ Find {
+ range: LineRange,
+ regex: Regex,
+ buffer: Option<usize>,
+ },
+ Insert {
+ position: LineIndex,
+ content: String,
+ buffer: Option<usize>,
+ },
+ Join {
+ range: LineRange,
+ buffer: Option<usize>,
+ },
+ Bookmark {
+ position: LineIndex,
+ name: String,
+ buffer: Option<usize>,
+ },
+ Move {
+ range: LineRange,
+ position: LineIndex,
+ buffer: Option<usize>,
+ },
+ Read {
+ range: LineRange,
+ buffer: Option<usize>,
+ },
+ Substitute {
+ range: LineRange,
+ regex: Regex,
+ content: String,
+ buffer: Option<usize>,
+ },
+ Copy {
+ range: LineRange,
+ position: LineIndex,
+ buffer: Option<usize>,
+ },
+ Undo {
+ buffer: Option<usize>,
+ },
+ Write {
+ buffer: Option<usize>,
+ },
+ Close {
+ buffer: Option<usize>,
+ },
+ CurrentLineNumber {
+ buffer: Option<usize>,
+ },
+ Jump {
+ position: LineIndex,
+ buffer: Option<usize>,
+ },
+}
+
+fn parse_line_index(string: &str) -> Result<LineIndex, Rc<str>> {
+ if string == "." {
+ Ok(LineIndex::Current)
+ } else if string == "$" {
+ Ok(LineIndex::Last)
+ } else if string.starts_with("+") || string.starts_with("-") {
+ Ok(LineIndex::CurrentPlus(
+ string.parse::<i32>().map_err(|e| e.to_string())?,
+ ))
+ } else if let Ok(number) = string.parse::<u32>() {
+ Ok(LineIndex::Number(number))
+ } else if let Some(bookmark) = string.strip_prefix("'") {
+ Ok(LineIndex::Bookmark(bookmark.to_string()))
+ } else if let Some(regex) = string.strip_prefix("/") {
+ Ok(LineIndex::FirstRegex(Regex::new(regex)?))
+ } else if let Some(regex) = string.strip_prefix("?") {
+ Ok(LineIndex::LastRegex(Regex::new(regex)?))
+ } else {
+ Err("invalid line".into())
+ }
+}
+
+fn expect_field(
+ message: &mut impl Iterator<Item = MessageField>,
+ error: &str,
+) -> Result<MessageField, Rc<str>> {
+ match message.next() {
+ Some(field) => Ok(field),
+ None => Err(error.into()),
+ }
+}
+
+fn field_to_string(field: &MessageField) -> Result<&str, Rc<str>> {
+ let Some(field) = field.string() else {
+ return Err("command must be a string".into());
+ };
+ match field {
+ Ok(field) => Ok(field),
+ Err(error) => Err(error.to_string().into()),
+ }
+}
+
+fn expect_position(message: &mut impl Iterator<Item = MessageField>) -> Result<LineIndex, Rc<str>> {
+ parse_line_index(field_to_string(&expect_field(
+ message,
+ "expected a line specifier",
+ )?)?)
+}
+
+fn expect_range(message: &mut impl Iterator<Item = MessageField>) -> Result<LineRange, Rc<str>> {
+ Ok(LineRange {
+ start: expect_position(message)?,
+ end: expect_position(message)?,
+ })
+}
+
+fn expect_content(message: &mut impl Iterator<Item = MessageField>) -> Result<String, Rc<str>> {
+ Ok(field_to_string(&expect_field(message, "expected a string argument")?)?.to_string())
+}
+
+fn expect_file(
+ message: &mut impl Iterator<Item = MessageField>,
+) -> Result<Box<dyn VirtualFile>, Rc<str>> {
+ let file = expect_field(message, "expected a file")?;
+ match file {
+ MessageField::File(file) => Ok(file),
+ _ => Err("expected a file".into()),
+ }
+}
+
+fn expect_regex(message: &mut impl Iterator<Item = MessageField>) -> Result<Regex, Rc<str>> {
+ let regex = expect_content(message)?;
+ Regex::new(&regex)
+}
+
+fn expect_buffer(
+ message: &mut impl Iterator<Item = MessageField>,
+) -> Result<Option<usize>, Rc<str>> {
+ message
+ .next()
+ .map(|field| {
+ field_to_string(&field)?
+ .parse::<usize>()
+ .map_err(|e| e.to_string().into())
+ })
+ .transpose()
+}
+
+fn parse_command(mut message: impl Iterator<Item = MessageField>) -> Result<Command, Rc<str>> {
+ let command = expect_field(&mut message, "no command provided")?;
+ let command = field_to_string(&command)?;
+
+ if command == "append" {
+ let position = expect_position(&mut message)?;
+ let content = expect_content(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Append {
+ position,
+ content,
+ buffer,
+ })
+ } else if command == "change" {
+ let range = expect_range(&mut message)?;
+ let content = expect_content(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Change {
+ range,
+ content,
+ buffer,
+ })
+ } else if command == "delete" {
+ let range = expect_range(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Delete { range, buffer })
+ } else if command == "open" {
+ let file = expect_file(&mut message)?;
+ Ok(Command::Open { file })
+ } else if command == "new" {
+ Ok(Command::New {})
+ } else if command == "setfile" {
+ let file = expect_file(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::SetFile { file, buffer })
+ } else if command == "find" {
+ let range = expect_range(&mut message)?;
+ let regex = expect_regex(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Find {
+ range,
+ regex,
+ buffer,
+ })
+ } else if command == "insert" {
+ let position = expect_position(&mut message)?;
+ let content = expect_content(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Insert {
+ position,
+ content,
+ buffer,
+ })
+ } else if command == "join" {
+ let range = expect_range(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Join { range, buffer })
+ } else if command == "bookmark" {
+ let position = expect_position(&mut message)?;
+ let name = expect_content(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Bookmark {
+ position,
+ name,
+ buffer,
+ })
+ } else if command == "move" {
+ let range = expect_range(&mut message)?;
+ let position = expect_position(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Move {
+ range,
+ position,
+ buffer,
+ })
+ } else if command == "read" {
+ let range = expect_range(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Read { range, buffer })
+ } else if command == "substitute" {
+ let range = expect_range(&mut message)?;
+ let regex = expect_regex(&mut message)?;
+ let content = expect_content(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Substitute {
+ range,
+ regex,
+ content,
+ buffer,
+ })
+ } else if command == "copy" {
+ let range = expect_range(&mut message)?;
+ let position = expect_position(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Copy {
+ range,
+ position,
+ buffer,
+ })
+ } else if command == "undo" {
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Undo { buffer })
+ } else if command == "write" {
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Write { buffer })
+ } else if command == "close" {
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Close { buffer })
+ } else if command == "linenumber" {
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::CurrentLineNumber { buffer })
+ } else if command == "jump" {
+ let position = expect_position(&mut message)?;
+ let buffer = expect_buffer(&mut message)?;
+ Ok(Command::Jump { position, buffer })
+ } else {
+ Err(format!("{command} is not a valid command").into())
+ }
+}
+
+enum CommandResponse {
+ Content(Box<[String]>),
+ Number(usize),
+ LineNumbers(Box<[usize]>),
+ Empty,
+}
+
+fn run_command(
+ command: Command,
+ program_state: &mut ProgramState,
+) -> Result<CommandResponse, Rc<str>> {
+ match command {
+ Command::Append {
+ position,
+ content,
+ buffer,
+ } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let position = buffer.get_line_index(&position)?;
+ buffer.append(position, content);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Change {
+ range,
+ content,
+ buffer,
+ } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let range = buffer.get_line_range(&range)?;
+ buffer.change(range, content);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Delete { range, buffer } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let range = buffer.get_line_range(&range)?;
+ buffer.delete(range);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Open { file } => {
+ let id = program_state.next_buffer_id();
+ let buffer = FileBuffer::open(file).map_err(|e| e.to_string())?;
+ program_state.open_files.insert(id, buffer);
+ Ok(CommandResponse::Number(id))
+ }
+ Command::New {} => {
+ let id = program_state.next_buffer_id();
+ let buffer = FileBuffer::new();
+ program_state.open_files.insert(id, buffer);
+ Ok(CommandResponse::Number(id))
+ }
+ Command::SetFile { file, buffer } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ buffer.set_file(file);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Find {
+ range,
+ regex,
+ buffer,
+ } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let range = buffer.get_line_range(&range)?;
+ let matches = buffer.find(range, &regex);
+ Ok(CommandResponse::LineNumbers(matches.collect()))
+ }
+ Command::Insert {
+ position,
+ content,
+ buffer,
+ } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let position = buffer.get_line_index(&position)?;
+ buffer.insert(position, content);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Join { range, buffer } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let range = buffer.get_line_range(&range)?;
+ buffer.join(range);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Bookmark {
+ position,
+ name,
+ buffer,
+ } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let position = buffer.get_line_index(&position)?;
+ buffer.bookmark(position, name);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Move {
+ range,
+ position,
+ buffer,
+ } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let range = buffer.get_line_range(&range)?;
+ let position = buffer.get_line_index(&position)?;
+
+ if range.contains(&position) {
+ return Err("the range of moved lines contains the new position".into());
+ }
+
+ buffer.move_lines(range, position);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Read { range, buffer } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let range = buffer.get_line_range(&range)?;
+ Ok(CommandResponse::Content(
+ buffer.read_lines(range).map(ToOwned::to_owned).collect(),
+ ))
+ }
+ Command::Substitute {
+ range,
+ regex,
+ content,
+ buffer,
+ } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let range = buffer.get_line_range(&range)?;
+ buffer.replace_all(range, &regex, &content);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Copy {
+ range,
+ position,
+ buffer,
+ } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let range = buffer.get_line_range(&range)?;
+ let position = buffer.get_line_index(&position)?;
+ buffer.copy(range, position);
+ Ok(CommandResponse::Empty)
+ }
+ Command::Undo { .. } => Err("Undo is not yet supported".into()),
+ Command::Write { buffer } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ buffer.write().map_err(|e| e.to_string())?;
+ Ok(CommandResponse::Empty)
+ }
+ Command::Close { buffer } => {
+ program_state.close_buffer(buffer)?;
+ Ok(CommandResponse::Empty)
+ }
+ Command::CurrentLineNumber { buffer } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ Ok(CommandResponse::Number(buffer.current_line_number()))
+ }
+ Command::Jump { position, buffer } => {
+ let buffer = program_state.buffer_mut(buffer)?;
+ let position = buffer.get_line_index(&position)?;
+ buffer.set_current_line(position);
+ Ok(CommandResponse::Empty)
+ }
+ }
+}
+
+pub fn dit(mut key: ThreadKey, channel: Receiver<Message>) {
+ for message in channel {
+ let sending_program = message.sending_program;
+ let message_fields = message.fields.into_iter();
+ let return_space = message.return_space;
+
+ let mut buffers = OPEN_BUFFERS.write().unwrap();
+ let program_state = buffers.entry(sending_program).or_default();
+ let command = parse_command(message_fields);
+ let response = command.and_then(|command| run_command(command, program_state));
+ match response {
+ Ok(CommandResponse::Empty) => return_space.respond_ok(MessageField::Empty),
+ Ok(CommandResponse::Content(string)) => {
+ return_space.respond_ok(MessageField::from(string.join("\n").as_str()))
+ }
+ Ok(CommandResponse::Number(number)) => {
+ return_space.respond_ok(MessageField::from(number.to_string().as_str()));
+ }
+ Ok(CommandResponse::LineNumbers(numbers)) => {
+ return_space.respond_ok(MessageField::from(
+ numbers
+ .iter()
+ .fold(String::new(), |mut output, num| {
+ output.push_str(&num.to_string());
+ output.push(',');
+ output
+ })
+ .as_str(),
+ ))
+ }
+ Err(error) => return_space.respond_err(NonZeroU8::MIN, MessageField::from(&*error)),
+ }
+ }
+}
diff --git a/src/builtins/doer.rs b/src/builtins/doer.rs
new file mode 100644
index 0000000..8df1453
--- /dev/null
+++ b/src/builtins/doer.rs
@@ -0,0 +1,31 @@
+use std::path::Path;
+
+use happylock::ThreadKey;
+use uuid::Uuid;
+
+use crate::file;
+
+pub enum Package {
+ Program(Program),
+}
+
+pub struct Program {
+ id: Uuid,
+ name: String,
+
+ interpreter: Uuid,
+ main_file: Uuid,
+ modules: Box<[Uuid]>,
+}
+
+pub fn package_metadata(key: &mut ThreadKey, program_id: Uuid) -> Option<Program> {
+ let file = file::open(key, program_id)?;
+}
+
+pub fn program_code(key: &mut ThreadKey, program_id: Uuid) -> Option<Box<[u8]>> {
+ todo!()
+}
+
+pub fn program_file(key: &mut ThreadKey, program_id: Uuid, module: &Path) -> Option<Box<[u8]>> {
+ todo!()
+}
diff --git a/src/file.rs b/src/file.rs
new file mode 100644
index 0000000..2e69cdd
--- /dev/null
+++ b/src/file.rs
@@ -0,0 +1,31 @@
+use std::{
+ fs::File,
+ path::{Path, PathBuf},
+};
+
+use happylock::ThreadKey;
+use uuid::Uuid;
+
+use crate::processes::process_id;
+
+fn root() -> &'static Path {
+ Path::new("~/.dozos")
+}
+
+fn path(root: &Path, program_id: Uuid, file_id: Uuid) -> PathBuf {
+ let mut builder = PathBuf::from(root);
+ builder.push(program_id.to_string());
+ builder.push(file_id.to_string());
+
+ builder
+}
+
+pub fn open(key: &mut ThreadKey, file_id: Uuid) -> std::io::Result<File> {
+ let path = path(root(), process_id(key), file_id);
+ File::options()
+ .read(true)
+ .write(true)
+ .create(true)
+ .truncate(false)
+ .open(path)
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..56066a5
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,20 @@
+use std::time::Duration;
+
+use happylock::ThreadKey;
+use uuid::Uuid;
+
+mod builtins;
+mod file;
+mod pipe;
+mod processes;
+
+use processes::start_builtin;
+
+fn main() {
+ let mut key = ThreadKey::get().unwrap();
+ start_builtin(&mut key, Uuid::from_u128(2));
+
+ loop {
+ std::thread::sleep(Duration::from_millis(350));
+ }
+}
diff --git a/src/pipe.rs b/src/pipe.rs
new file mode 100644
index 0000000..a140ffd
--- /dev/null
+++ b/src/pipe.rs
@@ -0,0 +1,157 @@
+use std::fmt::Debug;
+use std::io::{Read, Write};
+use std::num::NonZeroU8;
+use std::str::Utf8Error;
+use std::sync::{Arc, OnceLock};
+
+use happylock::ThreadKey;
+use uuid::Uuid;
+
+use crate::processes::process_id;
+
+pub trait VirtualFile: Read + Write + Debug + Send + Sync {}
+impl<T: Read + Write + Debug + Send + Sync> VirtualFile for T {}
+
+#[derive(Debug)]
+pub enum MessageField {
+ File(Box<dyn VirtualFile>),
+ Bytes(Box<[u8]>),
+ Empty,
+}
+
+#[derive(Debug)]
+struct ReturnSpace {
+ message: MessageField,
+ error_code: Option<NonZeroU8>,
+}
+
+#[derive(Debug, Clone)]
+pub struct SharedReturnSpace(Arc<OnceLock<ReturnSpace>>);
+
+#[derive(Debug)]
+pub struct Message {
+ pub sending_program: Uuid,
+ pub fields: Box<[MessageField]>,
+ pub return_space: SharedReturnSpace,
+}
+
+#[derive(Debug)]
+pub struct MessageResponse {
+ return_space: SharedReturnSpace,
+}
+
+#[derive(Debug)]
+pub struct MessageError<'a> {
+ error_code: u8,
+ message: &'a MessageField,
+}
+
+impl Message {
+ pub fn new(key: &mut ThreadKey, fields: impl Into<Box<[MessageField]>>) -> Self {
+ Self {
+ sending_program: process_id(key),
+ fields: fields.into(),
+ return_space: SharedReturnSpace(Arc::new(OnceLock::new())),
+ }
+ }
+
+ pub fn fields(&self) -> &[MessageField] {
+ &self.fields
+ }
+
+ pub fn sending_program(&self) -> Uuid {
+ self.sending_program
+ }
+
+ pub fn respond_ok(self, value: MessageField) {
+ self.return_space.respond_ok(value)
+ }
+
+ pub fn respond_err(self, error_code: NonZeroU8, error_message: MessageField) {
+ self.return_space.respond_err(error_code, error_message);
+ }
+}
+
+impl From<&[u8]> for MessageField {
+ fn from(value: &[u8]) -> Self {
+ Self::Bytes(value.into())
+ }
+}
+
+impl From<&str> for MessageField {
+ fn from(value: &str) -> Self {
+ Self::Bytes(value.as_bytes().into())
+ }
+}
+
+impl MessageField {
+ pub fn string(&self) -> Option<Result<&str, Utf8Error>> {
+ if let Self::Bytes(bytes) = self {
+ Some(str::from_utf8(bytes))
+ } else {
+ None
+ }
+ }
+}
+
+impl ReturnSpace {
+ fn as_result<'a>(&'a self) -> Result<&'a MessageField, MessageError<'a>> {
+ match self.error_code {
+ None => Ok(&self.message),
+ Some(error_code) => Err(MessageError {
+ error_code: error_code.get(),
+ message: &self.message,
+ }),
+ }
+ }
+}
+
+impl SharedReturnSpace {
+ fn get(&self) -> Option<&ReturnSpace> {
+ self.0.get()
+ }
+
+ fn wait(&self) -> &ReturnSpace {
+ self.0.wait()
+ }
+
+ pub fn respond_ok(self, value: MessageField) {
+ debug_assert!(
+ self.0
+ .set(ReturnSpace {
+ message: value,
+ error_code: None
+ })
+ .is_ok(),
+ "it should not be possible to call this function twice"
+ );
+ }
+
+ pub fn respond_err(self, error_code: NonZeroU8, error_message: MessageField) {
+ debug_assert!(
+ self.0
+ .set(ReturnSpace {
+ message: error_message,
+ error_code: Some(error_code)
+ })
+ .is_ok(),
+ "it should not be possible to call this function twice"
+ );
+ }
+}
+
+impl MessageResponse {
+ pub fn from_message(message: &Message) -> Self {
+ Self {
+ return_space: message.return_space.clone(),
+ }
+ }
+
+ pub fn poll<'a>(&'a self) -> Option<Result<&'a MessageField, MessageError<'a>>> {
+ self.return_space.get().map(ReturnSpace::as_result)
+ }
+
+ pub fn wait<'a>(&'a self) -> Result<&'a MessageField, MessageError<'a>> {
+ self.return_space.wait().as_result()
+ }
+}
diff --git a/src/processes.rs b/src/processes.rs
new file mode 100644
index 0000000..7ac326f
--- /dev/null
+++ b/src/processes.rs
@@ -0,0 +1,78 @@
+use std::collections::HashMap;
+use std::sync::{LazyLock, RwLock, mpsc};
+use std::thread::JoinHandle;
+
+use happylock::ThreadKey;
+use uuid::Uuid;
+
+use crate::pipe::{Message, MessageResponse};
+
+thread_local! {
+ static PROCESS_ID: RwLock<Uuid> = const { RwLock::new(Uuid::nil()) };
+}
+
+static RUNNING_PROCESSES: LazyLock<RwLock<HashMap<Uuid, Process>>> =
+ LazyLock::new(|| RwLock::new(HashMap::new()));
+
+#[derive(Debug)]
+pub struct Process {
+ id: Uuid,
+ thread: JoinHandle<()>,
+ channel: mpsc::Sender<Message>,
+}
+
+pub fn is_builtin(program_id: Uuid) -> bool {
+ program_id.as_u64_pair().0 == 0
+}
+
+pub fn process_id(key: &mut ThreadKey) -> Uuid {
+ PROCESS_ID.with(|id| *id.read().unwrap())
+}
+
+pub fn send_message(key: &mut ThreadKey, program_id: Uuid, message: Message) -> MessageResponse {
+ start_program(key, program_id);
+
+ let programs = RUNNING_PROCESSES.read().unwrap();
+ // TODO: this can crash if the program runs and exits immediately
+ let program = programs.get(&program_id).unwrap();
+
+ let response = MessageResponse::from_message(&message);
+ _ = program.channel.send(message);
+ response
+}
+
+pub fn start_builtin_as(key: &mut ThreadKey, package_id: Uuid, as_program: Uuid) -> Option<()> {
+ let mut processes = RUNNING_PROCESSES.write().unwrap();
+ if processes.contains_key(&as_program) {
+ return Some(());
+ }
+
+ let builtin_index = package_id.as_u64_pair().1 as usize;
+ let func = crate::builtins::BUILTINS.get(builtin_index)?.as_ref()?;
+ let (sender, receiver) = mpsc::channel();
+ let handle = std::thread::spawn(move || {
+ let key = ThreadKey::get().expect("the thread just started");
+ PROCESS_ID.with(|id| {
+ *id.write().unwrap() = as_program;
+ });
+ func(key, receiver);
+ });
+ let process = Process {
+ id: as_program,
+ thread: handle,
+ channel: sender,
+ };
+ processes.insert(as_program, process);
+
+ Some(())
+}
+
+pub fn start_builtin(key: &mut ThreadKey, package_id: Uuid) -> Option<()> {
+ start_builtin_as(key, package_id, package_id)
+}
+
+pub fn start_program(key: &mut ThreadKey, package_id: Uuid) -> Option<()> {
+ start_builtin(key, package_id)?;
+
+ Some(())
+}