diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/builtins.rs | 19 | ||||
| -rw-r--r-- | src/builtins/delsh.rs | 136 | ||||
| -rw-r--r-- | src/builtins/dit.rs | 788 | ||||
| -rw-r--r-- | src/builtins/doer.rs | 31 | ||||
| -rw-r--r-- | src/file.rs | 31 | ||||
| -rw-r--r-- | src/main.rs | 20 | ||||
| -rw-r--r-- | src/pipe.rs | 157 | ||||
| -rw-r--r-- | src/processes.rs | 78 |
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(®ex) +} + +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, ®ex); + 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, ®ex, &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(()) +} |
