diff --git a/builder/src/main.rs b/builder/src/main.rs index c028098..01f0cf9 100644 --- a/builder/src/main.rs +++ b/builder/src/main.rs @@ -95,6 +95,12 @@ fn qemu() -> Result<()> { } else if system_code.exists() && system_vars.exists() { println!("Using system OVFM"); + // We need to ensure that ovmf folder exists + DirBuilder::new() + .recursive(true) + .create("ovmf") + .context("Failed to create ovmf folder")?; + // QEMU will make changes to this file, so we need a local copy. We do // not want to overwrite it every build if !local_vars.exists() { diff --git a/kernel_userspace/Cargo.toml b/kernel_userspace/Cargo.toml index 58d653b..038821a 100644 --- a/kernel_userspace/Cargo.toml +++ b/kernel_userspace/Cargo.toml @@ -9,5 +9,7 @@ edition = "2021" postcard = { version = "1.0.4", features = ["alloc"] } serde = { version = "1.0.*", default-features = false, features = ["derive"] } input = { path = "../input" } +lazy_static = { version = "1.4", features = ["spin_no_std"] } + conquer-once = {version = "0.4", default-features = false} thiserror = { version = "1.0", package = "thiserror-core", default-features = false } diff --git a/kernel_userspace/src/fs.rs b/kernel_userspace/src/fs.rs index c473697..646c962 100644 --- a/kernel_userspace/src/fs.rs +++ b/kernel_userspace/src/fs.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use thiserror::Error; use alloc::{ boxed::Box, @@ -22,10 +23,13 @@ pub enum FSServiceMessage<'a> { GetDisksRequest, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Error)] pub enum FSServiceError { + #[error("No such partition: {0}")] NoSuchPartition(u64), + #[error("Could not follow path")] CouldNotFollowPath, + #[error("File not found")] FileNotFound, } diff --git a/kernel_userspace/src/service.rs b/kernel_userspace/src/service.rs index e038f6b..5ae0524 100644 --- a/kernel_userspace/src/service.rs +++ b/kernel_userspace/src/service.rs @@ -2,6 +2,7 @@ use core::sync::atomic::AtomicU64; use alloc::vec::Vec; use serde::{Deserialize, Serialize}; +use thiserror::Error; use crate::{ ids::{ProcessID, ServiceID}, @@ -37,13 +38,19 @@ pub enum SendServiceMessageDest { ToSubscribers, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Error)] pub enum SendError { + #[error("TODO")] Ok, + #[error("TODO")] ParseError, + #[error("TODO")] NoSuchService, + #[error("TODO")] NotYourPID, + #[error("TODO")] TargetNotExists, + #[error("TODO")] FailedToDecodeResponse, } diff --git a/terminal/Cargo.toml b/terminal/Cargo.toml index a128983..dd125da 100644 --- a/terminal/Cargo.toml +++ b/terminal/Cargo.toml @@ -11,6 +11,9 @@ userspace = { path = "../userspace" } kernel_userspace = { path = "../kernel_userspace" } input = { path = "../input" } +thiserror = { version = "1.0", package = "thiserror-core", default-features = false } +hashbrown = "0.14" + [profile.dev] strip = true opt-level = 1 \ No newline at end of file diff --git a/terminal/src/error.rs b/terminal/src/error.rs new file mode 100644 index 0000000..8f83b98 --- /dev/null +++ b/terminal/src/error.rs @@ -0,0 +1,93 @@ +//! This is my own little version of anyhow, but with a lot more control and +//! less std dependencies + +use core::{ + error::Error, + fmt::{Debug, Display}, +}; + +extern crate alloc; +use alloc::{ + boxed::Box, + string::{String, ToString}, + vec::Vec, +}; + +pub type Result = core::result::Result; + +pub trait Context { + fn context(self, context: C) -> Result + where + C: Display; + + fn with_context(self, f: F) -> Result + where + C: Display, + F: FnOnce() -> C; +} + +impl Context for Result { + fn context(self, context: C) -> Result + where + C: Display, + { + self.map_err(|e| e.push_context(context.to_string())) + } + + fn with_context(self, f: F) -> Result + where + C: Display, + F: FnOnce() -> C, + { + self.map_err(|e| e.push_context(f().to_string())) + } +} + +/// This is an error that can store a bunch of extra context information +pub struct InternalError { + primary: Box, + context: Vec, +} + +impl InternalError { + pub fn new(primary: Box, context: Vec) -> Self { + Self { primary, context } + } + + pub fn push_context(mut self, context: String) -> Self { + self.context.push(context); + self + } +} + +impl Debug for InternalError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + (self as &dyn Display).fmt(f) + } +} + +impl Display for InternalError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Error: ")?; + f.write_str(&self.primary.to_string())?; + + if self.context.len() != 0 { + f.write_str("\n\nCaused by:")?; + for ctx in self.context.iter() { + f.write_str("\n\t")?; + f.write_str(&ctx)?; + } + } + + Ok(()) + } +} + +impl From for InternalError +where + E: Error + Sized + 'static, +{ + fn from(value: E) -> Self { + InternalError::new(Box::new(value), Vec::new()) + } +} diff --git a/terminal/src/fns.rs b/terminal/src/fns.rs new file mode 100644 index 0000000..e6c1559 --- /dev/null +++ b/terminal/src/fns.rs @@ -0,0 +1,124 @@ +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use kernel_userspace::fs::{self, add_path, get_disks, read_file_sector, StatResponse}; +use terminal::error::Result; +use terminal::script::execute::{args_to_string, execute_expr}; +use terminal::script::{execute::Value, parser::Expr, Environment}; +use thiserror::Error; +use userspace::{print, println}; + +pub fn pwd<'a>(env: &mut Environment<'a>, _args: Vec) -> Result { + println!("{}", env.cwd); + Ok(Value::Null) +} + +pub fn echo<'a>(env: &mut Environment<'a>, args: Vec) -> Result { + if args.len() == 0 { + println!("ECHO!"); + } else { + println!("{}", execute_expr(&args[0], env)?); + } + + Ok(Value::Null) +} + +pub fn disk<'a>(env: &mut Environment<'a>, args: Vec) -> Result { + if args.len() == 1 { + if let Some(new_id) = execute_expr(&args[0], env)? + .to_string() + .chars() + .next() + .and_then(|c| c.to_digit(10)) + { + env.partition_id = new_id as u64; + return Ok(Value::Null); + } + } + + let fs_sid = env.services.ok_or(FuncExecError::UninitedService)?.fs; + + println!("Drives:"); + for part in get_disks(fs_sid, env.services_buffer()?)?.iter() { + println!("{}:", part) + } + + Ok(Value::Null) +} + +pub fn ls<'a>(env: &mut Environment<'a>, args: Vec) -> Result { + let fs_sid = env.services.ok_or(FuncExecError::UninitedService)?.fs; + + let path = add_path(&env.cwd.clone(), &args_to_string(args, env)?); + + let stat = fs::stat( + fs_sid, + env.partition_id as usize, + path.as_str(), + env.services_buffer()?, + )?; + + match stat { + StatResponse::File(_) => println!("This is a file"), + StatResponse::Folder(c) => { + for child in c.children { + println!("{child}") + } + } + }; + + Ok(Value::Null) +} + +pub fn cd<'a>(env: &mut Environment<'a>, args: Vec) -> Result { + env.cwd = add_path(&env.cwd.clone(), &args_to_string(args, env)?); + Ok(Value::Null) +} + +pub fn cat<'a>(env: &mut Environment<'a>, args: Vec) -> Result { + let fs_sid = env.services.ok_or(FuncExecError::UninitedService)?.fs; + + for file in args { + let file = execute_expr(&file, env)?.to_string(); + + let path = add_path(&env.cwd, &file); + + let stat = fs::stat( + fs_sid, + env.partition_id as usize, + path.as_str(), + env.services_buffer()?, + )?; + + let file = match stat { + StatResponse::File(f) => f, + StatResponse::Folder(_) => { + println!("Not a file"); + continue; + } + }; + + for i in 0..file.file_size / 512 { + let sect = read_file_sector( + fs_sid, + env.partition_id as usize, + file.node_id, + i as u32, + env.services_buffer()?, + )?; + if let Some(data) = sect { + print!("{}", String::from_utf8_lossy(data)) + } else { + print!("Error reading"); + break; + } + } + } + + Ok(Value::Null) +} + +#[derive(Debug, Clone, Error)] +pub enum FuncExecError { + #[error("Service was not initialized")] + UninitedService, +} diff --git a/terminal/src/lib.rs b/terminal/src/lib.rs new file mode 100644 index 0000000..7707ebb --- /dev/null +++ b/terminal/src/lib.rs @@ -0,0 +1,6 @@ +#![no_std] +#![feature(error_in_core)] +#![feature(let_chains)] + +pub mod error; +pub mod script; diff --git a/terminal/src/main.rs b/terminal/src/main.rs index 34f63e9..34b124a 100644 --- a/terminal/src/main.rs +++ b/terminal/src/main.rs @@ -1,13 +1,14 @@ #![no_std] #![no_main] +#![feature(error_in_core)] use kernel_userspace::{ - elf::spawn_elf_process, - fs::{self, add_path, get_disks, read_file_sector, read_full_file, StatResponse}, ids::ServiceID, - service::get_public_service_id, syscall::{exit, receive_service_message_blocking, service_subscribe}, }; +use terminal::script::{execute, Environment}; + +mod fns; extern crate alloc; #[macro_use] @@ -94,22 +95,30 @@ impl Iterator for KBInputDecoder { #[export_name = "_start"] pub extern "C" fn main() { - let mut cwd: String = String::from("/"); - let mut partiton_id = 0u64; + let mut env = Environment::new(String::from("/"), 0); - let mut buffer = Vec::new(); - let mut file_buffer = Vec::new(); + let fs_sid = env.add_service("FS").unwrap(); + let keyboard_sid = env.add_service("INPUT:KB").unwrap(); + let elf_loader_sid = env.add_service("ELF_LOADER").unwrap(); - let fs_sid = get_public_service_id("FS", &mut buffer).unwrap(); - let keyboard_sid = get_public_service_id("INPUT:KB", &mut buffer).unwrap(); - let elf_loader_sid = get_public_service_id("ELF_LOADER", &mut buffer).unwrap(); + env.services = Some(execute::Services { + fs: fs_sid, + keyboard: keyboard_sid, + elf_loader: elf_loader_sid, + }); - service_subscribe(keyboard_sid); + env.add_internal_fn("pwd", &fns::pwd); + env.add_internal_fn("echo", &fns::echo); + env.add_internal_fn("disk", &fns::disk); + env.add_internal_fn("ls", &fns::ls); + env.add_internal_fn("cd", &fns::cd); + env.add_internal_fn("cat", &fns::cat); + service_subscribe(keyboard_sid); let mut input: KBInputDecoder = KBInputDecoder::new(keyboard_sid); loop { - print!("{partiton_id}:/ "); + print!("{}:{} ", env.partition_id, env.cwd); let mut curr_line = String::new(); @@ -128,153 +137,10 @@ pub extern "C" fn main() { } } - let (command, rest) = curr_line - .trim() - .split_once(' ') - .unwrap_or((curr_line.as_str(), "")); - match command { - "" => (), - "pwd" => println!("{}", cwd.to_string()), - "echo" => { - print!("ECHO!"); - } - "disk" => { - let c = rest.trim(); - let c = c.chars().next(); - if let Some(chr) = c { - if let Some(n) = chr.to_digit(10) { - match fs::stat(fs_sid, n as usize, "/", &mut buffer) { - Ok(StatResponse::File(_)) => println!("cd: cannot cd into a file"), - Ok(StatResponse::Folder(_)) => { - partiton_id = n.into(); - } - Err(e) => println!("cd: fs error: {e:?}"), - }; - - continue; - } - } - - println!("Drives:"); - for part in get_disks(fs_sid, &mut buffer).unwrap().iter() { - println!("{}:", part) - } - } - "ls" => { - let path = add_path(&cwd, rest); - - match fs::stat(fs_sid, partiton_id as usize, path.as_str(), &mut buffer) { - Ok(StatResponse::File(_)) => println!("This is a file"), - Ok(StatResponse::Folder(c)) => { - for child in c.children { - println!("{child}") - } - } - Err(e) => println!("Error: {e:?}"), - }; - } - "cd" => cwd = add_path(&cwd, rest), - "cat" => { - for file in rest.split_ascii_whitespace() { - let path = add_path(&cwd, file); - - let file = - match fs::stat(fs_sid, partiton_id as usize, path.as_str(), &mut buffer) { - Ok(StatResponse::File(f)) => f, - Ok(StatResponse::Folder(_)) => { - println!("Not a file"); - continue; - } - Err(e) => { - println!("Error: {e:?}"); - break; - } - }; - - for i in 0..file.file_size / 512 { - let sect = match read_file_sector( - fs_sid, - partiton_id as usize, - file.node_id, - i as u32, - &mut file_buffer, - ) { - Ok(s) => s, - Err(e) => { - println!("Error: {e:?}"); - break; - } - }; - if let Some(data) = sect { - print!("{}", String::from_utf8_lossy(data)) - } else { - print!("Error reading"); - break; - } - } - } - } - "exec" => { - let (prog, args) = rest.split_once(' ').unwrap_or((rest, "")); - - let path = add_path(&cwd, prog); - - let stat = fs::stat(fs_sid, partiton_id as usize, path.as_str(), &mut buffer); - - let file = match stat { - Ok(StatResponse::File(f)) => f, - Ok(StatResponse::Folder(_)) => { - println!("Not a file"); - continue; - } - Err(e) => { - println!("Error: {e:?}"); - continue; - } - }; - println!("READING..."); - let contents = match read_full_file( - fs_sid, - partiton_id as usize, - file.node_id, - &mut file_buffer, - ) { - Ok(Some(c)) => c, - Ok(None) => { - println!("Failed to read file"); - continue; - } - Err(e) => { - println!("Error: {e:?}"); - continue; - } - }; - - println!("SPAWNING..."); - - let pid = spawn_elf_process(elf_loader_sid, contents, args.as_bytes(), &mut buffer); - - match pid { - Ok(_) => (), - Err(err) => println!("Error spawning: `{err}`"), - } - - // let pid = load_elf(&contents_buffer.data, args.as_bytes()); - // while TASKMANAGER.lock().processes.contains_key(&pid) { - // yield_now(); - // } - } - // "uptime" => { - // let mut uptime = time::uptime() / 1000; - // let seconds = uptime % 60; - // uptime /= 60; - // let minutes = uptime % 60; - // uptime /= 60; - // println!("Up: {:02}:{:02}:{:02}", uptime, minutes, seconds) - // } - _ => { - println!("{command}: command not found") - } + curr_line.push('\n'); + match execute(&curr_line, &mut env) { + Ok(_) => {} + Err(error) => println!("{}", error.to_string()), } } } diff --git a/terminal/src/script/execute.rs b/terminal/src/script/execute.rs new file mode 100644 index 0000000..60c2778 --- /dev/null +++ b/terminal/src/script/execute.rs @@ -0,0 +1,291 @@ +extern crate alloc; + +use core::fmt::Display; + +use alloc::{ + borrow::ToOwned, + string::{String, ToString}, + vec::Vec, +}; +use hashbrown::HashMap; +use kernel_userspace::{ + elf::LoadElfError, + fs::{self, add_path, read_full_file, StatResponse}, + ids::{ProcessID, ServiceID}, + service::{generate_tracking_number, get_public_service_id, ServiceMessage}, + syscall::{send_and_get_response_service_message, CURRENT_PID}, +}; +use thiserror::Error; +use userspace::{print, println}; + +use super::parser::{Expr, Stmt}; +use crate::error::Result; + +pub fn execute<'a>(stmts: Vec, env: &mut Environment<'a>) -> Result<()> { + for stmt in stmts { + execute_stmt(stmt, env)?; + } + + Ok(()) +} + +fn execute_stmt<'a>(stmt: Stmt, env: &mut Environment<'a>) -> Result<()> { + match stmt { + Stmt::Noop => Ok(()), + Stmt::Execution(expr) => { + execute_expr(&expr, env)?; + Ok(()) + } + Stmt::VarDec { id, expr } => { + let val = execute_expr(&expr, env)?; + env.set_var(id, val); + Ok(()) + } + } +} + +fn execute_binary<'a>(path: String, pos_args: Vec, env: &mut Environment<'a>) -> Result<()> { + let fs_sid = env.services.ok_or(ExecutionErrors::UninitedService)?.fs; + let elf_loader_sid = env + .services + .ok_or(ExecutionErrors::UninitedService)? + .elf_loader; + + let path = add_path(&env.cwd, &path); + let stat = fs::stat( + fs_sid, + env.partition_id as usize, + &path, + env.services_buffer()?, + )?; + + let file = match stat { + StatResponse::File(ref f) => f.clone(), + StatResponse::Folder(_) => Err(ExecutionErrors::ExecNotAFile)?, + }; + + drop(stat); + + println!("READING..."); + let contents = read_full_file( + fs_sid, + env.partition_id as usize, + file.node_id, + env.services_buffer()?, + )? + .ok_or(ExecutionErrors::ReadError)? + .to_owned(); + + println!("SPAWNING..."); + let args = args_to_string(pos_args, env)?; + + let _: ServiceMessage>> = + send_and_get_response_service_message( + &ServiceMessage { + service_id: elf_loader_sid, + sender_pid: *CURRENT_PID, + tracking_number: generate_tracking_number(), + destination: kernel_userspace::service::SendServiceMessageDest::ToProvider, + message: (contents, args.as_bytes()), + }, + env.services_buffer()?, + )?; + + Ok(()) +} + +pub fn args_to_string<'a>(pos_args: Vec, env: &mut Environment<'a>) -> Result { + Ok(pos_args + .iter() + // TODO: Proper error handling / not unwrapping + .map(|arg| execute_expr(arg, env).unwrap().to_string()) + .collect::>() + .join(" ")) +} + +pub fn execute_expr<'a>(expr: &Expr, env: &mut Environment<'a>) -> Result { + Ok(match expr { + Expr::String(str) => Value::String(str.clone()), + Expr::Exec { path, pos_args } => { + if path.starts_with("./") { + execute_binary(path.to_string(), pos_args.clone(), env)?; + return Ok(Value::Null); + } + + if env.has_function(&path) { + return env.call_function(&path, pos_args.clone()); + } + + Err(ExecutionErrors::UnresolvedCall(path.clone()))? + } + Expr::Var(key) => env.get_var(key), + }) +} + +pub struct Environment<'a> { + pub cwd: String, + pub partition_id: u64, + + services_buffer_internal: Option>, + pub services: Option, + + parent: Option<&'a mut Environment<'a>>, + variables: HashMap, + functions: HashMap, +} + +impl<'a> Environment<'a> { + pub fn new(cwd: String, partition_id: u64) -> Environment<'a> { + Environment { + cwd, + partition_id, + services_buffer_internal: Some(Vec::new()), + services: None, + parent: None, + variables: HashMap::new(), + functions: HashMap::new(), + } + } + + pub fn with_parent(env: &'a mut Environment<'a>) -> Environment<'a> { + Environment { + cwd: env.cwd.clone(), + partition_id: env.partition_id, + services_buffer_internal: None, + services: env.services.clone(), + parent: Some(env), + variables: HashMap::new(), + functions: HashMap::new(), + } + } + + pub fn has_function(&self, name: &str) -> bool { + let mut found = self.functions.contains_key(name); + + if !found && let Some(parent) = &self.parent { + found = parent.has_function(name); + } + + found + } + + pub fn get_function<'b>(&'b self, name: &str) -> Option<&'b Value> { + let mut found = self.functions.get(name); + + if found.is_none() && let Some(parent) = &self.parent { + found = parent.get_function(name); + } + + found + } + + pub fn call_function(&mut self, name: &str, args: Vec) -> Result { + let func = self + .get_function(name) + .ok_or(ExecutionErrors::CouldNotFindFunction(name.to_string()))? + .clone(); + + func.function_call(self, args) + } + + pub fn add_internal_fn(&mut self, name: &str, func: InternalFunctionType) { + self.functions + .insert(name.to_string(), Value::InternalFunction(func)); + } + + pub fn get_var(&self, key: &str) -> Value { + self.variables.get(key).map_or(Value::Null, |v| v.clone()) + } + + pub fn set_var(&mut self, key: String, val: Value) { + self.variables.insert(key, val); + } + + pub fn add_service(&mut self, name: &str) -> Result { + Ok(get_public_service_id(name, self.services_buffer()?) + .ok_or_else(|| ExecutionErrors::NoService(name.to_string()))?) + } + + pub fn services_buffer<'b>(&'b mut self) -> Result<&'b mut Vec> { + if let Some(services_buff) = &mut self.services_buffer_internal { + Ok(services_buff) + } else if let Some(parent) = &mut self.parent { + parent.services_buffer() + } else { + Err(ExecutionErrors::NoParentServiceBuffer)? + } + } +} + +#[derive(Clone, Copy)] +pub struct Services { + pub fs: ServiceID, + pub keyboard: ServiceID, + pub elf_loader: ServiceID, +} + +type InternalFunctionType = + &'static dyn for<'a> Fn(&mut Environment<'a>, Vec) -> Result; + +#[derive(Clone)] +pub enum Value { + Null, + String(String), + InternalFunction(InternalFunctionType), +} + +impl Value { + pub fn function_call<'a>(&self, env: &mut Environment<'a>, args: Vec) -> Result { + if let Value::InternalFunction(func) = self { + return func(env, args); + } + + Err(ExecutionErrors::NotAFunction(self.to_string()))? + } +} + +impl Display for Value { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Value::Null => f.write_str("null"), + Value::String(str) => f.write_str(str), + Value::InternalFunction(_) => f.write_str("[InternalFunction]"), + } + } +} + +#[derive(Debug, Error)] +pub enum ExecutionErrors { + #[error("Could not resolve execution target '{0}'")] + UnresolvedCall(String), + + #[error("Could not find fs service id")] + CouldNotFindFSSID, + + #[error("Could not find elf service id")] + CouldNotFindELFSID, + + #[error("Could not execute: found folder")] + ExecNotAFile, + + #[error("Could not execute: file not found")] + ExecCouldNotFind, + + #[error("Could not read file")] + ReadError, + + #[error("Could not execute something that is not a function: {0}")] + NotAFunction(String), + + #[error("Could obtain service '{0}'")] + NoService(String), + + #[error("Service was not initialized")] + UninitedService, + + #[error("The parent does not have a services buffer")] + NoParentServiceBuffer, + + #[error("Could not find function: {0}")] + CouldNotFindFunction(String), +} diff --git a/terminal/src/script/mod.rs b/terminal/src/script/mod.rs new file mode 100644 index 0000000..88b5d22 --- /dev/null +++ b/terminal/src/script/mod.rs @@ -0,0 +1,23 @@ +extern crate alloc; +use alloc::vec::Vec; +use userspace::print; + +pub use self::execute::Environment; +use self::parser::parse; +use self::tokenizer::tiny_tokenizer; +use crate::error::Result; + +pub mod execute; +pub mod parser; +mod tokenizer; + +#[cfg(test)] +mod tests; + +pub fn execute<'a>(line: &str, env: &mut Environment<'a>) -> Result<()> { + let tokens = tiny_tokenizer(Vec::new(), line, 0)?; + let stmts = parse(tokens)?; + execute::execute(stmts, env)?; + + Ok(()) +} diff --git a/terminal/src/script/parser.rs b/terminal/src/script/parser.rs new file mode 100644 index 0000000..356fff3 --- /dev/null +++ b/terminal/src/script/parser.rs @@ -0,0 +1,227 @@ +extern crate alloc; + +use alloc::{format, string::String, vec::Vec}; +use thiserror::Error; + +use super::tokenizer::{ + Token, + TokenKind::{self, *}, +}; +use crate::error::{Context, Result}; + +pub fn parse(tokens: Vec) -> Result> { + let mut parser = Parser::new(tokens); + let mut stmts = Vec::new(); + + while !parser.is_at_end() { + let stmt = match parser.peek().context("Inside parse")? { + Eq => Err(ParserError::UnexpectedToken( + parser.peek().context("Inside parse Eq")?, + ))?, + Dot | Slash | Str(_) => parse_exec(&mut parser)?, + Var(_) => parse_var(&mut parser)?, + StmtEnd => { + parser.consume()?; + Stmt::Noop + } + }; + + stmts.push(stmt); + } + + Ok(stmts) +} + +fn parse_var(p: &mut Parser) -> Result { + let id = p.expect_var()?; + p.expect(Eq)?; + let expr = parse_expr(p)?; + p.expect(StmtEnd)?; + + Ok(Stmt::VarDec { id, expr }) +} + +fn parse_exec(p: &mut Parser) -> Result { + let path = parse_possible_strings(p)?; + let mut pos_args = Vec::new(); + + while !p.is_at_end() + && match p.peek().context("Inside parse_exec")? { + Eq | StmtEnd => false, + _ => true, + } + { + pos_args.push(parse_expr(p)?); + } + + Ok(Stmt::Execution(Expr::Exec { path, pos_args })) +} + +fn parse_expr(p: &mut Parser) -> Result { + Ok(match p.peek().context("Inside parse_expr")? { + Dot | Slash | Str(_) => Expr::String(parse_possible_strings(p)?), + Var(name) => { + p.consume()?; + Expr::Var(name) + } + Eq | StmtEnd => Err(ParserError::UnexpectedToken( + p.peek().context("Inside parse_expr::Eq|StmtEnd")?, + ))?, + }) +} + +fn parse_possible_strings(p: &mut Parser) -> Result { + if p.is_at_end() { + return Ok(String::new()); + } + + Ok(match p.peek().context("Inside parse_possible_strings")? { + Dot => { + p.expect(Dot)?; + format!(".{}", parse_possible_strings(p)?) + } + Slash => { + p.expect(Slash)?; + format!("/{}", parse_possible_strings(p)?) + } + Str(str) => { + p.consume()?; + str + } + Eq | Var(_) | StmtEnd => Err(ParserError::UnexpectedToken( + p.peek() + .context("Inside parse_possible_strings::Eq|Var|StmtEnd")?, + ))?, + }) +} + +#[derive(Debug)] +pub struct Parser { + pub index: usize, + pub tokens: Vec, +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Parser { index: 0, tokens } + } + + pub fn is_at_end(&self) -> bool { + self.index >= self.tokens.len() + } + + pub fn is(&self, tokens: &[TokenKind]) -> Result { + let peek = self.peek().context("Inside is")?; + + for token in tokens { + if peek == *token { + return Ok(true); + } + } + + Ok(false) + } + + fn ensure_current(&self) -> Result<()> { + if self.is_at_end() { + Err(ParserError::UnexpectedEOF)? + } + + Ok(()) + } + + fn ensure_next(&self) -> Result<()> { + if self.index + 1 > self.tokens.len() { + Err(ParserError::UnexpectedEOF)? + } + + Ok(()) + } + + pub fn peek(&self) -> Result { + self.ensure_current().context("Inside peek")?; + Ok(self.tokens[self.index].kind.clone()) + } + + pub fn consume(&mut self) -> Result { + self.ensure_next().context("Inside consume")?; + let current = self.peek().context("Inside consume")?; + + self.index += 1; + Ok(current) + } + + pub fn expect(&mut self, token: TokenKind) -> Result { + self.ensure_current() + .with_context(|| format!("Expected: {}", token))?; + + if self.tokens[self.index].kind != token { + Err(ParserError::ExpectedToken( + self.tokens[self.index].kind.clone(), + token, + ))? + } + + let token = self.tokens[self.index].kind.clone(); + self.index += 1; + + Ok(token) + } + + pub fn expect_id(&mut self) -> Result { + self.ensure_next().context("Inside expect_id")?; + + let id = match &self.tokens[self.index].kind { + Str(val) => val.clone(), + _ => Err(ParserError::ExpectedIdentifier( + self.tokens[self.index].kind.clone(), + ))?, + }; + self.index += 1; + + Ok(id) + } + + pub fn expect_var(&mut self) -> Result { + self.ensure_next().context("Inside expect_var")?; + + let id = match &self.tokens[self.index].kind { + Var(val) => val.clone(), + _ => Err(ParserError::ExpectedIdentifier( + self.tokens[self.index].kind.clone(), + ))?, + }; + self.index += 1; + + Ok(id) + } +} + +#[derive(Debug, Clone)] +pub enum Stmt { + Noop, + VarDec { id: String, expr: Expr }, + Execution(Expr), +} + +#[derive(Debug, Clone)] +pub enum Expr { + Exec { path: String, pos_args: Vec }, + Var(String), + String(String), +} + +#[derive(Debug, Error)] +pub enum ParserError { + #[error("Unexpected token '{0}'")] + UnexpectedToken(TokenKind), + + #[error("Expected token '{0}', found '{1}'")] + ExpectedToken(TokenKind, TokenKind), + + #[error("Expected identifier, found '{0}'")] + ExpectedIdentifier(TokenKind), + + #[error("Unexpected end of input")] + UnexpectedEOF, +} diff --git a/terminal/src/script/tests.rs b/terminal/src/script/tests.rs new file mode 100644 index 0000000..e69de29 diff --git a/terminal/src/script/tokenizer.rs b/terminal/src/script/tokenizer.rs new file mode 100644 index 0000000..8dcfb99 --- /dev/null +++ b/terminal/src/script/tokenizer.rs @@ -0,0 +1,145 @@ +extern crate alloc; + +use core::fmt::{Display, Write}; +use userspace::{print, println}; + +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use thiserror::Error; + +use crate::error::{Context, Result}; + +pub fn tiny_tokenizer(mut tokens: Vec, src: &str, start: usize) -> Result> { + let next = src.chars().next(); + if let None = next { + return Ok(tokens); + } + + let next = next.ok_or(LexerError::UnexpectedEOF)?; + if next == ' ' { + return tiny_tokenizer(tokens, &src[1..], start + 1); + } + + let (token, size) = match next { + '.' => (TokenKind::Dot, 1), + '/' => (TokenKind::Slash, 1), + '=' => (TokenKind::Eq, 1), + '\n' => (TokenKind::StmtEnd, 1), + ';' => (TokenKind::StmtEnd, 1), + '$' => { + let (str, length) = + tokenize_str(&src[1..]).with_context(|| LexerError::IdentifierFailed)?; + (TokenKind::Var(str), length + 1) + } + c if c.is_alphanumeric() || c == '-' || c == '.' => { + let (str, length) = tokenize_str(src).with_context(|| LexerError::IdentifierFailed)?; + (TokenKind::Str(str), length) + } + _ => Err(LexerError::UnknownChar(start + 1, next))?, + }; + + let end = start + size; + tokens.push(Token::new(token, start, end)); + + tiny_tokenizer(tokens, &src[size..], end) +} + +fn tokenize_str(data: &str) -> TokResult<(String, usize)> { + // TODO: Reintroduce this once I have number types + // match data.chars().next() { + // Some(ch) if ch.is_digit(10) => Err(LexerError::StartWithNum)?, + // None => Err(LexerError::UnexpectedEOF)?, + // _ => {} + // } + + let (got, bytes_read) = take_while(data, |ch| ch == '-' || ch.is_alphanumeric() || ch == '.')?; + Ok((got.to_string(), bytes_read)) +} + +fn take_while(data: &str, mut pred: F) -> TokResult<(&str, usize)> +where + F: FnMut(char) -> bool, +{ + let mut current_index = 0; + + for ch in data.chars() { + let should_continue = pred(ch); + + if !should_continue { + break; + } + + current_index += ch.len_utf8(); + } + + if current_index == 0 { + Err(LexerError::NoMatches)? + } else { + Ok((&data[..current_index], current_index)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TokenKind { + Eq, + Dot, + Slash, + StmtEnd, + Str(String), + Var(String), +} + +impl Display for TokenKind { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use TokenKind::*; + + match self { + Dot => f.write_char('.'), + Slash => f.write_char('/'), + Str(str) => { + f.write_char('"')?; + f.write_str(str)?; + f.write_char('"') + } + Eq => f.write_char('='), + Var(str) => { + f.write_char('$')?; + f.write_str(str) + } + StmtEnd => f.write_str("\\n"), + } + } +} + +#[derive(Debug, Clone)] +pub struct Token { + pub kind: TokenKind, + /// The location inside of the initial tokenizing string + pub start: usize, + pub end: usize, +} + +impl Token { + pub fn new(kind: TokenKind, start: usize, end: usize) -> Self { + Token { kind, start, end } + } +} + +type TokResult = Result; + +#[derive(Error, Debug)] +pub enum LexerError { + #[error("Unexpected end of file")] + UnexpectedEOF, + + #[error("Failed to parse identifier")] + IdentifierFailed, + + #[error("No matches")] + NoMatches, + + #[error("{0}: Unknown chars '{1}'")] + UnknownChar(usize, char), +}