From 4682cfcca2857f6b46a1cac67dedca507793937d Mon Sep 17 00:00:00 2001 From: TrickyPR <23250792+trickypr@users.noreply.github.com> Date: Sun, 11 Jun 2023 21:49:01 +1000 Subject: [PATCH 1/5] Lexer & parser --- terminal/Cargo.toml | 4 +- terminal/src/error.rs | 93 +++++++++++++ terminal/src/lib.rs | 5 + terminal/src/main.rs | 3 + terminal/src/script/execute.rs | 36 +++++ terminal/src/script/mod.rs | 22 ++++ terminal/src/script/parser.rs | 169 ++++++++++++++++++++++++ terminal/src/script/tests.rs | 0 terminal/src/script/tokenizer.rs | 217 +++++++++++++++++++++++++++++++ 9 files changed, 548 insertions(+), 1 deletion(-) create mode 100644 terminal/src/error.rs create mode 100644 terminal/src/lib.rs create mode 100644 terminal/src/script/execute.rs create mode 100644 terminal/src/script/mod.rs create mode 100644 terminal/src/script/parser.rs create mode 100644 terminal/src/script/tests.rs create mode 100644 terminal/src/script/tokenizer.rs diff --git a/terminal/Cargo.toml b/terminal/Cargo.toml index 1a7cb9f..316a722 100644 --- a/terminal/Cargo.toml +++ b/terminal/Cargo.toml @@ -11,5 +11,7 @@ userspace = { path = "../userspace" } kernel_userspace = { path = "../kernel_userspace" } input = { path = "../input" } +thiserror = { version = "1.0", package = "thiserror-core", default-features = false } + [profile.dev] -strip = true \ No newline at end of file +strip = true diff --git a/terminal/src/error.rs b/terminal/src/error.rs new file mode 100644 index 0000000..7066f0c --- /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, Write}, +}; + +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/lib.rs b/terminal/src/lib.rs new file mode 100644 index 0000000..7965e70 --- /dev/null +++ b/terminal/src/lib.rs @@ -0,0 +1,5 @@ +#![no_std] +#![feature(error_in_core)] + +mod error; +pub mod script; diff --git a/terminal/src/main.rs b/terminal/src/main.rs index b9e5e00..69653f2 100644 --- a/terminal/src/main.rs +++ b/terminal/src/main.rs @@ -1,11 +1,13 @@ #![no_std] #![no_main] +#![feature(error_in_core)] use kernel_userspace::{ fs::{add_path, read_file_sector}, proc::PID, syscall::{exit, read_args}, }; +use terminal::script::execute; extern crate alloc; #[macro_use] @@ -270,6 +272,7 @@ pub extern "C" fn main() { // println!("Up: {:02}:{:02}:{:02}", uptime, minutes, seconds) // } _ => { + execute(&curr_line).unwrap(); println!("{command}: command not found") } } diff --git a/terminal/src/script/execute.rs b/terminal/src/script/execute.rs new file mode 100644 index 0000000..315ef28 --- /dev/null +++ b/terminal/src/script/execute.rs @@ -0,0 +1,36 @@ +extern crate alloc; + +use alloc::vec::Vec; + +use super::parser::Stmt; +use crate::error::Result; + +pub fn execute<'a>(stmts: Vec, env: &mut Environment<'a>) -> Result<()> { + for stmt in stmts { + execute_single(stmt, env)?; + } + + Ok(()) +} + +fn execute_single<'a>(stmt: Stmt, env: &mut Environment<'a>) -> Result<()> { + match stmt { + Stmt::Execution { path, pos_args } => todo!(), + } + + Ok(()) +} + +pub struct Environment<'a> { + parent: Option<&'a mut Environment<'a>>, +} + +impl<'a> Environment<'a> { + pub fn new() -> Environment<'a> { + Environment { parent: None } + } + + pub fn with_parent(env: &'a mut Environment<'a>) -> Environment<'a> { + Environment { parent: Some(env) } + } +} diff --git a/terminal/src/script/mod.rs b/terminal/src/script/mod.rs new file mode 100644 index 0000000..8153c04 --- /dev/null +++ b/terminal/src/script/mod.rs @@ -0,0 +1,22 @@ +use userspace::{print, println}; + +use self::parser::parse; +use self::tokenizer::tokenize; +use crate::error::Result; + +mod execute; +mod parser; +mod tokenizer; + +#[cfg(test)] +mod tests; + +pub fn execute(line: &str) -> Result<()> { + let tokens = tokenize(line)?; + println!("{:?}", tokens); + + let stmts = parse(tokens)?; + println!("{:?}", stmts); + + Ok(()) +} diff --git a/terminal/src/script/parser.rs b/terminal/src/script/parser.rs new file mode 100644 index 0000000..841a103 --- /dev/null +++ b/terminal/src/script/parser.rs @@ -0,0 +1,169 @@ +extern crate alloc; + +use alloc::{format, string::String, vec::Vec}; +use thiserror::Error; +use userspace::{print, println}; + +use super::tokenizer::{ + Token, + TokenKind::{self, *}, +}; +use crate::error::Result; + +pub fn parse(tokens: Vec) -> Result> { + let mut parser = Parser::new(tokens); + let mut stmts = Vec::new(); + + while !parser.is_at_end() { + println!( + "{} {} {:?} {}", + parser.tokens.len(), + parser.index, + parser.tokens, + parser.is_at_end() + ); + + stmts.push(parse_exec(&mut parser)?); + } + + println!( + "{} {} {:?} {}", + parser.tokens.len(), + parser.index, + parser.tokens, + parser.is_at_end() + ); + + Ok(stmts) +} + +fn parse_exec(p: &mut Parser) -> Result { + let path = parse_possible_strings(p)?; + let pos_args = parse_positional_arguments(p)?; + + Ok(Stmt::Execution { path, pos_args }) +} + +fn parse_positional_arguments(p: &mut Parser) -> Result> { + let mut exprs = Vec::new(); + + while !p.is_at_end() { + let param = parse_possible_strings(p)?; + exprs.push(Expr::String(param)); + } + + Ok(exprs) +} + +fn parse_possible_strings(p: &mut Parser) -> Result { + Ok(match p.peek()? { + Dot => { + p.expect(Dot)?; + format!(".{}", parse_possible_strings(p)?) + } + Slash => { + p.expect(Slash)?; + format!("/{}", parse_possible_strings(p)?) + } + Str(str) => { + p.consume()?; + str + } + }) +} + +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() + } + + 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()?; + Ok(self.tokens[self.index].kind.clone()) + } + + pub fn consume(&mut self) -> Result { + self.ensure_next()?; + let current = self.peek()?; + + self.index += 1; + Ok(current) + } + + pub fn expect(&mut self, token: TokenKind) -> Result { + self.ensure_current()?; + + 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()?; + + 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) + } +} + +#[derive(Debug)] +pub enum Stmt { + Execution { path: String, pos_args: Vec }, +} + +#[derive(Debug)] +pub enum Expr { + String(String), +} + +#[derive(Debug, Error)] +pub enum ParserError { + #[error("Expected token '{0}', found '{0}'")] + 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..791b0bf --- /dev/null +++ b/terminal/src/script/tokenizer.rs @@ -0,0 +1,217 @@ +extern crate alloc; + +use core::fmt::{Display, Write}; + +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use thiserror::Error; +use userspace::{print, println}; + +use crate::error::{Context, Result}; + +struct Tokenizer<'a> { + current_index: usize, + remaining_text: &'a str, +} + +impl<'a> Tokenizer<'a> { + fn new(src: &str) -> Tokenizer { + Tokenizer { + current_index: 0, + remaining_text: src, + } + } + + fn next_token(&mut self) -> Result> { + self.skip_whitespace(); + + if self.remaining_text.is_empty() { + Ok(None) + } else { + let start = self.current_index; + let tok = self + ._next_token() + .with_context(|| LexerError::ReadFailed(self.current_index))?; + let end = self.current_index; + Ok(Some(Token::new(tok, start, end))) + } + } + + fn skip_whitespace(&mut self) { + let skipped = skip(self.remaining_text); + self.chomp(skipped); + } + + fn _next_token(&mut self) -> Result { + let (tok, bytes_read) = tokenize_single_token(self.remaining_text)?; + self.chomp(bytes_read); + + Ok(tok) + } + + fn chomp(&mut self, num_bytes: usize) { + self.remaining_text = &self.remaining_text[num_bytes..]; + self.current_index += num_bytes; + println!("{} {}", self.current_index, self.remaining_text); + } +} + +pub fn tokenize(src: &str) -> Result> { + let mut tokenizer = Tokenizer::new(src); + let mut tokens = Vec::new(); + + while let Some(tok) = tokenizer.next_token()? { + tokens.push(tok); + } + + Ok(tokens) +} + +fn skip_whitespace(data: &str) -> usize { + match take_while(data, |ch| ch.is_whitespace()) { + Ok((_, bytes_skipped)) => bytes_skipped, + _ => 0, + } +} + +/// Skip past any whitespace characters or comments. +fn skip(src: &str) -> usize { + let mut remaining = src; + + loop { + let ws = skip_whitespace(remaining); + remaining = &remaining[ws..]; + // let comments = skip_comments(remaining); + // remaining = &remaining[comments..]; + + if ws == 0 { + return src.len() - remaining.len(); + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TokenKind { + Dot, + Slash, + Str(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('"') + } + } + } +} + +impl From for TokenKind { + fn from(value: String) -> Self { + TokenKind::Str(value) + } +} + +impl<'a> From<&'a str> for TokenKind { + fn from(value: &'a str) -> Self { + TokenKind::Str(value.to_string()) + } +} + +#[derive(Debug)] +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; + +pub fn tokenize_single_token(data: &str) -> TokResult<(TokenKind, usize)> { + let next = match data.chars().next() { + Some(c) => c, + None => Err(LexerError::UnexpectedEOF)?, + }; + + Ok(match next { + '.' => (TokenKind::Dot, 1), + '/' => (TokenKind::Slash, 1), + c if c.is_alphanumeric() || c == '-' || c == '.' => { + tokenize_ident(data).with_context(|| LexerError::IdentifierFailed)? + } + _ => Err(LexerError::UnknownChar(next))?, + }) +} + +fn tokenize_ident(data: &str) -> TokResult<(TokenKind, usize)> { + 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 == '.')?; + + let tok = TokenKind::Str(got.to_string()); + Ok((tok, 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(Error, Debug)] +pub enum LexerError { + #[error("Identifiers can't start with a number")] + StartWithNum, + + #[error("Unexpected end of file")] + UnexpectedEOF, + + #[error("Failed to parse identifier")] + IdentifierFailed, + + #[error("No matches")] + NoMatches, + + #[error("Unknown chars '{0}'")] + UnknownChar(char), + + #[error("{0}: Could not read the next token")] + ReadFailed(usize), +} From 18f553c6f67523db0bdddc85d96f0f7d9aa5eea9 Mon Sep 17 00:00:00 2001 From: TrickyPR <23250792+trickypr@users.noreply.github.com> Date: Mon, 12 Jun 2023 00:12:55 +1000 Subject: [PATCH 2/5] Internal functions + execution --- kernel_userspace/Cargo.toml | 4 +- kernel_userspace/src/lib.rs | 1 + kernel_userspace/src/service.rs | 8 +- terminal/Cargo.toml | 2 + terminal/src/fns.rs | 106 +++++++++++++++++ terminal/src/lib.rs | 3 +- terminal/src/main.rs | 158 +++----------------------- terminal/src/script/execute.rs | 189 ++++++++++++++++++++++++++++++- terminal/src/script/mod.rs | 13 +-- terminal/src/script/parser.rs | 21 +--- terminal/src/script/tokenizer.rs | 13 +-- 11 files changed, 338 insertions(+), 180 deletions(-) create mode 100644 terminal/src/fns.rs diff --git a/kernel_userspace/Cargo.toml b/kernel_userspace/Cargo.toml index e5aa085..4ae4d23 100644 --- a/kernel_userspace/Cargo.toml +++ b/kernel_userspace/Cargo.toml @@ -9,4 +9,6 @@ edition = "2021" postcard = { version = "1.0.4", features = ["alloc"] } serde = { version = "1.0.*", default-features = false } input = { path = "../input" } -lazy_static = {version = "1.4", features = ["spin_no_std"]} +lazy_static = { version = "1.4", features = ["spin_no_std"] } + +thiserror = { version = "1.0", package = "thiserror-core", default-features = false } diff --git a/kernel_userspace/src/lib.rs b/kernel_userspace/src/lib.rs index a669436..fcbd1f2 100644 --- a/kernel_userspace/src/lib.rs +++ b/kernel_userspace/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![feature(error_in_core)] #[macro_use] extern crate alloc; diff --git a/kernel_userspace/src/service.rs b/kernel_userspace/src/service.rs index f47d3da..b146256 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::{ fs::FSServiceMessage, @@ -50,12 +51,17 @@ 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")] FailedToDecodeResponse, } diff --git a/terminal/Cargo.toml b/terminal/Cargo.toml index 316a722..6f57ade 100644 --- a/terminal/Cargo.toml +++ b/terminal/Cargo.toml @@ -12,6 +12,8 @@ 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 diff --git a/terminal/src/fns.rs b/terminal/src/fns.rs new file mode 100644 index 0000000..79ce559 --- /dev/null +++ b/terminal/src/fns.rs @@ -0,0 +1,106 @@ +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use kernel_userspace::fs::{self, add_path, get_disks, read_file_sector, StatResponse}; +use kernel_userspace::service::get_public_service_id; +use terminal::error::Result; +use terminal::script::execute::{args_to_string, execute_expr}; +use terminal::script::{execute::Value, parser::Expr, Environment}; + +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 { + // TODO: Variable echo + } + + 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])? + .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 = get_public_service_id("FS").unwrap(); + + println!("Drives:"); + for part in get_disks(fs_sid) { + println!("{}:", part) + } + + Ok(Value::Null) +} + +pub fn ls<'a>(env: &mut Environment<'a>, args: Vec) -> Result { + let fs_sid = get_public_service_id("FS").unwrap(); + + let path = add_path(&env.cwd, &args_to_string(args)?); + + let stat = fs::stat(fs_sid, env.partition_id as usize, path.as_str()); + + match stat { + StatResponse::File(_) => println!("This is a file"), + StatResponse::Folder(c) => { + for child in c.children { + println!("{child}") + } + } + StatResponse::NotFound => println!("Invalid Path"), + }; + + Ok(Value::Null) +} + +pub fn cd<'a>(env: &mut Environment<'a>, args: Vec) -> Result { + env.cwd = add_path(&env.cwd, &args_to_string(args)?); + Ok(Value::Null) +} + +pub fn cat<'a>(env: &mut Environment<'a>, args: Vec) -> Result { + let fs_sid = get_public_service_id("FS").unwrap(); + + for file in args { + let file = execute_expr(&file)?.to_string(); + + let path = add_path(&env.cwd, &file); + + let stat = fs::stat(fs_sid, env.partition_id as usize, path.as_str()); + + let file = match stat { + StatResponse::File(f) => f, + StatResponse::Folder(_) => { + println!("Not a file"); + continue; + } + StatResponse::NotFound => { + println!("File not found"); + 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); + if let Some(data) = sect { + print!("{}", String::from_utf8_lossy(data.get_data())) + } else { + print!("Error reading"); + break; + } + } + } + + Ok(Value::Null) +} diff --git a/terminal/src/lib.rs b/terminal/src/lib.rs index 7965e70..7707ebb 100644 --- a/terminal/src/lib.rs +++ b/terminal/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] #![feature(error_in_core)] +#![feature(let_chains)] -mod error; +pub mod error; pub mod script; diff --git a/terminal/src/main.rs b/terminal/src/main.rs index 4e58cc5..024da6f 100644 --- a/terminal/src/main.rs +++ b/terminal/src/main.rs @@ -3,18 +3,13 @@ #![feature(error_in_core)] use kernel_userspace::{ - fs::{self, add_path, get_disks, read_file_sector, read_full_file, StatResponse}, ids::ServiceID, - proc::PID, - service::{ - generate_tracking_number, get_public_service_id, ServiceMessage, ServiceMessageType, - }, - syscall::{ - exit, get_pid, send_and_wait_response_service_message, service_subscribe, - wait_receive_service_message, CURRENT_PID, - }, + service::{get_public_service_id, ServiceMessageType}, + syscall::{exit, service_subscribe, wait_receive_service_message}, }; -use terminal::script::execute; +use terminal::script::{execute, Environment}; + +mod fns; extern crate alloc; #[macro_use] @@ -97,19 +92,23 @@ impl Iterator for KBInputDecoder { #[export_name = "_start"] pub extern "C" fn main() { - let mut cwd: String = String::from("/"); - let mut partiton_id = 0u64; - - let fs_sid = get_public_service_id("FS").unwrap(); let keyboard_sid = get_public_service_id("INPUT:KB").unwrap(); - let elf_loader_sid = get_public_service_id("ELF_LOADER").unwrap(); service_subscribe(keyboard_sid); let mut input: KBInputDecoder = KBInputDecoder::new(keyboard_sid); + let mut env = Environment::new(String::from("/"), 0); + + 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); + loop { - print!("{partiton_id}:/ "); + print!("{}:{} ", env.partition_id, env.cwd); let mut curr_line = String::new(); @@ -128,130 +127,9 @@ 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!"); - unsafe { core::arch::asm!("cli") } - } - "disk" => { - let c = rest.trim(); - let c = c.chars().next(); - if let Some(chr) = c { - if let Some(n) = chr.to_digit(10) { - partiton_id = n.into(); - continue; - } - } - - println!("Drives:"); - for part in get_disks(fs_sid) { - println!("{}:", part) - } - } - "ls" => { - let path = add_path(&cwd, rest); - - let stat = fs::stat(fs_sid, partiton_id as usize, path.as_str()); - - match stat { - StatResponse::File(_) => println!("This is a file"), - StatResponse::Folder(c) => { - for child in c.children { - println!("{child}") - } - } - StatResponse::NotFound => println!("Invalid Path"), - }; - } - "cd" => cwd = add_path(&cwd, rest), - "cat" => { - for file in rest.split_ascii_whitespace() { - let path = add_path(&cwd, file); - - let stat = fs::stat(fs_sid, partiton_id as usize, path.as_str()); - - let file = match stat { - StatResponse::File(f) => f, - StatResponse::Folder(_) => { - println!("Not a file"); - continue; - } - StatResponse::NotFound => { - println!("File not found"); - continue; - } - }; - - for i in 0..file.file_size / 512 { - let sect = - read_file_sector(fs_sid, partiton_id as usize, file.node_id, i as u32); - if let Some(data) = sect { - print!("{}", String::from_utf8_lossy(data.get_data())) - } else { - print!("Error reading"); - break; - } - } - } - } - "exec" => { - let (prog, args) = rest.split_once(' ').unwrap_or_else(|| (rest, "")); - - let path = add_path(&cwd, prog); - - let stat = fs::stat(fs_sid, partiton_id as usize, path.as_str()); - - let file = match stat { - StatResponse::File(f) => f, - StatResponse::Folder(_) => { - println!("Not a file"); - continue; - } - StatResponse::NotFound => { - println!("File not found"); - continue; - } - }; - println!("READING..."); - let contents = read_full_file(fs_sid, partiton_id as usize, file.node_id).unwrap(); - - println!("SPAWNING..."); - - let resp = send_and_wait_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: kernel_userspace::service::ServiceMessageType::ElfLoader( - contents.get_data(), - args.as_bytes(), - ), - }) - .unwrap(); - - // 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) - // } - _ => { - execute(&curr_line).unwrap(); - println!("{command}: command not found") - } + 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 index 315ef28..fef7210 100644 --- a/terminal/src/script/execute.rs +++ b/terminal/src/script/execute.rs @@ -1,8 +1,21 @@ extern crate alloc; -use alloc::vec::Vec; +use core::fmt::Display; -use super::parser::Stmt; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use hashbrown::HashMap; +use kernel_userspace::{ + fs::{self, add_path, read_full_file, StatResponse}, + service::{generate_tracking_number, get_public_service_id, ServiceMessage}, + syscall::{send_and_wait_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<()> { @@ -15,22 +28,186 @@ pub fn execute<'a>(stmts: Vec, env: &mut Environment<'a>) -> Result<()> { fn execute_single<'a>(stmt: Stmt, env: &mut Environment<'a>) -> Result<()> { match stmt { - Stmt::Execution { path, pos_args } => todo!(), + Stmt::Execution { path, pos_args } => { + // TODO: Function resolution + + if path.starts_with("./") { + return execute_binary(path, pos_args, env); + } + + if env.has_function(&path) { + env.call_function(&path, pos_args)?; + return Ok(()); + } + + Err(ExecutionErrors::UnresolvedCall(path))? + } } +} + +fn execute_binary<'a>(path: String, pos_args: Vec, env: &Environment<'a>) -> Result<()> { + let fs_sid = get_public_service_id("FS").ok_or(ExecutionErrors::CouldNotFindFSSID)?; + let elf_loader_sid = + get_public_service_id("ELF_LOADER").ok_or(ExecutionErrors::CouldNotFindELFSID)?; + + let path = add_path(&env.cwd, &path); + let stat = fs::stat(fs_sid, env.partition_id as usize, &path); + + let file = match stat { + StatResponse::File(f) => f, + StatResponse::Folder(_) => Err(ExecutionErrors::ExecNotAFile)?, + StatResponse::NotFound => Err(ExecutionErrors::ExecCouldNotFind)?, + }; + + println!("READING..."); + let contents = read_full_file(fs_sid, env.partition_id as usize, file.node_id) + .ok_or(ExecutionErrors::ReadError)?; + + println!("SPAWNING..."); + send_and_wait_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: kernel_userspace::service::ServiceMessageType::ElfLoader( + contents.get_data(), + args_to_string(pos_args)?.as_bytes(), + ), + })?; Ok(()) } +pub fn args_to_string(pos_args: Vec) -> Result { + Ok(pos_args + .iter() + // TODO: Proper error handling / not unwrapping + .map(|arg| execute_expr(arg).unwrap().to_string()) + .collect::>() + .join(" ")) +} + +pub fn execute_expr(expr: &Expr) -> Result { + Ok(match expr { + Expr::String(str) => Value::String(str.clone()), + }) +} + pub struct Environment<'a> { + pub cwd: String, + pub partition_id: u64, + parent: Option<&'a mut Environment<'a>>, + functions: HashMap, } impl<'a> Environment<'a> { - pub fn new() -> Environment<'a> { - Environment { parent: None } + pub fn new(cwd: String, partition_id: u64) -> Environment<'a> { + Environment { + cwd, + partition_id, + parent: None, + functions: HashMap::new(), + } } pub fn with_parent(env: &'a mut Environment<'a>) -> Environment<'a> { - Environment { parent: Some(env) } + Environment { + cwd: env.cwd.clone(), + partition_id: env.partition_id, + parent: Some(env), + 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)); + } +} + +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 not find function: {0}")] + CouldNotFindFunction(String), } diff --git a/terminal/src/script/mod.rs b/terminal/src/script/mod.rs index 8153c04..708e740 100644 --- a/terminal/src/script/mod.rs +++ b/terminal/src/script/mod.rs @@ -1,22 +1,21 @@ -use userspace::{print, println}; - +pub use self::execute::Environment; use self::parser::parse; use self::tokenizer::tokenize; use crate::error::Result; -mod execute; -mod parser; +pub mod execute; +pub mod parser; mod tokenizer; #[cfg(test)] mod tests; -pub fn execute(line: &str) -> Result<()> { +pub fn execute<'a>(line: &str, env: &mut Environment<'a>) -> Result<()> { let tokens = tokenize(line)?; - println!("{:?}", tokens); let stmts = parse(tokens)?; - println!("{:?}", stmts); + + execute::execute(stmts, env)?; Ok(()) } diff --git a/terminal/src/script/parser.rs b/terminal/src/script/parser.rs index 841a103..3445027 100644 --- a/terminal/src/script/parser.rs +++ b/terminal/src/script/parser.rs @@ -2,7 +2,6 @@ extern crate alloc; use alloc::{format, string::String, vec::Vec}; use thiserror::Error; -use userspace::{print, println}; use super::tokenizer::{ Token, @@ -15,25 +14,9 @@ pub fn parse(tokens: Vec) -> Result> { let mut stmts = Vec::new(); while !parser.is_at_end() { - println!( - "{} {} {:?} {}", - parser.tokens.len(), - parser.index, - parser.tokens, - parser.is_at_end() - ); - stmts.push(parse_exec(&mut parser)?); } - println!( - "{} {} {:?} {}", - parser.tokens.len(), - parser.index, - parser.tokens, - parser.is_at_end() - ); - Ok(stmts) } @@ -56,6 +39,10 @@ fn parse_positional_arguments(p: &mut Parser) -> Result> { } fn parse_possible_strings(p: &mut Parser) -> Result { + if p.is_at_end() { + return Ok(String::new()); + } + Ok(match p.peek()? { Dot => { p.expect(Dot)?; diff --git a/terminal/src/script/tokenizer.rs b/terminal/src/script/tokenizer.rs index 791b0bf..e7cbd6f 100644 --- a/terminal/src/script/tokenizer.rs +++ b/terminal/src/script/tokenizer.rs @@ -7,7 +7,6 @@ use alloc::{ vec::Vec, }; use thiserror::Error; -use userspace::{print, println}; use crate::error::{Context, Result}; @@ -54,7 +53,6 @@ impl<'a> Tokenizer<'a> { fn chomp(&mut self, num_bytes: usize) { self.remaining_text = &self.remaining_text[num_bytes..]; self.current_index += num_bytes; - println!("{} {}", self.current_index, self.remaining_text); } } @@ -160,11 +158,12 @@ pub fn tokenize_single_token(data: &str) -> TokResult<(TokenKind, usize)> { } fn tokenize_ident(data: &str) -> TokResult<(TokenKind, usize)> { - match data.chars().next() { - Some(ch) if ch.is_digit(10) => Err(LexerError::StartWithNum)?, - None => Err(LexerError::UnexpectedEOF)?, - _ => {} - } + // 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 == '.')?; From 4cedc10b92f72a781bba5196d811deb739903e24 Mon Sep 17 00:00:00 2001 From: TrickyPR <23250792+trickypr@users.noreply.github.com> Date: Tue, 13 Jun 2023 22:20:53 +1000 Subject: [PATCH 3/5] Smaller tokenizer --- terminal/src/script/mod.rs | 7 +- terminal/src/script/tokenizer.rs | 176 ++++++++----------------------- 2 files changed, 47 insertions(+), 136 deletions(-) diff --git a/terminal/src/script/mod.rs b/terminal/src/script/mod.rs index 708e740..a51a362 100644 --- a/terminal/src/script/mod.rs +++ b/terminal/src/script/mod.rs @@ -1,6 +1,9 @@ +extern crate alloc; +use alloc::vec::Vec; + pub use self::execute::Environment; use self::parser::parse; -use self::tokenizer::tokenize; +use self::tokenizer::tiny_tokenizer; use crate::error::Result; pub mod execute; @@ -11,7 +14,7 @@ mod tokenizer; mod tests; pub fn execute<'a>(line: &str, env: &mut Environment<'a>) -> Result<()> { - let tokens = tokenize(line)?; + let tokens = tiny_tokenizer(Vec::new(), line, 0)?; let stmts = parse(tokens)?; diff --git a/terminal/src/script/tokenizer.rs b/terminal/src/script/tokenizer.rs index e7cbd6f..5ac36f6 100644 --- a/terminal/src/script/tokenizer.rs +++ b/terminal/src/script/tokenizer.rs @@ -10,83 +10,62 @@ use thiserror::Error; use crate::error::{Context, Result}; -struct Tokenizer<'a> { - current_index: usize, - remaining_text: &'a str, -} - -impl<'a> Tokenizer<'a> { - fn new(src: &str) -> Tokenizer { - Tokenizer { - current_index: 0, - remaining_text: src, - } +pub fn tiny_tokenizer(mut tokens: Vec, src: &str, start: usize) -> Result> { + if src.is_empty() { + return Ok(tokens); } - fn next_token(&mut self) -> Result> { - self.skip_whitespace(); - - if self.remaining_text.is_empty() { - Ok(None) - } else { - let start = self.current_index; - let tok = self - ._next_token() - .with_context(|| LexerError::ReadFailed(self.current_index))?; - let end = self.current_index; - Ok(Some(Token::new(tok, start, end))) - } - } + let next = src.chars().next().ok_or(LexerError::UnexpectedEOF)?; - fn skip_whitespace(&mut self) { - let skipped = skip(self.remaining_text); - self.chomp(skipped); - } + let (token, size) = match next { + '.' => (TokenKind::Dot, 1), + '/' => (TokenKind::Slash, 1), + c if c.is_alphanumeric() || c == '-' || c == '.' => { + tokenize_ident(src).with_context(|| LexerError::IdentifierFailed)? + } + _ => Err(LexerError::UnknownChar(next))?, + }; - fn _next_token(&mut self) -> Result { - let (tok, bytes_read) = tokenize_single_token(self.remaining_text)?; - self.chomp(bytes_read); + let end = start + size; + tokens.push(Token::new(token, start, end)); - Ok(tok) - } - - fn chomp(&mut self, num_bytes: usize) { - self.remaining_text = &self.remaining_text[num_bytes..]; - self.current_index += num_bytes; - } + Ok(tiny_tokenizer(tokens, &src[size..], end)?) } -pub fn tokenize(src: &str) -> Result> { - let mut tokenizer = Tokenizer::new(src); - let mut tokens = Vec::new(); - - while let Some(tok) = tokenizer.next_token()? { - tokens.push(tok); - } +fn tokenize_ident(data: &str) -> TokResult<(TokenKind, 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)?, + // _ => {} + // } - Ok(tokens) -} + let (got, bytes_read) = take_while(data, |ch| ch == '-' || ch.is_alphanumeric() || ch == '.')?; -fn skip_whitespace(data: &str) -> usize { - match take_while(data, |ch| ch.is_whitespace()) { - Ok((_, bytes_skipped)) => bytes_skipped, - _ => 0, - } + let tok = TokenKind::Str(got.to_string()); + Ok((tok, bytes_read)) } -/// Skip past any whitespace characters or comments. -fn skip(src: &str) -> usize { - let mut remaining = src; +fn take_while(data: &str, mut pred: F) -> TokResult<(&str, usize)> +where + F: FnMut(char) -> bool, +{ + let mut current_index = 0; - loop { - let ws = skip_whitespace(remaining); - remaining = &remaining[ws..]; - // let comments = skip_comments(remaining); - // remaining = &remaining[comments..]; + for ch in data.chars() { + let should_continue = pred(ch); - if ws == 0 { - return src.len() - remaining.len(); + if !should_continue { + break; } + + current_index += ch.len_utf8(); + } + + if current_index == 0 { + Err(LexerError::NoMatches)? + } else { + Ok((&data[..current_index], current_index)) } } @@ -113,18 +92,6 @@ impl Display for TokenKind { } } -impl From for TokenKind { - fn from(value: String) -> Self { - TokenKind::Str(value) - } -} - -impl<'a> From<&'a str> for TokenKind { - fn from(value: &'a str) -> Self { - TokenKind::Str(value.to_string()) - } -} - #[derive(Debug)] pub struct Token { pub kind: TokenKind, @@ -141,64 +108,8 @@ impl Token { type TokResult = Result; -pub fn tokenize_single_token(data: &str) -> TokResult<(TokenKind, usize)> { - let next = match data.chars().next() { - Some(c) => c, - None => Err(LexerError::UnexpectedEOF)?, - }; - - Ok(match next { - '.' => (TokenKind::Dot, 1), - '/' => (TokenKind::Slash, 1), - c if c.is_alphanumeric() || c == '-' || c == '.' => { - tokenize_ident(data).with_context(|| LexerError::IdentifierFailed)? - } - _ => Err(LexerError::UnknownChar(next))?, - }) -} - -fn tokenize_ident(data: &str) -> TokResult<(TokenKind, 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 == '.')?; - - let tok = TokenKind::Str(got.to_string()); - Ok((tok, 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(Error, Debug)] pub enum LexerError { - #[error("Identifiers can't start with a number")] - StartWithNum, - #[error("Unexpected end of file")] UnexpectedEOF, @@ -210,7 +121,4 @@ pub enum LexerError { #[error("Unknown chars '{0}'")] UnknownChar(char), - - #[error("{0}: Could not read the next token")] - ReadFailed(usize), } From 11bc4726fe9114806c5d4a3ad16d65b9a2563200 Mon Sep 17 00:00:00 2001 From: TrickyPR <23250792+trickypr@users.noreply.github.com> Date: Sun, 18 Jun 2023 01:53:59 +1000 Subject: [PATCH 4/5] Implement variables --- terminal/src/error.rs | 2 +- terminal/src/fns.rs | 11 +-- terminal/src/main.rs | 1 + terminal/src/script/execute.rs | 60 ++++++++++------ terminal/src/script/mod.rs | 3 +- terminal/src/script/parser.rs | 115 +++++++++++++++++++++++++------ terminal/src/script/tokenizer.rs | 45 ++++++++---- 7 files changed, 175 insertions(+), 62 deletions(-) diff --git a/terminal/src/error.rs b/terminal/src/error.rs index 7066f0c..8f83b98 100644 --- a/terminal/src/error.rs +++ b/terminal/src/error.rs @@ -3,7 +3,7 @@ use core::{ error::Error, - fmt::{Debug, Display, Write}, + fmt::{Debug, Display}, }; extern crate alloc; diff --git a/terminal/src/fns.rs b/terminal/src/fns.rs index 79ce559..b09edcd 100644 --- a/terminal/src/fns.rs +++ b/terminal/src/fns.rs @@ -5,6 +5,7 @@ use kernel_userspace::service::get_public_service_id; use terminal::error::Result; use terminal::script::execute::{args_to_string, execute_expr}; use terminal::script::{execute::Value, parser::Expr, Environment}; +use userspace::{print, println}; pub fn pwd<'a>(env: &mut Environment<'a>, _args: Vec) -> Result { println!("{}", env.cwd); @@ -15,7 +16,7 @@ pub fn echo<'a>(env: &mut Environment<'a>, args: Vec) -> Result { if args.len() == 0 { println!("ECHO!"); } else { - // TODO: Variable echo + println!("{}", execute_expr(&args[0], env)?); } Ok(Value::Null) @@ -23,7 +24,7 @@ pub fn echo<'a>(env: &mut Environment<'a>, args: Vec) -> Result { pub fn disk<'a>(env: &mut Environment<'a>, args: Vec) -> Result { if args.len() == 1 { - if let Some(new_id) = execute_expr(&args[0])? + if let Some(new_id) = execute_expr(&args[0], env)? .to_string() .chars() .next() @@ -47,7 +48,7 @@ pub fn disk<'a>(env: &mut Environment<'a>, args: Vec) -> Result { pub fn ls<'a>(env: &mut Environment<'a>, args: Vec) -> Result { let fs_sid = get_public_service_id("FS").unwrap(); - let path = add_path(&env.cwd, &args_to_string(args)?); + 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()); @@ -65,7 +66,7 @@ pub fn ls<'a>(env: &mut Environment<'a>, args: Vec) -> Result { } pub fn cd<'a>(env: &mut Environment<'a>, args: Vec) -> Result { - env.cwd = add_path(&env.cwd, &args_to_string(args)?); + env.cwd = add_path(&env.cwd.clone(), &args_to_string(args, env)?); Ok(Value::Null) } @@ -73,7 +74,7 @@ pub fn cat<'a>(env: &mut Environment<'a>, args: Vec) -> Result { let fs_sid = get_public_service_id("FS").unwrap(); for file in args { - let file = execute_expr(&file)?.to_string(); + let file = execute_expr(&file, env)?.to_string(); let path = add_path(&env.cwd, &file); diff --git a/terminal/src/main.rs b/terminal/src/main.rs index df6a898..d31cf01 100644 --- a/terminal/src/main.rs +++ b/terminal/src/main.rs @@ -127,6 +127,7 @@ pub extern "C" fn main() { } } + 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 index 0f97e80..acd8301 100644 --- a/terminal/src/script/execute.rs +++ b/terminal/src/script/execute.rs @@ -20,32 +20,28 @@ use crate::error::Result; pub fn execute<'a>(stmts: Vec, env: &mut Environment<'a>) -> Result<()> { for stmt in stmts { - execute_single(stmt, env)?; + execute_stmt(stmt, env)?; } Ok(()) } -fn execute_single<'a>(stmt: Stmt, env: &mut Environment<'a>) -> Result<()> { +fn execute_stmt<'a>(stmt: Stmt, env: &mut Environment<'a>) -> Result<()> { match stmt { - Stmt::Execution { path, pos_args } => { - // TODO: Function resolution - - if path.starts_with("./") { - return execute_binary(path, pos_args, env); - } - - if env.has_function(&path) { - env.call_function(&path, pos_args)?; - return Ok(()); - } - - Err(ExecutionErrors::UnresolvedCall(path))? + 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: &Environment<'a>) -> Result<()> { +fn execute_binary<'a>(path: String, pos_args: Vec, env: &mut Environment<'a>) -> Result<()> { let fs_sid = get_public_service_id("FS").ok_or(ExecutionErrors::CouldNotFindFSSID)?; let elf_loader_sid = get_public_service_id("ELF_LOADER").ok_or(ExecutionErrors::CouldNotFindELFSID)?; @@ -71,25 +67,38 @@ fn execute_binary<'a>(path: String, pos_args: Vec, env: &Environment<'a>) destination: kernel_userspace::service::SendServiceMessageDest::ToProvider, message: kernel_userspace::service::ServiceMessageType::ElfLoader( contents.get_data(), - args_to_string(pos_args)?.as_bytes(), + args_to_string(pos_args, env)?.as_bytes(), ), })?; Ok(()) } -pub fn args_to_string(pos_args: Vec) -> Result { +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).unwrap().to_string()) + .map(|arg| execute_expr(arg, env).unwrap().to_string()) .collect::>() .join(" ")) } -pub fn execute_expr(expr: &Expr) -> Result { +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), }) } @@ -98,6 +107,7 @@ pub struct Environment<'a> { pub partition_id: u64, parent: Option<&'a mut Environment<'a>>, + variables: HashMap, functions: HashMap, } @@ -107,6 +117,7 @@ impl<'a> Environment<'a> { cwd, partition_id, parent: None, + variables: HashMap::new(), functions: HashMap::new(), } } @@ -116,6 +127,7 @@ impl<'a> Environment<'a> { cwd: env.cwd.clone(), partition_id: env.partition_id, parent: Some(env), + variables: HashMap::new(), functions: HashMap::new(), } } @@ -153,6 +165,14 @@ impl<'a> Environment<'a> { 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); + } } type InternalFunctionType = diff --git a/terminal/src/script/mod.rs b/terminal/src/script/mod.rs index a51a362..88b5d22 100644 --- a/terminal/src/script/mod.rs +++ b/terminal/src/script/mod.rs @@ -1,5 +1,6 @@ extern crate alloc; use alloc::vec::Vec; +use userspace::print; pub use self::execute::Environment; use self::parser::parse; @@ -15,9 +16,7 @@ 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 index 3445027..356fff3 100644 --- a/terminal/src/script/parser.rs +++ b/terminal/src/script/parser.rs @@ -7,35 +7,67 @@ use super::tokenizer::{ Token, TokenKind::{self, *}, }; -use crate::error::Result; +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() { - stmts.push(parse_exec(&mut parser)?); + 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_exec(p: &mut Parser) -> Result { - let path = parse_possible_strings(p)?; - let pos_args = parse_positional_arguments(p)?; +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::Execution { path, pos_args }) + Ok(Stmt::VarDec { id, expr }) } -fn parse_positional_arguments(p: &mut Parser) -> Result> { - let mut exprs = Vec::new(); +fn parse_exec(p: &mut Parser) -> Result { + let path = parse_possible_strings(p)?; + let mut pos_args = Vec::new(); - while !p.is_at_end() { - let param = parse_possible_strings(p)?; - exprs.push(Expr::String(param)); + while !p.is_at_end() + && match p.peek().context("Inside parse_exec")? { + Eq | StmtEnd => false, + _ => true, + } + { + pos_args.push(parse_expr(p)?); } - Ok(exprs) + 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 { @@ -43,7 +75,7 @@ fn parse_possible_strings(p: &mut Parser) -> Result { return Ok(String::new()); } - Ok(match p.peek()? { + Ok(match p.peek().context("Inside parse_possible_strings")? { Dot => { p.expect(Dot)?; format!(".{}", parse_possible_strings(p)?) @@ -56,9 +88,14 @@ fn parse_possible_strings(p: &mut Parser) -> Result { 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, @@ -73,6 +110,18 @@ impl Parser { 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)? @@ -90,20 +139,21 @@ impl Parser { } pub fn peek(&self) -> Result { - self.ensure_current()?; + self.ensure_current().context("Inside peek")?; Ok(self.tokens[self.index].kind.clone()) } pub fn consume(&mut self) -> Result { - self.ensure_next()?; - let current = self.peek()?; + 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()?; + self.ensure_current() + .with_context(|| format!("Expected: {}", token))?; if self.tokens[self.index].kind != token { Err(ParserError::ExpectedToken( @@ -119,7 +169,7 @@ impl Parser { } pub fn expect_id(&mut self) -> Result { - self.ensure_next()?; + self.ensure_next().context("Inside expect_id")?; let id = match &self.tokens[self.index].kind { Str(val) => val.clone(), @@ -131,21 +181,42 @@ impl Parser { 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)] +#[derive(Debug, Clone)] pub enum Stmt { - Execution { path: String, pos_args: Vec }, + Noop, + VarDec { id: String, expr: Expr }, + Execution(Expr), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Expr { + Exec { path: String, pos_args: Vec }, + Var(String), String(String), } #[derive(Debug, Error)] pub enum ParserError { - #[error("Expected token '{0}', found '{0}'")] + #[error("Unexpected token '{0}'")] + UnexpectedToken(TokenKind), + + #[error("Expected token '{0}', found '{1}'")] ExpectedToken(TokenKind, TokenKind), #[error("Expected identifier, found '{0}'")] diff --git a/terminal/src/script/tokenizer.rs b/terminal/src/script/tokenizer.rs index 5ac36f6..8dcfb99 100644 --- a/terminal/src/script/tokenizer.rs +++ b/terminal/src/script/tokenizer.rs @@ -1,6 +1,7 @@ extern crate alloc; use core::fmt::{Display, Write}; +use userspace::{print, println}; use alloc::{ string::{String, ToString}, @@ -11,28 +12,41 @@ use thiserror::Error; use crate::error::{Context, Result}; pub fn tiny_tokenizer(mut tokens: Vec, src: &str, start: usize) -> Result> { - if src.is_empty() { + let next = src.chars().next(); + if let None = next { return Ok(tokens); } - let next = src.chars().next().ok_or(LexerError::UnexpectedEOF)?; + 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 == '.' => { - tokenize_ident(src).with_context(|| LexerError::IdentifierFailed)? + let (str, length) = tokenize_str(src).with_context(|| LexerError::IdentifierFailed)?; + (TokenKind::Str(str), length) } - _ => Err(LexerError::UnknownChar(next))?, + _ => Err(LexerError::UnknownChar(start + 1, next))?, }; let end = start + size; tokens.push(Token::new(token, start, end)); - Ok(tiny_tokenizer(tokens, &src[size..], end)?) + tiny_tokenizer(tokens, &src[size..], end) } -fn tokenize_ident(data: &str) -> TokResult<(TokenKind, usize)> { +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)?, @@ -41,9 +55,7 @@ fn tokenize_ident(data: &str) -> TokResult<(TokenKind, usize)> { // } let (got, bytes_read) = take_while(data, |ch| ch == '-' || ch.is_alphanumeric() || ch == '.')?; - - let tok = TokenKind::Str(got.to_string()); - Ok((tok, bytes_read)) + Ok((got.to_string(), bytes_read)) } fn take_while(data: &str, mut pred: F) -> TokResult<(&str, usize)> @@ -71,9 +83,12 @@ where #[derive(Debug, Clone, PartialEq, Eq)] pub enum TokenKind { + Eq, Dot, Slash, + StmtEnd, Str(String), + Var(String), } impl Display for TokenKind { @@ -88,11 +103,17 @@ impl Display for TokenKind { 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)] +#[derive(Debug, Clone)] pub struct Token { pub kind: TokenKind, /// The location inside of the initial tokenizing string @@ -119,6 +140,6 @@ pub enum LexerError { #[error("No matches")] NoMatches, - #[error("Unknown chars '{0}'")] - UnknownChar(char), + #[error("{0}: Unknown chars '{1}'")] + UnknownChar(usize, char), } From b6d8957115eaa08feba594aa5499ef8d50c4fdde Mon Sep 17 00:00:00 2001 From: TrickyPR <23250792+trickypr@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:32:36 +1000 Subject: [PATCH 5/5] Update for new branch --- builder/src/main.rs | 6 ++++++ kernel_userspace/src/fs.rs | 6 +++++- terminal/src/fns.rs | 13 ++++--------- terminal/src/main.rs | 1 - terminal/src/script/execute.rs | 32 +++++++++++++++----------------- 5 files changed, 30 insertions(+), 28 deletions(-) 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/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/terminal/src/fns.rs b/terminal/src/fns.rs index dbab333..e6c1559 100644 --- a/terminal/src/fns.rs +++ b/terminal/src/fns.rs @@ -38,7 +38,7 @@ pub fn disk<'a>(env: &mut Environment<'a>, args: Vec) -> Result { let fs_sid = env.services.ok_or(FuncExecError::UninitedService)?.fs; println!("Drives:"); - for part in get_disks(fs_sid, env.services_buffer()?).iter() { + for part in get_disks(fs_sid, env.services_buffer()?)?.iter() { println!("{}:", part) } @@ -55,7 +55,7 @@ pub fn ls<'a>(env: &mut Environment<'a>, args: Vec) -> Result { env.partition_id as usize, path.as_str(), env.services_buffer()?, - ); + )?; match stat { StatResponse::File(_) => println!("This is a file"), @@ -64,7 +64,6 @@ pub fn ls<'a>(env: &mut Environment<'a>, args: Vec) -> Result { println!("{child}") } } - StatResponse::NotFound => println!("Invalid Path"), }; Ok(Value::Null) @@ -88,7 +87,7 @@ pub fn cat<'a>(env: &mut Environment<'a>, args: Vec) -> Result { env.partition_id as usize, path.as_str(), env.services_buffer()?, - ); + )?; let file = match stat { StatResponse::File(f) => f, @@ -96,10 +95,6 @@ pub fn cat<'a>(env: &mut Environment<'a>, args: Vec) -> Result { println!("Not a file"); continue; } - StatResponse::NotFound => { - println!("File not found"); - continue; - } }; for i in 0..file.file_size / 512 { @@ -109,7 +104,7 @@ pub fn cat<'a>(env: &mut Environment<'a>, args: Vec) -> Result { file.node_id, i as u32, env.services_buffer()?, - ); + )?; if let Some(data) = sect { print!("{}", String::from_utf8_lossy(data)) } else { diff --git a/terminal/src/main.rs b/terminal/src/main.rs index 2782773..34b124a 100644 --- a/terminal/src/main.rs +++ b/terminal/src/main.rs @@ -4,7 +4,6 @@ use kernel_userspace::{ ids::ServiceID, - service::{get_public_service_id, ServiceMessageType}, syscall::{exit, receive_service_message_blocking, service_subscribe}, }; use terminal::script::{execute, Environment}; diff --git a/terminal/src/script/execute.rs b/terminal/src/script/execute.rs index 31d9c5c..60c2778 100644 --- a/terminal/src/script/execute.rs +++ b/terminal/src/script/execute.rs @@ -9,8 +9,9 @@ use alloc::{ }; use hashbrown::HashMap; use kernel_userspace::{ + elf::LoadElfError, fs::{self, add_path, read_full_file, StatResponse}, - ids::ServiceID, + ids::{ProcessID, ServiceID}, service::{generate_tracking_number, get_public_service_id, ServiceMessage}, syscall::{send_and_get_response_service_message, CURRENT_PID}, }; @@ -56,12 +57,11 @@ fn execute_binary<'a>(path: String, pos_args: Vec, env: &mut Environment<' env.partition_id as usize, &path, env.services_buffer()?, - ); + )?; let file = match stat { StatResponse::File(ref f) => f.clone(), StatResponse::Folder(_) => Err(ExecutionErrors::ExecNotAFile)?, - StatResponse::NotFound => Err(ExecutionErrors::ExecCouldNotFind)?, }; drop(stat); @@ -72,26 +72,24 @@ fn execute_binary<'a>(path: String, pos_args: Vec, env: &mut Environment<' 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)?; - 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: kernel_userspace::service::ServiceMessageType::ElfLoader( - &contents, - args.as_bytes(), - ), - }, - env.services_buffer()?, - )?; + 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(()) }