diff --git a/CHANGELOG.md b/CHANGELOG.md index 65fa5c874..e60aa8193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## Unreleased +- Add calc command #263 +- Add website (#261) - Fix VGA issues with real hardware (#258) - Add rust binaries support (#255) - Add dynamical disk information (#252) diff --git a/Cargo.lock b/Cargo.lock index 6c9e7255a..308b3848c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -236,6 +236,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "moros" version = "0.6.0" @@ -251,6 +257,7 @@ dependencies = [ "libm", "linked_list_allocator", "littlewing", + "nom", "object", "pbkdf2", "pc-keyboard", @@ -274,6 +281,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +[[package]] +name = "nom" +version = "7.0.0" +source = "git+https://github.com/Geal/nom#02433c2c98a0448d30818ee057d303f2e63b8288" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + [[package]] name = "num-traits" version = "0.2.14" diff --git a/Cargo.toml b/Cargo.toml index 50e58e4fb..ec64619db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ lazy_static = { version = "1.4.0", features = ["spin_no_std"] } libm = "0.2.1" linked_list_allocator = "0.9.0" littlewing = { version = "0.7.0", default-features = false } +nom = { git = "https://github.com/Geal/nom", default-features = false, features = ["alloc"] } object = { version = "0.26.2", default-features = false, features = ["read"] } pbkdf2 = { version = "0.9.0", default-features = false } pc-keyboard = "0.5.1" diff --git a/src/usr/calc.rs b/src/usr/calc.rs new file mode 100644 index 000000000..60c04a0c2 --- /dev/null +++ b/src/usr/calc.rs @@ -0,0 +1,182 @@ +use crate::usr; +use crate::api::prompt::Prompt; +use crate::api::console::Style; + +use alloc::boxed::Box; +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; + +use nom::branch::alt; +use nom::character::complete::{char, space0}; +use nom::number::complete::float; +use nom::combinator::map; +use nom::multi::many0; +use nom::sequence::{delimited, tuple}; +use nom::IResult; + +// Adapted from Basic Calculator +// Copyright 2021 Balaji Sivaraman +// https://github.com/balajisivaraman/basic_calculator_rs + +#[derive(Debug, PartialEq)] +pub enum Exp { + Num(f32), + Add(Box, Box), + Sub(Box, Box), + Mul(Box, Box), + Div(Box, Box), + Exp(Box, Box), +} + +// Parser + +fn parse(input: &str) -> IResult<&str, Exp> { + let (input, num1) = parse_term(input)?; + let (input, exps) = many0(tuple((alt((char('+'), char('-'))), parse_term)))(input)?; + Ok((input, parse_exp(num1, exps))) +} + +fn parse_term(input: &str) -> IResult<&str, Exp> { + let (input, num1) = parse_factor(input)?; + let (input, exps) = many0(tuple((alt((char('/'), char('*'))), parse_factor)))(input)?; + Ok((input, parse_exp(num1, exps))) +} + +fn parse_factor(input: &str) -> IResult<&str, Exp> { + let (input, num1) = alt((parse_parens, parse_num))(input)?; + let (input, exps) = many0(tuple((char('^'), parse_factor)))(input)?; + Ok((input, parse_exp(num1, exps))) +} + +fn parse_parens(input: &str) -> IResult<&str, Exp> { + delimited(space0, delimited(char('('), parse, char(')')), space0)(input) +} + +fn parse_num(input: &str) -> IResult<&str, Exp> { + map(delimited(space0, float, space0), Exp::Num)(input) +} + +fn parse_exp(exp: Exp, rem: Vec<(char, Exp)>) -> Exp { + rem.into_iter().fold(exp, |acc, val| parse_op(val, acc)) +} + +fn parse_op(tup: (char, Exp), exp1: Exp) -> Exp { + let (op, exp2) = tup; + match op { + '+' => Exp::Add(Box::new(exp1), Box::new(exp2)), + '-' => Exp::Sub(Box::new(exp1), Box::new(exp2)), + '*' => Exp::Mul(Box::new(exp1), Box::new(exp2)), + '/' => Exp::Div(Box::new(exp1), Box::new(exp2)), + '^' => Exp::Exp(Box::new(exp1), Box::new(exp2)), + _ => panic!("Unknown operation"), + } +} + +// Evaluation + +fn eval(exp: Exp) -> f32 { + match exp { + Exp::Num(num) => num, + Exp::Add(exp1, exp2) => eval(*exp1) + eval(*exp2), + Exp::Sub(exp1, exp2) => eval(*exp1) - eval(*exp2), + Exp::Mul(exp1, exp2) => eval(*exp1) * eval(*exp2), + Exp::Div(exp1, exp2) => eval(*exp1) / eval(*exp2), + Exp::Exp(exp1, exp2) => libm::powf(eval(*exp1), eval(*exp2)), + } +} + +// REPL + +fn parse_eval(line: &str) -> Result { + match parse(&line) { + Ok((line, parsed)) => { + if line.is_empty() { + Ok(eval(parsed)) + } else { + Err(format!("Could not parse '{}'", line)) + } + }, + Err(_) => { + Err(format!("Could not parse '{}'", line)) + }, + } +} + +fn repl() -> usr::shell::ExitCode { + println!("MOROS Calc v0.1.0\n"); + let csi_color = Style::color("Cyan"); + let csi_error = Style::color("LightRed"); + let csi_reset = Style::reset(); + let prompt_string = format!("{}>{} ", csi_color, csi_reset); + + let mut prompt = Prompt::new(); + let history_file = "~/.calc-history"; + prompt.history.load(history_file); + + while let Some(line) = prompt.input(&prompt_string) { + if line == "exit" || line == "quit" { + break; + } + + match parse_eval(&line) { + Ok(res) => { + println!("{}\n", res); + } + Err(msg) => { + println!("{}Error:{} {}\n", csi_error, csi_reset, msg); + continue; + } + } + + if !line.is_empty() { + prompt.history.add(&line); + prompt.history.save(history_file); + } + } + usr::shell::ExitCode::CommandSuccessful +} + +pub fn main(args: &[&str]) -> usr::shell::ExitCode { + if args.len() == 1 { + repl() + } else { + match parse_eval(&args[1..].join(" ")) { + Ok(res) => { + println!("{}", res); + usr::shell::ExitCode::CommandSuccessful + } + Err(msg) => { + println!("{}", msg); + usr::shell::ExitCode::CommandError + } + } + } +} + +#[test_case] +pub fn test_calc() { + macro_rules! eval { + ($e:expr) => { + format!("{}", parse_eval($e).unwrap()) + }; + } + + assert_eq!(eval!("1"), "1"); + assert_eq!(eval!("1.5"), "1.5"); + + assert_eq!(eval!("1 + 2"), "3"); + assert_eq!(eval!("1 + 2 + 3"), "6"); + assert_eq!(eval!("1 + 2.5"), "3.5"); + assert_eq!(eval!("1 + 2.5"), "3.5"); + assert_eq!(eval!("2 - 1"), "1"); + assert_eq!(eval!("1 - 2"), "-1"); + assert_eq!(eval!("2 * 3"), "6"); + assert_eq!(eval!("2 * 3.5"), "7"); + assert_eq!(eval!("6 / 2"), "3"); + assert_eq!(eval!("6 / 4"), "1.5"); + assert_eq!(eval!("2 ^ 4"), "16"); + + assert_eq!(eval!("2 * 3 + 4"), "10"); + assert_eq!(eval!("2 * (3 + 4)"), "14"); +} diff --git a/src/usr/lisp.rs b/src/usr/lisp.rs index 67a0356cc..a6219ce43 100644 --- a/src/usr/lisp.rs +++ b/src/usr/lisp.rs @@ -78,11 +78,11 @@ fn tokenize(expr: &str) -> Vec { } fn parse(tokens: &[String]) -> Result<(Exp, &[String]), Err> { - let (token, rest) = tokens.split_first().ok_or(Err::Reason("could not get token".to_string()))?; + let (token, rest) = tokens.split_first().ok_or(Err::Reason("Could not get token".to_string()))?; match &token[..] { "'" => parse_quoted(rest), "(" => read_seq(rest), - ")" => Err(Err::Reason("unexpected `)`".to_string())), + ")" => Err(Err::Reason("Unexpected `)`".to_string())), _ => Ok((parse_atom(token), rest)), } } @@ -91,7 +91,7 @@ fn read_seq(tokens: &[String]) -> Result<(Exp, &[String]), Err> { let mut res: Vec = vec![]; let mut xs = tokens; loop { - let (next_token, rest) = xs.split_first().ok_or(Err::Reason("could not find closing `)`".to_string()))?; + let (next_token, rest) = xs.split_first().ok_or(Err::Reason("Could not find closing `)`".to_string()))?; if next_token == ")" { return Ok((Exp::List(res), rest)) // skip `)`, head to the token after } @@ -103,7 +103,7 @@ fn read_seq(tokens: &[String]) -> Result<(Exp, &[String]), Err> { fn parse_quoted(tokens: &[String]) -> Result<(Exp, &[String]), Err> { let xs = tokens; - let (next_token, _) = xs.split_first().ok_or(Err::Reason("could not parse quote".to_string()))?; + let (next_token, _) = xs.split_first().ok_or(Err::Reason("Could not parse quote".to_string()))?; let (exp, rest) = if next_token == "(" { read_seq(&tokens[1..])? // Skip "(" } else { @@ -133,7 +133,7 @@ macro_rules! ensure_tonicity { ($check_fn:expr) => { |args: &[Exp]| -> Result { let floats = parse_list_of_floats(args)?; - let first = floats.first().ok_or(Err::Reason("expected at least one number".to_string()))?; + let first = floats.first().ok_or(Err::Reason("Expected at least one number".to_string()))?; let rest = &floats[1..]; fn f (prev: &f64, xs: &[f64]) -> bool { match xs.first() { @@ -171,7 +171,7 @@ fn default_env<'a>() -> Env<'a> { Exp::Func( |args: &[Exp]| -> Result { let floats = parse_list_of_floats(args)?; - let first = *floats.first().ok_or(Err::Reason("expected at least one number".to_string()))?; + let first = *floats.first().ok_or(Err::Reason("Expected at least one number".to_string()))?; let sum_of_rest = floats[1..].iter().fold(0.0, |sum, a| sum + a); Ok(Exp::Number(first - sum_of_rest)) } @@ -208,19 +208,19 @@ fn parse_list_of_floats(args: &[Exp]) -> Result, Err> { fn parse_single_float(exp: &Exp) -> Result { match exp { Exp::Number(num) => Ok(*num), - _ => Err(Err::Reason("expected a number".to_string())), + _ => Err(Err::Reason("Expected a number".to_string())), } } // Eval fn eval_quote_args(arg_forms: &[Exp]) -> Result { - let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_form = arg_forms.first().ok_or(Err::Reason("Expected first form".to_string()))?; Ok(first_form.clone()) } fn eval_atom_args(arg_forms: &[Exp], env: &mut Env) -> Result { - let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_form = arg_forms.first().ok_or(Err::Reason("Expected first form".to_string()))?; let first_eval = eval(first_form, env)?; match first_eval { Exp::Symbol(_) => Ok(Exp::Bool(true)), @@ -229,9 +229,9 @@ fn eval_atom_args(arg_forms: &[Exp], env: &mut Env) -> Result { } fn eval_eq_args(arg_forms: &[Exp], env: &mut Env) -> Result { - let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_form = arg_forms.first().ok_or(Err::Reason("Expected first form".to_string()))?; let first_eval = eval(first_form, env)?; - let second_form = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?; + let second_form = arg_forms.get(1).ok_or(Err::Reason("Expected second form".to_string()))?; let second_eval = eval(second_form, env)?; match first_eval { Exp::Symbol(a) => { @@ -255,54 +255,54 @@ fn eval_eq_args(arg_forms: &[Exp], env: &mut Env) -> Result { } fn eval_car_args(arg_forms: &[Exp], env: &mut Env) -> Result { - let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_form = arg_forms.first().ok_or(Err::Reason("Expected first form".to_string()))?; let first_eval = eval(first_form, env)?; match first_eval { Exp::List(list) => { - let exp = list.first().ok_or(Err::Reason("list cannot be empty".to_string()))?; // TODO: return nil? + let exp = list.first().ok_or(Err::Reason("List cannot be empty".to_string()))?; // TODO: return nil? Ok(exp.clone()) }, - _ => Err(Err::Reason("expected list form".to_string())), + _ => Err(Err::Reason("Expected list form".to_string())), } } fn eval_cdr_args(arg_forms: &[Exp], env: &mut Env) -> Result { - let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_form = arg_forms.first().ok_or(Err::Reason("Expected first form".to_string()))?; let first_eval = eval(first_form, env)?; match first_eval { Exp::List(list) => { if list.is_empty() { - return Err(Err::Reason("list cannot be empty".to_string())) // TODO: return nil? + return Err(Err::Reason("List cannot be empty".to_string())) // TODO: return nil? } Ok(Exp::List(list[1..].to_vec())) }, - _ => Err(Err::Reason("expected list form".to_string())), + _ => Err(Err::Reason("Expected list form".to_string())), } } fn eval_cons_args(arg_forms: &[Exp], env: &mut Env) -> Result { - let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_form = arg_forms.first().ok_or(Err::Reason("Expected first form".to_string()))?; let first_eval = eval(first_form, env)?; - let second_form = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?; + let second_form = arg_forms.get(1).ok_or(Err::Reason("Expected second form".to_string()))?; let second_eval = eval(second_form, env)?; match second_eval { Exp::List(mut list) => { list.insert(0, first_eval); Ok(Exp::List(list.to_vec())) }, - _ => Err(Err::Reason("expected list form".to_string())), + _ => Err(Err::Reason("Expected list form".to_string())), } } fn eval_cond_args(arg_forms: &[Exp], env: &mut Env) -> Result { if arg_forms.is_empty() { - return Err(Err::Reason("expected at least one form".to_string())) + return Err(Err::Reason("Expected at least one form".to_string())) } for arg_form in arg_forms { match arg_form { Exp::List(list) => { if list.len() != 2 { - return Err(Err::Reason("expected lists of predicate and expression".to_string())) + return Err(Err::Reason("Expected lists of predicate and expression".to_string())) } let pred = eval(&list[0], env)?; let exp = eval(&list[1], env)?; @@ -315,21 +315,21 @@ fn eval_cond_args(arg_forms: &[Exp], env: &mut Env) -> Result { _ => continue, } }, - _ => return Err(Err::Reason("expected lists of predicate and expression".to_string())), + _ => return Err(Err::Reason("Expected lists of predicate and expression".to_string())), } } Ok(Exp::List(Vec::new())) } fn eval_label_args(arg_forms: &[Exp], env: &mut Env) -> Result { - let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_form = arg_forms.first().ok_or(Err::Reason("Expected first form".to_string()))?; let first_str = match first_form { Exp::Symbol(s) => Ok(s.clone()), - _ => Err(Err::Reason("expected first form to be a symbol".to_string())) + _ => Err(Err::Reason("Expected first form to be a symbol".to_string())) }?; - let second_form = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?; + let second_form = arg_forms.get(1).ok_or(Err::Reason("Expected second form".to_string()))?; if arg_forms.len() > 2 { - return Err(Err::Reason("label can only have two forms".to_string())) + return Err(Err::Reason("Label can only have two forms".to_string())) } let second_eval = eval(second_form, env)?; env.data.insert(first_str, second_eval); @@ -337,10 +337,10 @@ fn eval_label_args(arg_forms: &[Exp], env: &mut Env) -> Result { } fn eval_lambda_args(arg_forms: &[Exp]) -> Result { - let params_exp = arg_forms.first().ok_or(Err::Reason("expected args form".to_string()))?; - let body_exp = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?; + let params_exp = arg_forms.first().ok_or(Err::Reason("Expected args form".to_string()))?; + let body_exp = arg_forms.get(1).ok_or(Err::Reason("Expected second form".to_string()))?; if arg_forms.len() > 2 { - return Err(Err::Reason("lambda definition can only have two forms".to_string())) + return Err(Err::Reason("Lambda definition can only have two forms".to_string())) } Ok(Exp::Lambda(Lambda { body_exp: Rc::new(body_exp.clone()), @@ -349,18 +349,18 @@ fn eval_lambda_args(arg_forms: &[Exp]) -> Result { } fn eval_defun_args(arg_forms: &[Exp], env: &mut Env) -> Result { - let name = arg_forms.get(0).ok_or(Err::Reason("expected first form".to_string()))?.clone(); - let params = arg_forms.get(1).ok_or(Err::Reason("expected second form".to_string()))?.clone(); - let exp = arg_forms.get(2).ok_or(Err::Reason("expected third form".to_string()))?.clone(); + let name = arg_forms.get(0).ok_or(Err::Reason("Expected first form".to_string()))?.clone(); + let params = arg_forms.get(1).ok_or(Err::Reason("Expected second form".to_string()))?.clone(); + let exp = arg_forms.get(2).ok_or(Err::Reason("Expected third form".to_string()))?.clone(); let lambda_args = vec![Exp::Symbol("lambda".to_string()), params, exp]; let label_args = vec![name, Exp::List(lambda_args)]; eval_label_args(&label_args, env) } fn eval_print_args(arg_forms: &[Exp], env: &mut Env) -> Result { - let first_form = arg_forms.first().ok_or(Err::Reason("expected first form".to_string()))?; + let first_form = arg_forms.first().ok_or(Err::Reason("Expected first form".to_string()))?; if arg_forms.len() > 1 { - return Err(Err::Reason("print can only have one form".to_string())) + return Err(Err::Reason("Print can only have one form".to_string())) } match eval(first_form, env) { Ok(res) => { @@ -414,12 +414,12 @@ fn env_get(k: &str, env: &Env) -> Option { fn parse_list_of_symbol_strings(form: Rc) -> Result, Err> { let list = match form.as_ref() { Exp::List(s) => Ok(s.clone()), - _ => Err(Err::Reason("expected args form to be a list".to_string())) + _ => Err(Err::Reason("Expected args form to be a list".to_string())) }?; list.iter().map(|x| { match x { Exp::Symbol(s) => Ok(s.clone()), - _ => Err(Err::Reason("expected symbols in the argument list".to_string())) + _ => Err(Err::Reason("Expected symbols in the argument list".to_string())) } }).collect() } @@ -427,7 +427,7 @@ fn parse_list_of_symbol_strings(form: Rc) -> Result, Err> { fn env_for_lambda<'a>(params: Rc, arg_forms: &[Exp], outer_env: &'a mut Env) -> Result, Err> { let ks = parse_list_of_symbol_strings(params)?; if ks.len() != arg_forms.len() { - return Err(Err::Reason(format!("expected {} arguments, got {}", ks.len(), arg_forms.len()))); + return Err(Err::Reason(format!("Expected {} arguments, got {}", ks.len(), arg_forms.len()))); } let vs = eval_forms(arg_forms, outer_env)?; let mut data: BTreeMap = BTreeMap::new(); @@ -446,11 +446,11 @@ fn eval_forms(arg_forms: &[Exp], env: &mut Env) -> Result, Err> { fn eval(exp: &Exp, env: &mut Env) -> Result { match exp { - Exp::Symbol(k) => env_get(k, env).ok_or(Err::Reason(format!("unexpected symbol k='{}'", k))), + Exp::Symbol(k) => env_get(k, env).ok_or(Err::Reason(format!("Unexpected symbol k='{}'", k))), Exp::Bool(_a) => Ok(exp.clone()), Exp::Number(_a) => Ok(exp.clone()), Exp::List(list) => { - let first_form = list.first().ok_or(Err::Reason("expected a non-empty list".to_string()))?; + let first_form = list.first().ok_or(Err::Reason("Expected a non-empty list".to_string()))?; let arg_forms = &list[1..]; match eval_built_in_form(first_form, arg_forms, env) { Some(res) => res, @@ -464,13 +464,13 @@ fn eval(exp: &Exp, env: &mut Env) -> Result { let new_env = &mut env_for_lambda(lambda.params_exp, arg_forms, env)?; eval(&lambda.body_exp, new_env) }, - _ => Err(Err::Reason("first form must be a function".to_string())), + _ => Err(Err::Reason("First form must be a function".to_string())), } } } }, - Exp::Func(_) => Err(Err::Reason("unexpected form".to_string())), - Exp::Lambda(_) => Err(Err::Reason("unexpected form".to_string())), + Exp::Func(_) => Err(Err::Reason("Unexpected form".to_string())), + Exp::Lambda(_) => Err(Err::Reason("Unexpected form".to_string())), } } @@ -510,7 +510,7 @@ fn repl(env: &mut Env) -> usr::shell::ExitCode { println!("MOROS Lisp v0.1.0\n"); let csi_color = Style::color("Cyan"); - let csi_error = Style::color("Red"); + let csi_error = Style::color("LightRed"); let csi_reset = Style::reset(); let prompt_string = format!("{}>{} ", csi_color, csi_reset); @@ -528,7 +528,7 @@ fn repl(env: &mut Env) -> usr::shell::ExitCode { println!("{}\n", res); } Err(e) => match e { - Err::Reason(msg) => println!("{}Error: {}{}\n", csi_error, msg, csi_reset), + Err::Reason(msg) => println!("{}Error:{} {}\n", csi_error, csi_reset, msg), }, } if !exp.is_empty() { diff --git a/src/usr/mod.rs b/src/usr/mod.rs index 92c298555..ce087f5f8 100644 --- a/src/usr/mod.rs +++ b/src/usr/mod.rs @@ -1,5 +1,6 @@ pub mod base64; pub mod beep; +pub mod calc; pub mod clear; pub mod colors; pub mod copy; diff --git a/src/usr/shell.rs b/src/usr/shell.rs index 1a723a84f..04a7e72e0 100644 --- a/src/usr/shell.rs +++ b/src/usr/shell.rs @@ -7,11 +7,11 @@ use alloc::vec::Vec; use alloc::string::String; // TODO: Scan /bin -const AUTOCOMPLETE_COMMANDS: [&str; 36] = [ - "base64", "clear", "colors", "copy", "date", "delete", "dhcp", "disk", "edit", "env", "exit", - "geotime", "goto", "halt", "help", "hex", "host", "http", "httpd", "install", "ip", "keyboard", - "lisp", "list", "memory", "move", "net", "print", "read", "route", "shell", "sleep", "tcp", - "user", "vga", "write" +const AUTOCOMPLETE_COMMANDS: [&str; 37] = [ + "base64", "calc", "clear", "colors", "copy", "date", "delete", "dhcp", "disk", "edit", "env", + "exit", "geotime", "goto", "halt", "help", "hex", "host", "http", "httpd", "install", "ip", + "keyboard", "lisp", "list", "memory", "move", "net", "print", "read", "route", "shell", + "sleep", "tcp", "user", "vga", "write" ]; #[repr(u8)] @@ -158,6 +158,7 @@ pub fn exec(cmd: &str) -> ExitCode { "sh" | "shell" => usr::shell::main(&args), "sleep" => usr::sleep::main(&args), "clear" => usr::clear::main(&args), + "calc" => usr::calc::main(&args), "base64" => usr::base64::main(&args), "date" => usr::date::main(&args), "env" => usr::env::main(&args),