diff --git a/src/env.rs b/src/env.rs new file mode 100644 index 0000000..d1313ed --- /dev/null +++ b/src/env.rs @@ -0,0 +1,13 @@ +use std::collections::HashMap; + +pub fn os_env_hashmap() -> HashMap { + let mut map = HashMap::new(); + use std::env; + for (key, val) in env::vars_os() { + // Use pattern bindings instead of testing .is_some() followed by .unwrap() + if let (Ok(k), Ok(v)) = (key.into_string(), val.into_string()) { + map.insert(k, v); + } + } + map +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4a9765a..1929e14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ mod parser; +mod env; +mod nativeFunctions; use std::io::{self, BufRead, Stdout, Write}; use std::cmp; @@ -13,6 +15,8 @@ use termion::event::*; use std::fs::File; use std::io::BufReader; use anyhow::Result; +use crate::nativeFunctions::get_native_functions; +use crate::parser::vars::Variable; struct Term { input: String, @@ -21,10 +25,10 @@ struct Term { impl Term { fn new() -> Term { - return Term { + Term { input: String::new(), idx: 0, - }; + } } fn print(self: &Self, stdout: &mut RawTerminal) { @@ -61,10 +65,10 @@ struct Shell { impl Shell { fn new() -> Shell { - return Shell { + Shell { term: Term::new(), ctx: parser::vars::Context::new() - }; + } } fn collect(&mut self) { @@ -88,7 +92,7 @@ impl Shell { } } Key::Backspace => { - if self.term.input.len() > 0 && self.term.idx > 0 { + if !self.term.input.is_empty() && self.term.idx > 0 { if self.term.idx == self.term.input.len() - 1 { self.term.input.pop(); } else { @@ -138,6 +142,7 @@ impl Shell { fn start() { let mut shell = Shell::new(); + shell.ctx.native_func = get_native_functions(); loop { print!("$: "); io::stdout().flush().unwrap(); @@ -146,11 +151,9 @@ impl Shell { break; } shell.term.input += "\n"; + shell.ctx.exports = env::os_env_hashmap().into_iter().map(|(k, v)| (k, Variable::String(v))).collect(); let res = parser::exec(&mut shell.term.input.as_bytes(), &mut shell.ctx); - match res { - Err(err) => eprintln!("rush: {}", err), - Ok(_) => {} - } + if let Err(err) = res { eprintln!("rush: {}", err) } } } } @@ -202,15 +205,8 @@ mod test { use std::fs::File; use std::io::BufReader; use std::path::Path; - use crate::parser; + use crate::{load_and_run, parser}; use anyhow::Result; - - fn load_and_run>(path: P) -> Result<()> { - let mut ctx = parser::vars::Context::new(); - let src = File::open(path).unwrap(); - parser::exec(&mut BufReader::new(src), &mut ctx) - } - #[test] fn simple() -> Result<()> { load_and_run("test/simple.rush") diff --git a/src/nativeFunctions.rs b/src/nativeFunctions.rs new file mode 100644 index 0000000..e00829f --- /dev/null +++ b/src/nativeFunctions.rs @@ -0,0 +1,60 @@ +use std::collections::HashMap; +use crate::parser::vars::{Context, NativeFunction, Variable, variables_to_string}; +use anyhow::{Result, bail}; + +pub fn get_native_functions() -> HashMap { + let mut map = HashMap::new(); + + fn rush_trim(_ctx: &mut Context, args: Vec) -> Result { + let text = variables_to_string(args); + let trimmed = text.trim(); + Ok(Variable::String(trimmed.to_string())) + } + map.insert("$trim".to_string(), NativeFunction { + name: "$trim".to_string(), + description: "Removes leading and trailing whitespaces from a string".to_string(), + args: vec![String::from("str")], + func: rush_trim + }); + + fn rush_test(_ctx: &mut Context, args: Vec) -> Result { + if args.len() != 3 { + bail!("Expected 3 arguments (source, operand, target), got {}", args.len()); + } + let source = args.get(0).unwrap(); + let operand = args.get(1).unwrap(); + let target = args.get(2).unwrap(); + + let res = match operand { + Variable::String(operand) => { + match operand.as_str() { + "=" => { + if source.to_string() == target.to_string() { + 0 + } else { + 1 + } + } + "!=" => { + if source.to_string() != target.to_string() { + 0 + } else { + 1 + } + } + _ => bail!("Unsupported operand: {}", operand) + } + } + _ => bail!("Unsupported operand: {}", operand) + }; + Ok(Variable::I32(res)) + } + map.insert("test".to_string(), NativeFunction { + name: "test".to_string(), + description: "Compares values. Supported operands are = != > < >= <=".to_string(), + args: vec![String::from("source"), String::from("operand"), String::from("target")], + func: rush_test + }); + + map +} \ No newline at end of file diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 969184c..32e56e2 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -47,10 +47,9 @@ pub enum ForValue { } #[derive(Debug)] -pub struct DefinedFunction { - name: String, - args: Vec, - body: Vec +pub struct DefinedFunctionCall { + pub name: String, + pub args: Vec } #[derive(Debug)] @@ -59,8 +58,7 @@ pub enum Value { Variable(String), ArrayVariable(String), ArrayDefinition(Vec), - ArrayFunction(DefinedFunction), - StringFunction(DefinedFunction), + ValueFunction(DefinedFunctionCall), Expressions(Vec), Values(Vec) } @@ -74,6 +72,8 @@ pub struct FunctionVariable { #[derive(Debug)] pub struct FunctionDefinitionExpression { pub name: String, + pub description: Option, + pub on_event: Option, pub args: Vec, pub body: Box } @@ -139,7 +139,11 @@ impl Tree { loop { if matches!(token, Tokens::Space) { if !buf.is_empty() { - values.push(CommandValue::Value(Value::Values(buf))); + if buf.len() == 1 { + values.push(CommandValue::Value(buf.pop().unwrap())); + } else { + values.push(CommandValue::Value(Value::Values(buf))); + } buf = Vec::new(); } if self.i >= end - 1 { break } @@ -171,6 +175,11 @@ impl Tree { } Value::Literal(token.to_str()) } + Tokens::StringFunction(_) | Tokens::ArrayFunction(_) => { + let val = self.get_value(end, false)?; + token = self.get_current_token(); + val + } _ => { Value::Literal(token.to_str()) } @@ -187,7 +196,11 @@ impl Tree { } // self.next(); if !buf.is_empty() { - values.push(CommandValue::Value(Value::Values(buf))); + if buf.len() == 1 { + values.push(CommandValue::Value(buf.pop().unwrap())); + } else { + values.push(CommandValue::Value(Value::Values(buf))); + } } Ok(Expression::Command(values)) } @@ -255,12 +268,26 @@ impl Tree { bail!("Functions not yet implemented") } - fn parse_array_func(&mut self, _str: String, _end: usize) -> Result { - bail!("Array functions not yet implemented"); - } + fn parse_string_or_array_func_call(&mut self, end: usize) -> Result { + let token = self.get_current_token(); + let name = match token { + Tokens::ArrayFunction(str) => { + String::from("@") + str + } + Tokens::StringFunction(str) => { + String::from("$") + str + } + _ => bail!("Expected string or array function - internal error") + }.clone(); + let mut args = Vec::new(); + self.inc(); + loop { + args.push(self.get_value(end, false)?); + self.inc(); + if self.i >= end { break } + } - fn parse_string_func(&mut self, _str: String, _end: usize) -> Result { - bail!("Array functions not yet implemented"); + Ok(DefinedFunctionCall { name, args }) } fn parse_for(&mut self, _end: usize) -> Result { @@ -360,6 +387,27 @@ impl Tree { Ok(values) } + fn get_parens_vals(&self, end: usize) -> (usize, usize) { + let mut len = 0; + let mut lvl = 1; + for token in &self.tokens[self.i..end] { + match token.token { + Tokens::SubStart => lvl += 1, + Tokens::StringFunction(_) => lvl += 1, + Tokens::ArrayFunction(_) => lvl += 1, + Tokens::ParenthesisStart => lvl += 1, + Tokens::ParenthesisEnd => lvl -= 1, + _ => {} + } + if lvl == 0 { + break; + } + + len += 1; + } + (len, lvl) + } + fn get_value(&mut self, end: usize, stop_on_space: bool) -> Result { let mut token = self.get_current_token(); let mut values: Vec = Vec::new(); @@ -381,8 +429,16 @@ impl Tree { Tokens::FileWrite => buf.push(Value::Literal(token.to_str())), Tokens::RedirectInto => bail!("Unexpected token REDIRECT (|)"), Tokens::ParenthesisEnd => bail!("Unexpected token FUNCTION CALL END ())"), - Tokens::ArrayFunction(_) => bail!("Unexpected array function"), - Tokens::StringFunction(_) => bail!("Unexpected string function"), + Tokens::StringFunction(_) | Tokens::ArrayFunction(_) => { + self.inc(); + let (len, lvl) = self.get_parens_vals(end); + self.i -= 1; + if lvl != 0 { + bail!("Parenthesis do not match"); + } + let val = self.parse_string_or_array_func_call(self.i + len)?; + return Ok(Value::ValueFunction(val)); + }, Tokens::ParenthesisStart => bail!("Parenthesis not yet implemented"), Tokens::ArrayStart => { let mut len = 0; @@ -410,26 +466,8 @@ impl Tree { }, Tokens::ArrayEnd => bail!("Unexpected token ARRAY END (])"), Tokens::SubStart => { - let mut len = 0; - let mut lvl = 1; + let (len, lvl) = self.get_parens_vals(end); self.inc(); - for token in &self.tokens[self.i..] { - match token.token { - Tokens::SubStart => lvl += 1, - Tokens::StringFunction(_) => lvl += 1, - Tokens::ArrayFunction(_) => lvl += 1, - Tokens::ParenthesisStart => lvl += 1, - Tokens::ParenthesisEnd => lvl -= 1, - _ => {} - } - if lvl == 0 { - break; - } - - if len + self.i == end { break } - len += 1; - } - // self.inc(); if lvl != 0 { bail!("Parenthesis do not match"); } @@ -466,7 +504,11 @@ impl Tree { token = self.inc().get_current_token(); } if !buf.is_empty() { - values.push(Value::Values(buf)); + if buf.len() == 1 { + values.push(buf.into_iter().next().unwrap()); + } else { + values.push(Value::Values(buf)); + } } if values.len() == 1 { return Ok(values.into_iter().next().unwrap()); @@ -500,22 +542,8 @@ impl Tree { Tokens::ParenthesisStart => if matches!(expr, Some(_)) { bail!("Unexpected parenthesis. After file redirect, you need to use a semicolon or newline."); } else { - let mut len = 1; - let mut lvl = 1; self.inc(); - for token in &self.tokens[self.i..] { - match token.token { - Tokens::ParenthesisStart => lvl += 1, - Tokens::ParenthesisEnd => lvl -= 1, - _ => {} - } - if lvl == 0 { - break; - } - - if len + self.i == end { break } - len += 1; - } + let (len, lvl) = self.get_parens_vals(end); if lvl != 0 { bail!("Parenthesis not ended properly."); } @@ -590,7 +618,7 @@ impl Tree { } pub fn build_tree(tokens: Vec) -> Result> { - dbg!(&tokens); + // dbg!(&tokens); let mut expressions: Vec = Vec::new(); let mut tree = Tree { tokens, i: 0 }; loop { @@ -604,5 +632,6 @@ pub fn build_tree(tokens: Vec) -> Result> { } } } + // dbg!(&expressions); Ok(expressions) } diff --git a/src/parser/exec.rs b/src/parser/exec.rs index 97ab9c6..5cfc5af 100644 --- a/src/parser/exec.rs +++ b/src/parser/exec.rs @@ -3,7 +3,7 @@ use std::process::{Child, Command, Stdio}; use std::io; use crate::parser::ast::{AndExpression, BreakExpression, CommandValue, Expression, FileSourceExpression, FileTargetExpression, IfExpression, LetExpression, OrExpression, RedirectTargetExpression, Value, WhileExpression}; use crate::parser::vars; -use crate::parser::vars::{Context, Variable}; +use crate::parser::vars::{AnyFunction, Context, Variable}; use anyhow::{Result, bail, Context as AnyhowContext}; trait ExecExpression { @@ -112,10 +112,8 @@ impl GetValue for Value { Value::Literal(str) => { Ok(Variable::String(str.clone())) }, - Value::Variable(str) => Ok(ctx.get_var(&str).unwrap_or(&mut Variable::String(String::from(""))).clone()), - Value::ArrayVariable(str) => Ok(ctx.get_var(&str).unwrap_or(&mut Variable::Array(Vec::new())).clone()), - Value::ArrayFunction(_) => todo!("Not implemented yet"), - Value::StringFunction(_) => todo!("Not implemented yet"), + Value::Variable(str) => Ok(ctx.get_var(str).unwrap_or(&mut Variable::String(String::from(""))).clone()), + Value::ArrayVariable(str) => Ok(ctx.get_var(str).unwrap_or(&mut Variable::Array(Vec::new())).clone()), Value::Expressions(expressions) => { let mut out = String::new(); ctx.add_scope(); @@ -138,10 +136,28 @@ impl GetValue for Value { } Ok(Variable::Array(out)) } + Value::ValueFunction(call) => { + let args = get_variables(ctx, &mut call.args)?; + let func = ctx.get_func(call.name.as_str()).with_context(|| format!("Function {} not found", call.name))?; + match func { + AnyFunction::Native(func) => { + (func.func)(ctx, args) + } + AnyFunction::UserDefined(_) => todo!("User defined functions are not yet supported") + } + } } } } +fn get_variables(ctx: &mut vars::Context, args: &mut Vec) -> Result> { + let mut out = Vec::new(); + for arg in args { + out.push(arg.get(ctx)?); + } + Ok(out) +} + impl ExecExpression for Expression { fn exec(self: &mut Expression, ctx: &mut vars::Context) -> Result> { match self { @@ -167,7 +183,7 @@ impl ExecExpression for BreakExpression { fn exec(self: &mut BreakExpression, ctx: &mut vars::Context) -> Result> { if ctx.break_num > 0 { ctx.break_num -= 1; return Ok(None) } let val = self.num.get(ctx)?.to_string(); - let num: u16 = if val.len() > 0 { val.parse()? } else { 1 }; + let num: u16 = if !val.is_empty() { val.parse()? } else { 1 }; ctx.break_num = if num == 0 { 1 } else { num }; Ok(None) } @@ -247,7 +263,7 @@ impl ExecExpression for LetExpression { impl ExecExpression for Vec { fn exec(self: &mut Vec, ctx: &mut vars::Context) -> Result> { if ctx.break_num > 0 { return Ok(None) } - if self.len() == 0 { bail!("Command with 0 length"); } + if self.is_empty() { bail!("Command with 0 length"); } let mut first = self.remove(0); let command_name = first.get(ctx)?.to_string(); let mut cmd = Command::new(command_name); @@ -286,8 +302,7 @@ impl ExecExpression for FileTargetExpression { todo!("Redirect without target file"); } }; - let command; - match src { + let command = match src { Some(mut cmd) => { cmd.stdout(Stdio::piped()); let file = File::create(target.to_string()); @@ -304,7 +319,7 @@ impl ExecExpression for FileTargetExpression { } } } - command = cmd; + cmd }, None => { bail!("Invalid command provided for file target"); } }; diff --git a/src/parser/tokens.rs b/src/parser/tokens.rs index a775cc4..794dac6 100644 --- a/src/parser/tokens.rs +++ b/src/parser/tokens.rs @@ -166,7 +166,8 @@ pub fn tokenize(reader: &mut dyn std::io::BufRead) -> Result> { skipper = 1; buf_add = false; } else { - let (skippers, mut token) = read_var_ahead(i, &text)?; + let (mut skippers, mut token) = read_var_ahead(i, &text)?; + skippers += 1; match token.token { Tokens::StringVariable(ref str, bool) => if !bool && !double_quote_active && text.len() > i + skippers && text.chars().nth(i + skippers).unwrap() == '(' { token = Token { token: Tokens::StringFunction(str.clone()), end: i + skippers, start: i }; diff --git a/src/parser/vars.rs b/src/parser/vars.rs index c845775..a0b4326 100644 --- a/src/parser/vars.rs +++ b/src/parser/vars.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::fmt::{Display, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use anyhow::{bail, Result}; use crate::parser::ast::FunctionDefinitionExpression; @@ -56,6 +56,17 @@ impl Display for Variable { } } +pub fn variables_to_string(vars: Vec) -> String { + let mut str = String::new(); + for (i, var) in vars.iter().enumerate() { + str += &*var.clone().to_string(); + if i < vars.len() - 1 { + str += " "; + } + } + str +} + impl Variable { pub fn index(&self, index: &Variable) -> Result<&Variable> { match self { @@ -134,6 +145,24 @@ impl Variable { } } +pub struct NativeFunction { + pub name: String, + pub description: String, + pub args: Vec, + pub func: fn(&mut Context, Vec) -> Result +} + +impl Debug for NativeFunction { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "NativeFunction {{ name: {}, description: {} }}", self.name, self.description) + } +} + +pub enum AnyFunction<'a> { + Native(&'a mut NativeFunction), + UserDefined(&'a mut FunctionDefinitionExpression) +} + #[derive(Debug)] pub struct Scope { /// list of variables @@ -147,8 +176,13 @@ pub struct Scope { #[derive(Debug)] pub struct Context { pub scopes: Vec, - pub exports: HashMap, + /// env variables + pub exports: HashMap, + /// list of native functions (Rust functions) + pub native_func: HashMap, + /// number of break statements called pub break_num: u16, + /// number of continue statements called pub continue_num: u16 } @@ -157,16 +191,17 @@ impl Context { let mut res = Context { scopes: Vec::new(), exports: HashMap::new(), + native_func: HashMap::new(), break_num: 0, continue_num: 0 }; res.add_scope(); res } - pub fn pop_scope(self: &mut Self) -> Option { + pub fn pop_scope(&mut self) -> Option { self.scopes.pop() } - pub fn add_scope(self: &mut Self) { + pub fn add_scope(&mut self) { let scope = Scope { func: HashMap::new(), vars: HashMap::new(), @@ -175,7 +210,16 @@ impl Context { self.scopes.push(scope); } - pub fn get_var(self: &mut Self, var: &str) -> Option<&mut Variable> { + pub fn get_var(&mut self, var: &str) -> Option<&mut Variable> { + if var.starts_with("env::") { + let key = var.replace("env::", ""); + return match self.exports.get_mut(&key) { + Some(val) => { + return Some(val); + }, + None => None + } + } for scope in self.scopes.iter_mut().rev() { let vars = &mut scope.vars; let val = vars.get_mut(var); @@ -191,25 +235,36 @@ impl Context { pub fn set_var(&mut self, key: String, val: Variable) { let vars = &mut self.scopes.last_mut().unwrap().vars; + if key.starts_with("env::") { + let key = key.replace("env::", ""); + self.exports.insert(key, Variable::String(val.to_string())); + } vars.insert(key, val); } - pub fn get_func(self: &mut Self, key: &str) -> Option<&mut FunctionDefinitionExpression> { + pub fn get_func(&mut self, key: &str) -> Option { for scope in self.scopes.iter_mut().rev() { let funcs = &mut scope.func; let val = funcs.get_mut(key); match val { None => {} Some(val) => { - return Some(val); + return Some(AnyFunction::UserDefined(val)); } } } + let val = self.native_func.get_mut(key); + match val { + None => {} + Some(val) => { + return Some(AnyFunction::Native(val)); + } + } None } pub fn set_func(&mut self, key: String, val: FunctionDefinitionExpression) { - let mut func = &mut self.scopes.last_mut().unwrap().func; + let func = &mut self.scopes.last_mut().unwrap().func; func.insert(key, val); } } diff --git a/test/builtins.rush b/test/builtins.rush new file mode 100644 index 0000000..6ec704e --- /dev/null +++ b/test/builtins.rush @@ -0,0 +1 @@ +test $trim("test ") = "test" \ No newline at end of file