From f8d44fc8dc03ab905810014910fe3c3bfb949c45 Mon Sep 17 00:00:00 2001 From: SooHoon Choi Date: Thu, 22 Aug 2024 21:19:50 -0700 Subject: [PATCH] wasm --- .gitignore | 2 + src/wasm/Cargo.lock | 197 ++++++++++++++++++ src/wasm/Cargo.toml | 17 ++ src/wasm/src/fs/directory.rs | 16 ++ src/wasm/src/fs/file.rs | 17 ++ src/wasm/src/fs/mod.rs | 3 + src/wasm/src/fs/node.rs | 70 +++++++ src/wasm/src/lib.rs | 65 ++++++ src/wasm/src/shell/exec.rs | 377 +++++++++++++++++++++++++++++++++++ src/wasm/src/shell/lexer.rs | 89 +++++++++ src/wasm/src/shell/mod.rs | 2 + 11 files changed, 855 insertions(+) create mode 100644 src/wasm/Cargo.lock create mode 100644 src/wasm/Cargo.toml create mode 100644 src/wasm/src/fs/directory.rs create mode 100644 src/wasm/src/fs/file.rs create mode 100644 src/wasm/src/fs/mod.rs create mode 100644 src/wasm/src/fs/node.rs create mode 100644 src/wasm/src/lib.rs create mode 100644 src/wasm/src/shell/exec.rs create mode 100644 src/wasm/src/shell/lexer.rs create mode 100644 src/wasm/src/shell/mod.rs diff --git a/.gitignore b/.gitignore index 8f322f0..77a82e4 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +target diff --git a/src/wasm/Cargo.lock b/src/wasm/Cargo.lock new file mode 100644 index 0000000..41cf66b --- /dev/null +++ b/src/wasm/Cargo.lock @@ -0,0 +1,197 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.208" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "soohoonc_wasm" +version = "0.0.1" +dependencies = [ + "once_cell", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "syn" +version = "2.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/src/wasm/Cargo.toml b/src/wasm/Cargo.toml new file mode 100644 index 0000000..db15575 --- /dev/null +++ b/src/wasm/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "soohoonc_wasm" +version = "0.0.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "soohoonc_wasm" +crate-type = ["cdylib"] +bench = true + +[dependencies] +wasm-bindgen = "0.2.83" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = { version = "1.0.108" } +web-sys = { version = "0.3.66", features = ["console"] } +once_cell = "1.19.0" diff --git a/src/wasm/src/fs/directory.rs b/src/wasm/src/fs/directory.rs new file mode 100644 index 0000000..b32a6b7 --- /dev/null +++ b/src/wasm/src/fs/directory.rs @@ -0,0 +1,16 @@ +use std::cell::RefCell; +use std::rc::Rc; +// use std::sync::{Arc, Mutex}; +use super::node::Node; + +#[derive(Clone, PartialEq)] +pub struct Directory { + pub name: String, + children: Vec>>, +} + +impl Directory { + pub fn new(name: String, children: Vec>>) -> Directory { + Directory { name, children } + } +} diff --git a/src/wasm/src/fs/file.rs b/src/wasm/src/fs/file.rs new file mode 100644 index 0000000..ccd4b22 --- /dev/null +++ b/src/wasm/src/fs/file.rs @@ -0,0 +1,17 @@ +#[derive(Clone, PartialEq)] +pub struct File { + pub name: String, + data: String, +} + +impl File { + pub fn new(name: String, data: String) -> File { + File { name, data } + } + pub fn read(&self) -> String { + self.data.clone() + } + pub fn write(&mut self, data: String) { + self.data = data; + } +} diff --git a/src/wasm/src/fs/mod.rs b/src/wasm/src/fs/mod.rs new file mode 100644 index 0000000..aed4cf3 --- /dev/null +++ b/src/wasm/src/fs/mod.rs @@ -0,0 +1,3 @@ +pub mod directory; +pub mod file; +pub mod node; diff --git a/src/wasm/src/fs/node.rs b/src/wasm/src/fs/node.rs new file mode 100644 index 0000000..6271121 --- /dev/null +++ b/src/wasm/src/fs/node.rs @@ -0,0 +1,70 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::directory::Directory; +use super::file::File; +// use std::sync::{Arc, Mutex}; + +#[derive(Clone, PartialEq)] +pub enum NodeType { + Directory(Directory), + File(File), +} + +#[derive(Clone, PartialEq)] +pub struct Node { + name: String, + node_type: NodeType, + parent: Option>>, + children: Vec>>, +} + +impl Node { + pub fn new(name: String, node_type: NodeType, parent: Option>>) -> Node { + Node { + name, + node_type, + parent, + children: Vec::new(), + } + } + + pub fn as_directory(&self) -> Option<&Directory> { + if let NodeType::Directory(ref dir) = self.node_type { + Some(dir) + } else { + None + } + } + + pub fn as_file(&self) -> Option<&File> { + if let NodeType::File(ref file) = self.node_type { + Some(file) + } else { + None + } + } + + pub fn get_node_type(&self) -> NodeType { + self.node_type.clone() + } + + pub fn get_name(&self) -> String { + self.name.clone() + } + + pub fn get_parent(&self) -> Option>> { + match &self.parent { + Some(parent) => Some(Rc::clone(parent)), + None => None, + } + } + + pub fn get_children(&self) -> Vec>> { + self.children.clone() + } + + pub fn add_child(&mut self, child: Rc>) { + self.children.push(Rc::clone(&child)); + } +} diff --git a/src/wasm/src/lib.rs b/src/wasm/src/lib.rs new file mode 100644 index 0000000..4e37bb5 --- /dev/null +++ b/src/wasm/src/lib.rs @@ -0,0 +1,65 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use wasm_bindgen::prelude::*; +// use web_sys::console; +use serde_json; + +mod fs; +mod shell; + +use fs::directory::Directory; +use fs::node::{Node, NodeType}; +use shell::exec::Exec; +use shell::lexer::Lexer; +// use filesystem::pipe::Pipe; + +#[wasm_bindgen] +pub struct Shell { + lexer: Lexer, + exec: Exec, + root: Rc>, + current: Rc>, + // pipe: Pipe, + user: Rc>, + hostname: Rc>, +} + +#[wasm_bindgen] +impl Shell { + #[wasm_bindgen(constructor)] + pub fn new(user: String, hostname: String) -> Shell { + let root = Rc::new(RefCell::new(Node::new( + "".to_string(), + NodeType::Directory(Directory::new("".to_string(), Vec::new())), + None, + ))); + + Shell { + lexer: Lexer::new(), + exec: Exec::new(), + current: Rc::clone(&root), + root, + // pipe: Pipe::new(""), + user: Rc::new(RefCell::new(user)), + hostname: Rc::new(RefCell::new(hostname)), + } + } + + /** Only one statement for now */ + pub fn run(&mut self, input: &str) -> String { + let statement = self.lexer.lex(input); + let result = self.exec.execute( + statement, + &self.root, + &self.current, + &self.user, + &self.hostname, + ); + // console::log_1(&result.clone().into()); + + let output_str = serde_json::to_string(&result).unwrap(); + // console::log_1(&output_str.clone().into()); + output_str + } +} diff --git a/src/wasm/src/shell/exec.rs b/src/wasm/src/shell/exec.rs new file mode 100644 index 0000000..8454ec5 --- /dev/null +++ b/src/wasm/src/shell/exec.rs @@ -0,0 +1,377 @@ +use serde_json::Value; +use std::cell::RefCell; +use std::rc::Rc; +use wasm_bindgen::prelude::*; +use web_sys::console; + +use crate::fs::directory::Directory; +use crate::fs::file::File; +use crate::fs::node::{Node, NodeType}; +// use crate::filesystem::directory::Directory; +use super::lexer::Statement; + +pub struct Exec {} + +impl Exec { + pub fn new() -> Exec { + Exec {} + } + + fn ls(current: &Rc>) -> String { + let mut children: Vec = Vec::new(); + let current_node = current.borrow(); + for child in current_node.get_children() { + children.push(child.borrow().get_name().to_owned()); + } + serde_json::to_string(&children).unwrap() + } + + fn pwd(current: &Rc>) -> String { + let mut path_string = String::new(); + let mut current_node = Rc::clone(¤t); + loop { + path_string = format!("{}/{}", current_node.borrow().get_name(), path_string); + let parent = current_node.borrow().get_parent(); + // console::log_1(&JsValue::from_str(¤t_node.borrow().get_name())); + match parent { + Some(p) => { + // console::log_1(&JsValue::from_str(&p.borrow().get_name())); + current_node = Rc::clone(&p) + } + None => break, + } + } + serde_json::to_string(&path_string).unwrap() + } + fn cd(current: &Rc>, root: &Rc>, args: Vec) -> String { + let new_path = { + if args.first().is_some() { + args.first().unwrap().clone() + } else { + "/".to_owned() + } + }; + let mut current_node = { + if new_path.starts_with("/") { + Rc::clone(&root) + } else { + Rc::clone(¤t) + } + }; + // console::log_1(&JsValue::from_str(new_path.as_str())); + for path in new_path.split("/").filter(|&x| !x.is_empty()) { + // console::log_1(&JsValue::from_str(path)); + if path == "." { + continue; + } else if path == ".." { + let parent = current.borrow().get_parent(); + match parent { + Some(parent) => current_node = parent, + None => (), + } + } else { + let mut found = false; + // console::log_1(&JsValue::from_str("Searching")); + let children = current_node.borrow().get_children(); + for child in children { + // console::log_1(&JsValue::from_str(child.borrow().get_name().as_str())); + let child_node = Rc::clone(&child); + // console::log_2(&JsValue::from_str(&child_node.get_name()), &JsValue::from_str(path)); + let child_node_type = child_node.borrow().get_node_type(); + if child_node.borrow().get_name() == path { + match child_node_type { + NodeType::Directory(_) => { + current_node = child_node; + found = true; + break; + } + NodeType::File(_) => { + return "Not a directory".to_string(); + } + } + } + } + if !found { + return "Directory not found".to_string(); + } + } + } + *current.borrow_mut() = current_node.borrow().clone(); + return " ".to_string(); + } + + fn mkdir(current: &Rc>, root: &Rc>, args: Vec) -> String { + // let new_node = Rc::new(RefCell::new(Node::new(new_dir, NodeType::Directory, Some(Rc::clone(&self.current)))); + // self.current.borrow_mut().add_child(Rc::clone(&new_node)); + // Ok(JsValue::from_str("Success")) + if args.len() == 0 { + return "usage: mkdir [-pv] [-m mode] directory_name ...".to_string(); + } + let new_path = args.first().unwrap().clone(); + let mut current_node = { + if new_path.starts_with("/") { + Rc::clone(&root) + } else { + Rc::clone(¤t) + } + }; + if current_node.borrow().as_directory().is_none() { + return "Not a directory".to_string(); + } + + for path in new_path.split("/").filter(|&x| !x.is_empty()) { + let mut found = false; + let children = current_node.borrow().get_children(); + for child in children { + let child_node_type = child.borrow().get_node_type(); + let child_node_name = child.borrow().get_name(); + if child_node_name == path { + match child_node_type { + NodeType::Directory(_) => { + current_node = Rc::clone(&child); // Use the cloned child + found = true; + break; + } + NodeType::File(_) => { + return "Not a directory".to_string(); + } + } + } + } + if !found { + let new_node = Rc::new(RefCell::new(Node::new( + path.to_string(), + NodeType::Directory(Directory::new(path.to_string(), Vec::new())), + Some(Rc::clone(¤t_node)), + ))); + current_node.borrow_mut().add_child(Rc::clone(&new_node)); + current_node = Rc::clone(&new_node); + } + } + " ".to_string() + } + + fn echo(&self, args: Vec) -> String { + let mut output = String::new(); + for arg in args { + output.push_str(arg.as_str()); + output.push_str(" "); + } + output.push_str(" "); + output + } + + fn touch(current: &Rc>, args: Vec) -> String { + if args.first().is_none() { + return "usage: touch file_name".to_string(); + } + let new_file = args.first().unwrap().clone(); + let cloned_new_file = new_file.clone(); + let new_node = Rc::new(RefCell::new(Node::new( + cloned_new_file, + NodeType::File(File::new(new_file, " ".to_string())), + Some(Rc::clone(¤t)), + ))); + current.borrow_mut().add_child(Rc::clone(&new_node)); + " ".to_string() + } + + fn cat(current: &Rc>, root: &Rc>, args: Vec) -> String { + if args.is_empty() { + return "usage: cat file_name".to_string(); + } + + let file_name = args[0].clone(); + let mut current_node = if file_name.starts_with("/") { + Rc::clone(&root) + } else { + Rc::clone(¤t) + }; + + let parts: Vec<&str> = file_name.split('/').filter(|&x| !x.is_empty()).collect(); + let mut file_node: Option = None; + let mut found = false; + + for (i, node) in parts.iter().enumerate() { + let children = current_node.borrow().get_children(); + for child in children { + let child_node = Rc::clone(&child); + if child_node.borrow().get_name() == *node { + match child_node.borrow().get_node_type() { + NodeType::File(ref file) => { + if i == parts.len() - 1 { + file_node = Some(file.clone()); + found = true; + } else { + return "Not a directory".to_string(); + } + } + NodeType::Directory(_) => { + if i < parts.len() - 1 { + current_node = Rc::clone(&child_node); + found = true; + } else { + return "Unsupported action for cat".to_string(); + } + } + } + break; // Exit the inner loop once the match is found + } + } + if !found { + return "File not found".to_string(); + } + found = false; // Reset found for the next iteration + } + + match file_node { + Some(file_node) => file_node.read(), + None => "File not found".to_string(), + } + } + + fn write(current: &Rc>, root: &Rc>, args: Vec) -> String { + // Validate arguments + if args.len() < 2 { + return "usage: write file_name 'content'".to_string(); + } + + let file_name = args[0].clone(); + let content = args[1].clone(); + + let mut current_node = if file_name.starts_with("/") { + Rc::clone(&root) + } else { + Rc::clone(¤t) + }; + + // console::log_1(&JsValue::from_str(file_name.as_str())); + // console::log_1(&JsValue::from_str(content.as_str())); + + let parts: Vec<&str> = file_name.split('/').filter(|&x| !x.is_empty()).collect(); + let mut file_node: Option>> = None; + let mut found = false; + + for (i, node) in parts.iter().enumerate() { + let children = current_node.borrow().get_children(); + for child in children { + let child_node = Rc::clone(&child); + if child_node.borrow().get_name() == *node { + match child_node.borrow().get_node_type() { + NodeType::File(_) => { + if i == parts.len() - 1 { + file_node = Some(Rc::clone(&child)); + found = true; + } else { + return "File not found".to_string(); + } + } + NodeType::Directory(_) => { + if i < parts.len() - 1 { + current_node = Rc::clone(&child_node); + found = true; + } else { + return "Unsupported action for write".to_string(); + } + } + } + break; // Exit the inner loop once the match is found + } + } + if !found { + return "File not found".to_string(); + } + found = false; // Reset found for the next iteration + } + + match file_node { + Some(file_node) => match file_node.borrow().get_node_type() { + NodeType::Directory(_) => { + return "Not a file".to_string(); + } + NodeType::File(ref mut file) => file.write(content), + }, + None => return "File not found".to_string(), + } + return " ".to_string(); + } + + pub fn execute( + &mut self, + statements: Vec, + root: &Rc>, + current: &Rc>, + user: &Rc>, + hostname: &Rc>, + ) -> Value { + let mut output = String::new(); + // let mut previous_output = None; + + for (i, statement) in statements.iter().enumerate() { + if i > 0 { + if let Some(op) = statements[i - 1].operators.last() { + match op.as_str() { + // "|" => { + // // Handle piping (not implemented in this example) + // // Assume piping is handled via previous_output + // previous_output = Some(output.clone()); + // } + // ">" => { + // // Handle output redirection + // if let Some(file_name) = statement.arguments.first() { + // std::fs::write(file_name, output.clone()).expect("Unable to write file"); + // output.clear(); + // } + // } + // ">>" => { + // // Handle append output redirection + // if let Some(file_name) = statement.arguments.first() { + // use std::fs::OpenOptions; + // let mut file = OpenOptions::new().append(true).open(file_name).expect("Unable to open file"); + // use std::io::Write; + // write!(file, "{}", output).expect("Unable to write file"); + // output.clear(); + // } + // } + _ => {} + } + } + } + + output = match &statement.command[..] { + "" => " ".to_owned(), + "github" => "github" + .to_owned(), + "help" => "

\nhelp command\n

".to_owned(), + "license" => "license command".to_owned(), + "ls" => Self::ls(current), + "pwd" => Self::pwd(current), + "cd" => Self::cd(current, root, statement.arguments.clone()), + "clear" => "".to_owned(), + "echo" => Self::echo(self, statement.arguments.clone()), + "cat" => Self::cat(current, root, statement.arguments.clone()), + "touch" => Self::touch(current, statement.arguments.clone()), + "mkdir" => Self::mkdir(current, root, statement.arguments.clone()), + "rm" => "rm command".to_owned(), + "rmdir" => "rmdir command".to_owned(), + "mv" => "mv command".to_owned(), + "cp" => "cp command".to_owned(), + "exit" => "Goodbye!".to_owned(), + "whoami" => user.borrow().to_owned(), + "host" => hostname.borrow().to_owned(), + "which" => "which command".to_owned(), + "write" => Self::write(current, root, statement.arguments.clone()), + command => (command.to_owned() + ": command not found").to_owned(), + }; + } + + // return json like object + let output = serde_json::json!({ + "result": output, + "user": user.borrow().to_owned(), + "host": hostname.borrow().to_owned(), + "path": current.borrow().get_name(), + }); + output + } +} diff --git a/src/wasm/src/shell/lexer.rs b/src/wasm/src/shell/lexer.rs new file mode 100644 index 0000000..6390316 --- /dev/null +++ b/src/wasm/src/shell/lexer.rs @@ -0,0 +1,89 @@ +pub enum Token { + Command(String), + Operator(String), + Option(String), + Argument(String), + Quote(String), + LParen(String), + RParen(String), +} + +pub struct Statement { + pub command: String, + pub operators: Vec, + pub options: Vec, + pub arguments: Vec, +} + +impl Statement { + pub fn new() -> Statement { + Statement { + command: String::new(), + operators: Vec::new(), + options: Vec::new(), + arguments: Vec::new(), + } + } + + pub fn push(&mut self, token: Token) { + match token { + Token::Command(command) => self.command = command, + Token::Operator(operator) => self.operators.push(operator), + Token::Option(option) => self.options.push(option), + Token::Argument(argument) => self.arguments.push(argument), + _ => {} + } + } +} + +pub struct Lexer {} + +impl Lexer { + pub fn new() -> Lexer { + Lexer {} + } + + pub fn lex(&self, input: &str) -> Vec { + let mut statements = Vec::new(); + let mut current_statement = Statement::new(); + let mut input_split = input.split_whitespace(); + + while let Some(token) = input_split.next() { + match token { + "|" | ">" | ">>" => { + current_statement.push(Token::Operator(token.to_string())); + statements.push(current_statement); + current_statement = Statement::new(); + } + + "'" | "\"" => { + let mut quote = token.to_string(); + while let Some(next_token) = input_split.next() { + quote.push_str(" "); + quote.push_str(next_token); + if next_token.ends_with(token) { + break; + } + } + current_statement.push(Token::Quote(quote)); + } + _ => { + if current_statement.command.is_empty() { + current_statement.push(Token::Command(token.to_string())); + } else if token.starts_with("--") { + current_statement.push(Token::Option(token.to_string())); + } else if token.starts_with("-") { + token.chars().skip(1).for_each(|c| { + current_statement.push(Token::Option(c.to_string())); + }); + } else { + current_statement.push(Token::Argument(token.to_string())); + } + } + } + } + + statements.push(current_statement); + statements + } +} diff --git a/src/wasm/src/shell/mod.rs b/src/wasm/src/shell/mod.rs new file mode 100644 index 0000000..1a0ef2e --- /dev/null +++ b/src/wasm/src/shell/mod.rs @@ -0,0 +1,2 @@ +pub mod exec; +pub mod lexer;