diff --git a/README.md b/README.md index b0a7837..f4d589e 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ Slices: `[x..y]` gets a substring (or subarray) of the variable. When `x` ommite Bracketless. Scopes are ended by the keyword `end`. - `if` - Runs it's scope if the command returns `0`. Useful in pair with `test` builtin. `else` supported. `else if` doesn't require another `end`. -- `for $val of @arr` - Runs for each value of the array (or hashmap) -- `for $val of X..Y` - Runs for each number in the range `X` and `Y` (both inclusive). +- `for val of @arr` - Runs for each value of the array (or hashmap) +- `for val of X..Y` - Runs for each number in the range `X` and `Y` (both inclusive). - `while` - Runs in loop as long as the command returns `0` ### Functions @@ -113,9 +113,11 @@ Use `source` to load external files with functions to be triggered. * `let` for assigning variables (`let var = value`) * `export` for exporting variables to env (`export var` to export var, or `export var = value`) * `test` tests for evaluation (`=` for equality, `>`, `<`, `<=`, `>=` for number comparisons) -* `exists` for existance of a given string, or if given a flag (`-F`unctions, `-v`ariables, `-e`nv, `-f`ile, `-d`irectory, `-r`eadable file, `-w`ritable file, e`-x`ecutable file), existence of the selected object +* `exists` for existence of a given string, or if given a flag (`-F`unctions, `-v`ariables, `-e`nv, `-f`ile, `-d`irectory, `-r`eadable file, `-w`ritable file, e`-x`ecutable file), existence of the selected object * `true` returns `0` * `false` returns `1` * `source` to run another file in the same file scope +* `typeof` returns the type of arguments passed +* `inspect` shows the object in a debug output Some GNU standard utils may be overwritten by rush builtins, but must be made compatible. diff --git a/src/nativeFunctions.rs b/src/nativeFunctions.rs index e00829f..20c8908 100644 --- a/src/nativeFunctions.rs +++ b/src/nativeFunctions.rs @@ -56,5 +56,80 @@ pub fn get_native_functions() -> HashMap { func: rush_test }); + fn rush_true(_ctx: &mut Context, _args: Vec) -> Result { + Ok(Variable::I32(0)) + } + map.insert("true".to_string(), NativeFunction { + name: "true".to_string(), + description: "Returns 0".to_string(), + args: vec![], + func: rush_true + }); + + fn rush_false(_ctx: &mut Context, _args: Vec) -> Result { + Ok(Variable::I32(1)) + } + map.insert("false".to_string(), NativeFunction { + name: "false".to_string(), + description: "Returns 1".to_string(), + args: vec![], + func: rush_false + }); + + fn rush_export(ctx: &mut Context, args: Vec) -> Result { + if args.len() != 1 && args.len() != 3 { + bail!("Expected 1 (name) or 3 (name = value) arguments, got {}", args.len()); + } + let name = args.get(0).unwrap(); + if args.len() == 1 { + let value = ctx.get_var(&name.to_string()); + match value { + Some(value) => { + let val = value.clone(); + ctx.exports.insert(name.to_string(), val); + } + None => return Ok(Variable::I32(1)) + } + } else { + let value = args.get(2).unwrap(); + ctx.set_var(name.to_string(), value.clone()); + } + Ok(Variable::I32(0)) + } + map.insert("export".to_string(), NativeFunction { + name: "export".to_string(), + description: "Exports a variable to the environment".to_string(), + args: vec![String::from("name"), String::from("="), String::from("value")], + func: rush_export + }); + + fn rush_typeof(_ctx: &mut Context, args: Vec) -> Result { + if args.len() != 1 { + bail!("Expected 1 argument, got {}", args.len()); + } + let arg = args.get(0).unwrap(); + let res = match arg { + Variable::String(_) => "string", + Variable::I32(_) => "i32", + Variable::I64(_) => "i64", + Variable::I128(_) => "i128", + Variable::U32(_) => "u32", + Variable::U64(_) => "u64", + Variable::U128(_) => "u128", + Variable::F32(_) => "f32", + Variable::F64(_) => "f64", + Variable::Bool(_) => "bool", + Variable::Array(_) => "array", + Variable::HMap(_) => "HMap" + }; + Ok(Variable::String(res.to_string())) + } + map.insert("typeof".to_string(), NativeFunction { + name: "typeof".to_string(), + description: "Returns the type of a variable".to_string(), + args: vec![String::from("var")], + func: rush_typeof + }); + map } \ No newline at end of file diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 32e56e2..36ea5f5 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -1,58 +1,60 @@ use crate::parser::tokens::{Token, Tokens}; use anyhow::{bail, Context, Result}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LetExpression { pub key: Box, pub vartype: Option, pub value: Box } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct AndExpression { pub first: Box, pub second: Box } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct OrExpression { pub first: Box, pub second: Box } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct IfExpression { pub condition: Box, pub contents: Vec, pub else_contents: Vec } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct WhileExpression { pub condition: Box, pub contents: Vec } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ForExpression { - pub key: String, - pub list: Box, - pub contents: Vec + pub arg_value: Value, + pub arg_key: Option, + pub list: Value, + pub contents: Vec, + pub else_contents: Vec } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ForValue { Value(Value), Range(Option, Option) } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DefinedFunctionCall { pub name: String, pub args: Vec } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Value { Literal(String), Variable(String), @@ -63,13 +65,13 @@ pub enum Value { Values(Vec) } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FunctionVariable { pub name: String, pub vartype: Option } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FunctionDefinitionExpression { pub name: String, pub description: Option, @@ -78,36 +80,36 @@ pub struct FunctionDefinitionExpression { pub body: Box } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RedirectTargetExpression { pub source: Box, pub target: Box } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FileTargetExpression { pub source: Option>, pub target: Box } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FileSourceExpression { pub source: Box, pub target: Option> } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum CommandValue { Value(Value), Var(String, Value) } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BreakExpression { pub num: Box } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Expression { LetExpression(LetExpression), Command(Vec), @@ -278,7 +280,7 @@ impl Tree { String::from("$") + str } _ => bail!("Expected string or array function - internal error") - }.clone(); + }; let mut args = Vec::new(); self.inc(); loop { @@ -290,30 +292,48 @@ impl Tree { Ok(DefinedFunctionCall { name, args }) } - fn parse_for(&mut self, _end: usize) -> Result { - bail!("For loop not yet implemented"); - } - - fn parse_if(&mut self, end: usize) -> Result { + fn parse_for(&mut self, end: usize) -> Result { self.inc(); - let condition = self.get_expression(end).with_context(|| "Error getting condition for if expression")?; + let arg_value = self.get_value(end, true)?; + let arg_key = match self.get_value(end, true)? { + Value::Literal(k) if k == "in" => None, + any => Some(any) + }; + if matches!(arg_key, Some(_)) { + match self.get_value(end, true)? { + Value::Literal(k) if k == "in" => {}, + _ => bail!("Expected 'in' after for key") + } + self.inc(); + } + let list = self.get_value(end, false)?; + let mut contents = Vec::new(); + loop { match self.get_current_token() { Tokens::End => break, Tokens::Space => {}, Tokens::Else => break, Tokens::CommandEnd(_) => {} - _ => contents.push(self.get_expression(end).with_context(|| "Error getting contents for if expression")?) - }; + _ => contents.push(self.get_expression(end).with_context(|| "Error getting contents for for expression")?) + } + if self.i >= end - 1 { break } self.inc(); } + let else_contents = self.parse_else(end)?; + + Ok(ForExpression { arg_key, arg_value, contents, else_contents, list }) + } + + fn parse_else(&mut self, end: usize) -> Result> { loop { match self.get_current_token() { Tokens::CommandEnd(_) => { self.inc(); }, Tokens::Space => { self.inc(); }, _ => break } + if self.i >= end - 1 { return Ok(Vec::new()) } } let mut else_contents = Vec::new(); if matches!(self.get_current_token(), Tokens::Else) { @@ -331,19 +351,39 @@ impl Tree { _ => else_contents.push(self.get_expression(end).with_context(|| "Error getting contents for if expression")?) }; self.inc(); + if self.i >= end { break } } } - if self.i < self.tokens.len() { + if self.i < end { loop { match self.get_current_token() { Tokens::CommandEnd(_) => { self.inc(); }, Tokens::Space => { self.inc(); }, _ => break } - if self.i >= self.tokens.len() - 1 { break } + if self.i >= end - 1 { break } } self.inc(); } + Ok(else_contents) + } + + fn parse_if(&mut self, end: usize) -> Result { + self.inc(); + let condition = self.get_expression(end).with_context(|| "Error getting condition for if expression")?; + let mut contents = Vec::new(); + loop { + match self.get_current_token() { + Tokens::End => break, + Tokens::Space => {}, + Tokens::Else => break, + Tokens::CommandEnd(_) => {} + _ => contents.push(self.get_expression(end).with_context(|| "Error getting contents for if expression")?) + }; + self.inc(); + if self.i >= end { break } + } + let else_contents = self.parse_else(end)?; Ok(IfExpression { condition: Box::new(condition), contents, else_contents }) } @@ -377,10 +417,10 @@ impl Tree { fn parse_array_definition(&mut self, end: usize) -> Result> { let mut values: Vec = Vec::new(); - if matches!(self.get_current_token(), Tokens::Space) { self.inc(); } loop { - if self.i >= end - 1 { break; } + if self.i >= end { break; } let val = self.get_value(end, true)?; + self.inc(); values.push(val); if matches!(self.get_current_token(), Tokens::Space) { self.inc(); } } @@ -415,8 +455,8 @@ impl Tree { loop { match token { Tokens::Space => { - if stop_on_space { break; } if buf.is_empty() { token = self.inc().get_current_token(); continue; } + if stop_on_space { break; } values.push(Value::Values(buf)); buf = Vec::new(); if self.i >= end - 1 { break } @@ -453,16 +493,15 @@ impl Tree { if lvl == 0 { break; } + len += 1; if len + self.i == end { break } - len += 1; } if lvl != 0 { bail!("Parenthesis do not match"); } let val = Value::ArrayDefinition(self.parse_array_definition(self.i + len)?); values.push(val); - self.inc(); }, Tokens::ArrayEnd => bail!("Unexpected token ARRAY END (])"), Tokens::SubStart => { @@ -599,7 +638,7 @@ impl Tree { Some(_) => bail!("Unexpected break") } Tokens::JobCommandEnd => bail!("Jobs not yet implemented") - } + }; if self.i >= end - 1 { break } token = self.get_current_token(); } @@ -614,7 +653,6 @@ impl Tree { self } fn get_current_token(&self) -> &Tokens { &self.tokens.get(self.i).unwrap().token } - fn get_next_token(&self) -> &Tokens { &self.tokens.get(self.i + 1).unwrap().token } } pub fn build_tree(tokens: Vec) -> Result> { diff --git a/src/parser/exec.rs b/src/parser/exec.rs index 5cfc5af..34062b3 100644 --- a/src/parser/exec.rs +++ b/src/parser/exec.rs @@ -1,7 +1,7 @@ use std::fs::File; 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::ast::{AndExpression, BreakExpression, CommandValue, Expression, FileSourceExpression, FileTargetExpression, ForExpression, IfExpression, LetExpression, OrExpression, RedirectTargetExpression, Value, WhileExpression}; use crate::parser::vars; use crate::parser::vars::{AnyFunction, Context, Variable}; use anyhow::{Result, bail, Context as AnyhowContext}; @@ -50,7 +50,7 @@ impl ExecResult { self.spawn(); let child = self.child.unwrap(); let mut stdout = child.stdout.unwrap(); - io::copy(&mut stdout, into); + io::copy(&mut stdout, into).unwrap(); } } into @@ -59,7 +59,7 @@ impl ExecResult { /// /// Uses `redirect_from_result` of the next result. Spawns this result, but not the next one. fn redirect_into_result(&mut self, into: &mut ExecResult) -> &mut Self { - into.redirect_from_result(self); + into.redirect_from_result(self).unwrap(); self } /// Redirects the `from` into the current pending result @@ -167,7 +167,7 @@ impl ExecExpression for Expression { Expression::Function(_) => todo!("Function definition"), Expression::IfExpression(expr) => expr.exec(ctx), Expression::WhileExpression(expr) => expr.exec(ctx), - Expression::ForExpression(_) => todo!("For expression"), + Expression::ForExpression(expr) => expr.exec(ctx), Expression::RedirectTargetExpression(expr) => expr.exec(ctx), Expression::FileTargetExpression(expr) => expr.exec(ctx), Expression::FileSourceExpression(expr) => expr.exec(ctx), @@ -197,18 +197,24 @@ impl ExecExpression for WhileExpression { Some(cmd) => cmd }; ctx.add_scope(); - let mut res; + let mut res = None; loop { match condition.spawn() { - Result::Err(_) => { + Err(_) => { res = Some(condition); break; }, - Result::Ok(mut child) => { + Ok(mut child) => { if !child.wait()?.success() { res = Some(condition); break } else { + match res { + None => {}, + Some(mut cmd) => { + wait_child(cmd.spawn()?, ctx)?; + } + } res = self.contents.exec(ctx)? } } @@ -224,6 +230,80 @@ impl ExecExpression for WhileExpression { } } +impl ExecExpression for ForExpression { + fn exec<'a>(&mut self, ctx: &mut Context) -> Result> { + if ctx.break_num > 0 { ctx.break_num -= 1; return Ok(None) } + let arg_value = self.arg_value.get(ctx)?; + let arg_key = match &self.arg_key { + None => None, + Some(key) => { + let mut lkey: Value = key.clone(); + let res = lkey.get(ctx)?; + Some(res) + } + }; + let mut res: Option = None; + let list = self.list.get(ctx)?; + + fn process(i: usize, val: Variable, ctx: &mut Context, arg_key: &Option, arg_value: &Variable) -> Result<()> { + ctx.add_scope(); + if let Some(key) = &arg_key { + ctx.set_var(key.to_string(), Variable::U64(i as u64)); + } + ctx.set_var(arg_value.to_string(), val); + Ok(()) + } + + match list { + Variable::Array(arr) => { + if arr.is_empty() { + self.else_contents.exec(ctx)?; + } else { + for (i, val) in arr.iter().enumerate() { + process(i, val.clone(), ctx, &arg_key, &arg_value)?; + match res { + None => {}, + Some(mut cmd) => { + wait_child(cmd.spawn()?, ctx)?; + } + } + res = self.contents.exec(ctx)?; + ctx.pop_scope(); + if ctx.break_num > 0 { + ctx.break_num -= 1; + break; + } + } + } + }, + Variable::String(str) => { + if str.is_empty() { + self.else_contents.exec(ctx)?; + } else { + for (i, char) in str.chars().enumerate() { + process(i, Variable::String(char.to_string()), ctx, &arg_key, &arg_value)?; + match res { + None => {}, + Some(mut cmd) => { + wait_child(cmd.spawn()?, ctx)?; + } + } + res = self.contents.exec(ctx)?; + ctx.pop_scope(); + if ctx.break_num > 0 { + ctx.break_num -= 1; + break; + } + } + } + }, + _ => bail!("Invalid for expression") + }; + + Ok(res) + } +} + impl ExecExpression for IfExpression { fn exec(self: &mut IfExpression, ctx: &mut vars::Context) -> Result> { if ctx.break_num > 0 { return Ok(None) } @@ -264,10 +344,10 @@ impl ExecExpression for Vec { fn exec(self: &mut Vec, ctx: &mut vars::Context) -> Result> { if ctx.break_num > 0 { return Ok(None) } if self.is_empty() { bail!("Command with 0 length"); } - let mut first = self.remove(0); + let first = self.get_mut(0).unwrap(); let command_name = first.get(ctx)?.to_string(); let mut cmd = Command::new(command_name); - for value in self { + for value in &mut self[1..] { cmd.arg(value.get(ctx)?.to_string()); } Ok(Some(cmd)) @@ -412,6 +492,12 @@ impl ExecExpression for AndExpression { } } +fn wait_child(mut child: Child, ctx: &mut Context) -> Result<()> { + let out = child.wait()?; + ctx.set_var(String::from("?"), Variable::I32(out.code().unwrap_or(1))); + Ok(()) +} + pub fn exec_tree(tree: Vec, ctx: &mut vars::Context) -> Result<()> { for mut expression in tree { let cmd = expression.exec(ctx)?; @@ -422,8 +508,7 @@ pub fn exec_tree(tree: Vec, ctx: &mut vars::Context) -> Result<()> { println!("Error executing: {}", e); }, Result::Ok(mut res) => { - let out = res.wait()?; - ctx.set_var(String::from("?"), Variable::I32(out.code().unwrap_or(1))); + wait_child(res, ctx)?; } } } diff --git a/src/parser/tokens.rs b/src/parser/tokens.rs index 794dac6..bd1f12e 100644 --- a/src/parser/tokens.rs +++ b/src/parser/tokens.rs @@ -167,12 +167,13 @@ pub fn tokenize(reader: &mut dyn std::io::BufRead) -> Result> { buf_add = false; } else { 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() == '(' { + Tokens::StringVariable(ref str, bool) => if !bool && !double_quote_active && text.len() > i + skippers + 1 && text.chars().nth(i + skippers + 1).unwrap() == '(' { + skippers += 1; token = Token { token: Tokens::StringFunction(str.clone()), end: i + skippers, start: i }; }, - Tokens::ArrayVariable(ref str, bool) => if !bool && !double_quote_active && text.len() > i + skippers && text.chars().nth(i + skippers).unwrap() == '(' { + Tokens::ArrayVariable(ref str, bool) => if !bool && !double_quote_active && text.len() > i + skippers + 1 && text.chars().nth(i + skippers + 1).unwrap() == '(' { + skippers += 1; token = Token { token: Tokens::ArrayFunction(str.clone()), end: i+skippers, start: i }; } _ => bail!("Cannot happen") diff --git a/src/parser/vars.rs b/src/parser/vars.rs index a0b4326..582a808 100644 --- a/src/parser/vars.rs +++ b/src/parser/vars.rs @@ -15,7 +15,8 @@ pub enum Variable { F32(f32), F64(f64), HMap(HashMap), - Array(Vec) + Array(Vec), + Bool(bool) } impl Display for Variable { @@ -32,6 +33,13 @@ impl Display for Variable { Variable::U128(num) => num.to_string(), Variable::F32(num) => num.to_string(), Variable::F64(num) => num.to_string(), + Variable::Bool(val) => { + if *val { + String::from("true") + } else { + String::from("false") + } + }, Variable::HMap(_map) => { String::from("[Object object]") },