diff --git a/README.md b/README.md index 7140335..4740a0d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,88 @@ # Rush In case you're reading this: rush is in the works and not a priority. Features may be missing even if defined below. + +## Ideating + +### Syntax + +```sh +let thing = var +let thing = (echo test) +echo test | cowsay +parse file.csv +parse file.csv | sort 2 asc +cat file.csv | parse --csv +parse <(cat file.csv) + file.csv +parse < file.csv > file2.csv +echo test >> file + +# what bindings do we support here? +# array/object destructors? +# for [i, val] in (cat file.csv | enumerate) might be nice syntax to get line numbers +for i in (parse file.csv) { + echo $i[1] +} + +if (true) { + echo $i[1] +} + +if 1 {} +else { + echo unused +} + +while true { + break +} + +loop {} + +# do we error if array literal is used directly in exec call? +# as that would likely be a mistake like $i [property] +echo $i[property] $i[$dynamicproperty] + +# or another option - array constructor with a different syntax +# like @[ ] [[ ]] +let array = [var] +# question here, do we allow multiline values? How? \ ? +# or the more classic comma `,` for separating values and ignoring white space? +# this is easier to write so might be preferred for very short scripting lang +let obj = ${ + key: value + $dynkey: $value2 +} +let literal = "$var" +# or perhaps ` ? +let formatted = f"$var" + +test 1 = 1 +# perhaps (( x )) could be used for math expressions? +# basically just alias to (calc x) +calc 1 + 1 +``` + +### Values + +- String +- Number (f64) +- Object +- Array + - Array streams +- Void + +#### Objects + +HashMaps mapping strings to values + +#### Array + +Arrays mapping integers (0 indexed) to values + +#### Void + +Acts as undefined for array and object properties that don't exist. diff --git a/src/env.rs b/src/env.rs deleted file mode 100644 index d1313ed..0000000 --- a/src/env.rs +++ /dev/null @@ -1,13 +0,0 @@ -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 ae34279..8a55e80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,225 +1,3 @@ -mod parser; -mod env; -mod nativeFunctions; - -use std::io::{self, BufRead, Stdout, Write}; -use std::cmp; -use std::convert::TryInto; -use std::path::Path; -use std::process; -use clap::{Command, arg}; -use termion::raw::{IntoRawMode, RawTerminal}; -use termion::input::TermRead; -use termion::cursor::{DetectCursorPos}; -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, - idx: usize, -} - -impl Term { - fn new() -> Term { - Term { - input: String::new(), - idx: 0, - } - } - - fn print(self: &Self, stdout: &mut RawTerminal) { - print!("{}", self.format(stdout)); - } - fn format(self: &Self, stdout: &mut RawTerminal) -> String { - let (_, y) = stdout.cursor_pos().unwrap(); - format!( - "{}{}{}{}{}{}", - termion::clear::CurrentLine, - termion::cursor::Goto(1, y), - "$: ", - &self.input, - termion::cursor::Left((self.input.len() - self.idx).try_into().unwrap()), - termion::cursor::Right(if self.input.len() > 0 { 1 } else { 0 } ) - ) - } - - fn insert(self: &mut Self, idx: usize, char: char) { - self.input.insert(idx, char); - } - fn insert_str(self: &mut Self, idx: usize, str: &str) { - self.input.insert_str(idx, str); - } - fn remove(self: &mut Self, idx: usize) { - self.input.remove(idx); - } -} - -struct Shell { - term: Term, - ctx: parser::vars::Context, -} - -impl Shell { - fn new() -> Shell { - Shell { - term: Term::new(), - ctx: parser::vars::Context::new() - } - } - - fn collect(&mut self) { - let stdin = std::io::stdin(); - let v = stdin.lock().lines().next().unwrap().unwrap(); - self.term.input = v; - } - - - fn edit(&mut self) { - let stdin = io::stdin(); - let mut stdout = io::stdout().into_raw_mode().unwrap(); - for c in stdin.keys() { - let c = c.unwrap(); - match c { - Key::Char('\n') => { - if self.term.input.chars().nth(self.term.idx).unwrap_or(' ') == '\\' { - self.term.insert_str(self.term.idx, "\\\n"); - } else { - break; - } - } - Key::Backspace => { - if !self.term.input.is_empty() && self.term.idx > 0 { - if self.term.idx == self.term.input.len() - 1 { - self.term.input.pop(); - } else { - self.term.remove(self.term.idx - 1); - } - self.term.idx -= 1; - } - } - Key::Delete => { - if self.term.idx < self.term.input.len() { - self.term.remove(self.term.idx); - } - } - Key::End => { - self.term.idx = cmp::max(self.term.input.len(), 1) - 1; - } - Key::Home => { - self.term.idx = 0; - } - Key::Left => { - if self.term.idx > 0 { - self.term.idx -= 1; - } - } - Key::Right => { - if self.term.idx < self.term.input.len() - 1 { - self.term.idx += 1; - } - } - Key::Ctrl('c') => { - process::exit(1); - } - Key::Ctrl('d') => { - process::exit(0); - } - Key::Char(char) => { - self.term.insert(self.term.idx, char); - self.term.idx += 1; - } - _ => {} - } - self.term.print(&mut stdout); - stdout.flush().unwrap(); - } - stdout.suspend_raw_mode().unwrap(); - } - - fn start() { - let mut shell = Shell::new(); - shell.ctx.native_func = get_native_functions(); - loop { - print!("$: "); - io::stdout().flush().unwrap(); - shell.collect(); - if shell.term.input == "exit" { - 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); - if let Err(err) = res { eprintln!("rush: {}", err) } - } - } -} - -const VERSION: &str = env!("CARGO_PKG_VERSION"); -const AUTHORS: &str = env!("CARGO_PKG_AUTHORS"); -const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); - -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) -} - fn main() { - let matches = Command::new("Rush") - .version(VERSION) - .author(AUTHORS) - .about(DESCRIPTION) - .arg( - arg!([file] "File to execute") - ) - .arg( - arg!(-c --command "Command to execute") - .required(false) - ) - .get_matches(); - - if let Some(command) = matches.value_of("command") { - let mut ctx = parser::vars::Context::new(); - parser::exec(&mut command.as_bytes(), &mut ctx).unwrap(); - return; - }; - if let Some(file) = matches.value_of("file") { - load_and_run(file).unwrap(); - return; - }; - Shell::start(); -} - -#[cfg(test)] -mod test { - use crate::{load_and_run}; - use anyhow::Result; - #[test] - fn simple() -> Result<()> { - load_and_run("test/simple.rush") - } - - #[test] - fn var() -> Result<()> { - load_and_run("test/var.rush") - } - - #[test] - fn if_base() -> Result<()> { - load_and_run("test/base_if.rush") - } - - #[test] - fn if_else() -> Result<()> { - load_and_run("test/if_else.rush") - } - - #[test] - fn while_expr() -> Result<()> { - load_and_run("test/while.rush") - } -} + +} \ No newline at end of file diff --git a/src/nativeFunctions.rs b/src/nativeFunctions.rs deleted file mode 100644 index 9a8c03c..0000000 --- a/src/nativeFunctions.rs +++ /dev/null @@ -1,162 +0,0 @@ -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 - }); - - 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 - }); - - fn rush_length(_ctx: &mut Context, args: Vec) -> Result { - if args.len() != 1 { - return Ok(Variable::I64(args.len() as i64)); - } - let arg = args.get(0).unwrap(); - let res = match arg { - Variable::String(s) => s.len(), - Variable::Array(a) => a.len(), - Variable::HMap(h) => h.len(), - _ => bail!("Unsupported type") - }; - Ok(Variable::I64(res as i64)) - } - map.insert("length".to_string(), NativeFunction { - name: "length".to_string(), - description: "Returns the length of a string, array or hashmap".to_string(), - args: vec![String::from("var")], - func: rush_length - }); - - map.insert("$length".to_string(), NativeFunction { - name: "$length".to_string(), - description: "Returns the length of a string, array or hashmap".to_string(), - args: vec![String::from("var")], - func: rush_length - }); - - map -} \ No newline at end of file diff --git a/src/parser/ast.rs b/src/parser/ast.rs deleted file mode 100644 index 41a9944..0000000 --- a/src/parser/ast.rs +++ /dev/null @@ -1,675 +0,0 @@ -use crate::parser::tokens::{Token, Tokens}; -use anyhow::{bail, Context, Result}; - -#[derive(Debug, Clone)] -pub struct LetExpression { - pub key: Box, - pub vartype: Option, - pub value: Box -} - -#[derive(Debug, Clone)] -pub struct AndExpression { - pub first: Box, - pub second: Box -} - -#[derive(Debug, Clone)] -pub struct OrExpression { - pub first: Box, - pub second: Box -} - -#[derive(Debug, Clone)] -pub struct IfExpression { - pub condition: Box, - pub contents: Vec, - pub else_contents: Vec -} - -#[derive(Debug, Clone)] -pub struct WhileExpression { - pub condition: Box, - pub contents: Vec -} - -#[derive(Debug, Clone)] -pub struct ForExpression { - pub arg_value: Value, - pub arg_key: Option, - pub list: Value, - pub contents: Vec, - pub else_contents: Vec -} - -#[derive(Debug, Clone)] -pub enum ForValue { - Value(Value), - Range(Option, Option) -} - -#[derive(Debug, Clone)] -pub struct DefinedFunctionCall { - pub name: String, - pub args: Vec -} - -#[derive(Debug, Clone)] -pub enum Value { - Literal(String), - Variable(String), - ArrayVariable(String), - ArrayDefinition(Vec), - ValueFunction(DefinedFunctionCall), - Expressions(Vec), - Values(Vec) -} - -#[derive(Debug, Clone)] -pub struct FunctionVariable { - pub name: String, - pub vartype: Option -} - -#[derive(Debug, Clone)] -pub struct FunctionDefinitionExpression { - pub name: String, - pub description: Option, - pub on_event: Option, - pub args: Vec, - pub body: Box -} - -#[derive(Debug, Clone)] -pub struct RedirectTargetExpression { - pub source: Box, - pub target: Box -} - -#[derive(Debug, Clone)] -pub struct FileTargetExpression { - pub source: Option>, - pub target: Box -} - -#[derive(Debug, Clone)] -pub struct FileSourceExpression { - pub source: Box, - pub target: Option> -} - -#[derive(Debug, Clone)] -pub enum CommandValue { - Value(Value), - Var(String, Value) -} - -#[derive(Debug, Clone)] -pub struct BreakExpression { - pub num: Box -} - -#[derive(Debug, Clone)] -pub enum Expression { - LetExpression(LetExpression), - Command(Vec), - JobCommand(Box), - Function(FunctionDefinitionExpression), - IfExpression(IfExpression), - WhileExpression(WhileExpression), - ForExpression(ForExpression), - RedirectTargetExpression(RedirectTargetExpression), - FileTargetExpression(FileTargetExpression), - FileSourceExpression(FileSourceExpression), - Expressions(Vec), - OrExpression(OrExpression), - AndExpression(AndExpression), - BreakExpression(BreakExpression) -} - -#[derive(Debug)] -struct Tree { - tokens: Vec, - i: usize -} - -impl Tree { - fn parse_call(&mut self, end: usize) -> Result { - let mut values: Vec = Vec::new(); - let mut buf: Vec = Vec::new(); - let mut token = self.get_current_token(); - loop { - if matches!(token, Tokens::Space) { - if !buf.is_empty() { - 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 } - self.i += 1; - token = self.get_current_token(); - continue; - } - let val = match &token { - Tokens::Literal(str) => Value::Literal(str.clone()), - Tokens::SubStart => { - let val = self.get_value(end, false)?; - token = self.get_current_token(); - val - }, - Tokens::StringVariable(str, _) => { - if str.is_empty() { bail!("Expected variable name"); } - Value::Variable(str.clone()) - }, - Tokens::ArrayVariable(str, _) => Value::ArrayVariable(str.clone()), - Tokens::FileWrite => break, - Tokens::FileRead => break, - Tokens::RedirectInto => break, - Tokens::And => break, - Tokens::Or => break, - Tokens::JobCommandEnd => break, - Tokens::ParenthesisEnd => { - if self.i >= end - 1 { - break; - } - 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()) - } - }; - buf.push(val); - if self.i >= end - 1 { break } - self.i += 1; - token = &self.tokens.get(self.i).unwrap().token; - if matches!(token, Tokens::CommandEnd(_)) { break } - } - match &token { - Tokens::FileWrite | Tokens::FileRead | Tokens::RedirectInto => self.i -= 1, - _ => {} - } - // self.next(); - if !buf.is_empty() { - if buf.len() == 1 { - values.push(CommandValue::Value(buf.pop().unwrap())); - } else { - values.push(CommandValue::Value(Value::Values(buf))); - } - } - Ok(Expression::Command(values)) - } - - fn parse_let(&mut self, end: usize) -> Result { - if end < self.i + 2 { bail!("Let needs name and equal sign (=) at minimum") } - self.inc(); - let mut len = 0; - for token in &self.tokens[self.i..] { - match token.token { - Tokens::ExportSet => { break }, - _ => len += 1 - } - } - let key = Box::new(self.get_value(self.i + len, false)?); - self.inc(); // ???? - self.inc(); - let value = Box::new(self.get_value(end, false)?); - Ok(Expression::LetExpression(LetExpression { key, vartype: None, value })) - } - - fn parse_read(&mut self, target: Option, _end: usize) -> Result { - let target = target.map(Box::new); - self.i += 1; - let mut val_end = self.i; - let mut found_first = false; - for token in &self.tokens[self.i..] { - val_end += 1; - match token.token { - Tokens::Space => if found_first { break }, - Tokens::CommandEnd(_) => if !found_first { bail!("Unexpected command end") } else { break }, - Tokens::FileRead => bail!("Unexpected file read (<)"), - Tokens::FileWrite => bail!("Unexpected file write (>)"), - _ => { found_first = true; } - } - } - val_end -= 1; - let source = Box::new(self.get_value(val_end, false)?); - self.inc(); - Ok(Expression::FileSourceExpression(FileSourceExpression { source, target })) - } - - fn parse_write(&mut self, source: Option, _end: usize) -> Result { - let source = source.map(Box::new); - self.i += 1; - let mut val_end = self.i; - let mut found_first = false; - for token in &self.tokens[self.i..] { - val_end += 1; - match token.token { - Tokens::Space => if found_first { break }, - Tokens::CommandEnd(_) => if !found_first { bail!("Unexpected command end") } else { break }, - Tokens::FileRead => bail!("Unexpected file read (<)"), - Tokens::FileWrite => bail!("Unexpected file write (>)"), - _ => { found_first = true; } - } - } - val_end -= 1; - let target = Box::new(self.get_value(val_end, false)?); - self.inc(); - Ok(Expression::FileTargetExpression(FileTargetExpression { source, target })) - } - - fn parse_function(&mut self, _end: usize) -> Result { - bail!("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") - }; - let mut args = Vec::new(); - self.inc(); - loop { - args.push(self.get_value(end, false)?); - self.inc(); - if self.i >= end { break } - } - - Ok(DefinedFunctionCall { name, args }) - } - - fn parse_for(&mut self, end: usize) -> Result { - self.inc(); - 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 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) { - self.inc(); - loop { - match self.get_current_token() { - Tokens::End => break, - Tokens::Space => {}, - Tokens::CommandEnd(_) => {} - Tokens::Else => break, - Tokens::If => { - else_contents.push(self.get_expression(end).with_context(|| "Error getting contents for if expression")?); - if else_contents.len() == 1 { break }; - } - _ => 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 < end { - loop { - match self.get_current_token() { - Tokens::CommandEnd(_) => { self.inc(); }, - Tokens::Space => { self.inc(); }, - _ => 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 }) - } - - fn parse_while(&mut self, end: usize) -> Result { - self.inc(); - let condition = self.get_expression(end).with_context(|| "Error getting condition for while expression")?; - let mut contents = Vec::new(); - self.inc(); - loop { - let token = self.get_current_token(); - match token { - Tokens::End => break, - Tokens::Else => bail!("Unexpected ELSE. Support for ELSE statements after WHILE may come later."), - Tokens::CommandEnd(_) => { self.inc(); }, - Tokens::Space => { self.inc(); }, - _ => contents.push(self.get_expression(end).with_context(|| "Error getting contents for while expression")?) - }; - } - self.inc(); - Ok(WhileExpression { condition: Box::new(condition), contents }) - } - - fn parse_sub(&mut self, end: usize) -> Result> { - let mut expressions: Vec = Vec::new(); - loop { - if self.i >= end - 1 { break; } - expressions.push(self.get_expression(end)?); - } - Ok(expressions) - } - - fn parse_array_definition(&mut self, end: usize) -> Result> { - let mut values: Vec = Vec::new(); - loop { - 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(); } - } - 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(); - let mut buf: Vec = Vec::new(); - loop { - match token { - Tokens::Space => { - 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 } - }, - Tokens::CommandEnd(_) => break, - Tokens::Literal(str) => buf.push(Value::Literal(str.clone())), - Tokens::ExportSet => bail!("Unexpected token EXPORT_SET (=)"), - Tokens::FileRead => buf.push(Value::Literal(token.to_str())), - Tokens::Function => buf.push(Value::Literal(token.to_str())), - Tokens::FileWrite => buf.push(Value::Literal(token.to_str())), - Tokens::RedirectInto => bail!("Unexpected token REDIRECT (|)"), - Tokens::ParenthesisEnd => bail!("Unexpected token FUNCTION CALL END ())"), - 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; - let mut lvl = 1; - self.inc(); - for token in &self.tokens[self.i..] { - match token.token { - Tokens::ArrayStart => lvl += 1, - Tokens::ArrayEnd => lvl -= 1, - _ => {} - } - if lvl == 0 { - break; - } - len += 1; - - if len + self.i == end { break } - } - if lvl != 0 { - bail!("Parenthesis do not match"); - } - let val = Value::ArrayDefinition(self.parse_array_definition(self.i + len)?); - values.push(val); - }, - Tokens::ArrayEnd => bail!("Unexpected token ARRAY END (])"), - Tokens::SubStart => { - let (len, lvl) = self.get_parens_vals(end); - self.inc(); - if lvl != 0 { - bail!("Parenthesis do not match"); - } - let val = Value::Expressions(self.parse_sub(self.i + len)?); - self.inc(); - return Ok(val); - }, - Tokens::Else => buf.push(Value::Literal(token.to_str())), - Tokens::End => buf.push(Value::Literal(token.to_str())), - Tokens::For => buf.push(Value::Literal(token.to_str())), - Tokens::If => buf.push(Value::Literal(token.to_str())), - Tokens::Let => buf.push(Value::Literal(token.to_str())), - Tokens::While => buf.push(Value::Literal(token.to_str())), - Tokens::StringVariable(str, _) => { - if !buf.is_empty() { - values.push(Value::Values(buf)); - buf = Vec::new(); - } - values.push(Value::Variable(str.clone())); - }, - Tokens::ArrayVariable(str, _) => { - if !buf.is_empty() { - values.push(Value::Values(buf)); - buf = Vec::new(); - } - values.push(Value::ArrayVariable(str.clone())); - }, - Tokens::And => bail!("Unexpected AND (&&)"), - Tokens::Or => bail!("Unexpected OR (||)"), - Tokens::Break => buf.push(Value::Literal(token.to_str())), - Tokens::JobCommandEnd => bail!("Unexpected job command end (&)"), - } - if self.i >= end - 1 { break } - token = self.inc().get_current_token(); - } - if !buf.is_empty() { - 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()); - } - Ok(Value::Values(values)) - } - - fn get_expression(&mut self, end: usize) -> Result { - let mut expr: Option = None; - let mut token = self.get_current_token(); - loop { - match token { - Tokens::Space => {self.inc();}, - Tokens::CommandEnd(_) => { if matches!(expr, Some(_)) { break }; self.inc();}, - Tokens::Literal(_) => if matches!(expr, Some(_)) { - bail!("Unexpected literal. After file redirect, you need to use a semicolon or newline."); - } else { - expr = Some(self.parse_call(end)?); - }, - Tokens::ExportSet => bail!("Unexpected token EXPORT SET (=)"), - Tokens::Function => return Ok(Expression::Function(self.parse_function(end)?)), - Tokens::FileRead => expr = Some(self.parse_read(expr, end)?), - Tokens::FileWrite => expr = Some(self.parse_write(expr, end)?), - Tokens::RedirectInto => match expr { - None => bail!("Unexpected token REDIRECT (|)"), - Some(_) => { - self.i += 1; - expr = Some(Expression::RedirectTargetExpression(RedirectTargetExpression { source: Box::new(expr.unwrap()), target: Box::new(self.get_expression(end)?) })); - } - }, - Tokens::ParenthesisStart => if matches!(expr, Some(_)) { - bail!("Unexpected parenthesis. After file redirect, you need to use a semicolon or newline."); - } else { - self.inc(); - let (len, lvl) = self.get_parens_vals(end); - if lvl != 0 { - bail!("Parenthesis not ended properly."); - } - expr = Some(self.get_expression(self.i + len)?); - self.inc(); - }, - Tokens::ParenthesisEnd => bail!("Unexpected token PARENTHESIS END ())"), - Tokens::ArrayStart => bail!("Arrays not yet implemented"), - Tokens::ArrayEnd => bail!("Unexpected token ARRAY END (])"), - Tokens::ArrayFunction(_) => bail!("Unexpected array function"), - Tokens::StringFunction(_) => bail!("Unexpected string function"), - Tokens::SubStart => match expr { - Some(_) => bail!("Unexpected literal. After file redirect, you need to use a semicolon or newline."), - _ => expr = Some(self.parse_call(end)?) - }, - Tokens::Else => bail!("Unexpected token ELSE"), - Tokens::End => { bail!("Unexpected token END"); }, - Tokens::For => match expr { - Some(_) => bail!("Commands must be ended properly"), - None => expr = Some(Expression::ForExpression(self.parse_for(end)?)), - }, - Tokens::If => match expr { - Some(_) => bail!("Commands must be ended properly"), - None => {expr = Some(Expression::IfExpression(self.parse_if(end)?)); }, - } - Tokens::Let => return self.parse_let(end), - Tokens::While => return Ok(Expression::WhileExpression(self.parse_while(end)?)), - Tokens::StringVariable(_, _) => if matches!(expr, Some(_)) { - bail!("Unexpected variable. After file redirect, you need to use a semicolon or newline."); - } else { - expr = Some(self.parse_call(end)?); - }, - Tokens::ArrayVariable(_, _) => bail!("Unexpected array variable"), - Tokens::And => match expr { - None => bail!("Unexpected AND (&&)"), - Some(_) => { - self.inc(); - expr = Some(Expression::AndExpression(AndExpression { first: Box::new(expr.unwrap()), second: Box::new(self.get_expression(end)?) })); - } - }, - Tokens::Or => match expr { - None => bail!("Unexpected OR (||)"), - Some(_) => { - self.inc(); - expr = Some(Expression::OrExpression(OrExpression { first: Box::new(expr.unwrap()), second: Box::new(self.get_expression(end)?) })); - } - }, - Tokens::Break => match expr { - None => { - self.inc(); - expr = Some(Expression::BreakExpression(BreakExpression { num: Box::new(self.get_value(end, false)?)})); - }, - Some(_) => bail!("Unexpected break") - } - Tokens::JobCommandEnd => bail!("Jobs not yet implemented") - }; - if self.i >= end - 1 { break } - token = self.get_current_token(); - } - match expr { - Some(expr) => Ok(expr), - None => bail!("No expression found") - } - } - - fn inc(&mut self) -> &Self { - self.i += 1; - self - } - fn get_current_token(&self) -> &Tokens { &self.tokens.get(self.i).unwrap().token } -} - -pub fn build_tree(tokens: Vec) -> Result> { - // dbg!(&tokens); - let mut expressions: Vec = Vec::new(); - let mut tree = Tree { tokens, i: 0 }; - loop { - if tree.i >= tree.tokens.len() - 1 { break; } - let val = tree.get_expression(tree.tokens.len()); - match val { - Ok(val) => expressions.push(val), - Err(error) => { - if error.to_string() == "No expression found" { break } - return Err(error); - } - } - } - dbg!(&expressions); - Ok(expressions) -} diff --git a/src/parser/exec.rs b/src/parser/exec.rs deleted file mode 100644 index f9be175..0000000 --- a/src/parser/exec.rs +++ /dev/null @@ -1,403 +0,0 @@ -use std::fs::File; -use std::io::Read; -use std::process::Command; -use std::thread; -use crate::parser::ast::{AndExpression, BreakExpression, CommandValue, Expression, FileSourceExpression, FileTargetExpression, ForExpression, IfExpression, LetExpression, OrExpression, RedirectTargetExpression, Value, WhileExpression}; -use crate::parser::vars::{AnyFunction, Context, ReaderOverride, Variable, WriterOverride}; -use anyhow::{Result, bail, Context as AnyhowContext}; - -#[derive(Debug, Default)] -struct ExecResult { - commands: Vec -} - -impl ExecResult { - fn exec(self, ctx: &mut Context) -> Result> { - let mut children = Vec::new(); - for mut command in self.commands { - let name = command.get_program().to_str().unwrap_or("unknown").to_string(); - let out = command.spawn() - .with_context(|| "Failed to spawn process ".to_string() + &name)?; - drop(command); - children.push(out); - } - let mut code = None; - for mut child in children { - let out = child.wait() - .with_context(|| "Command failed")?; - code = Some(out.code().unwrap_or(-1)); - } - if let Some(code) = code { - ctx.set_var(String::from("?"), Variable::I32(code)); - } - Ok(code) - } - - fn merge(&mut self, mut other: ExecResult) { - self.commands.append(&mut other.commands); - } -} - -trait ExecExpression { - fn exec(&mut self, ctx: &mut Context) -> Result; -} - -trait GetValue { - fn get(&mut self, ctx: &mut Context) -> Result; -} - -impl GetValue for CommandValue { - fn get(self: &mut CommandValue, ctx: &mut Context) -> Result { - match self { - CommandValue::Value(val) => val.get(ctx), - CommandValue::Var(_, _) => bail!("Broken executor") - } - } -} - -impl GetValue for Value { - fn get(self: &mut Value, ctx: &mut Context) -> Result { - match self { - 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::Expressions(expressions) => { - ctx.add_scope(); - let (mut reader, writer) = os_pipe::pipe()?; - let mut data = String::new(); - thread::scope(|s| -> Result<()> { - ctx.scopes.last_mut().unwrap().stdout_override = Some(WriterOverride::Pipe(writer)); - s.spawn(|| -> Result<()> { - let mut buf = Vec::new(); - reader.read_to_end(&mut buf)?; - data = String::from_utf8_lossy(&buf).to_string(); - Ok(()) - }); - expressions.exec(ctx)?.exec(ctx)?; - Ok(()) - })?; - ctx.pop_scope(); - Ok(Variable::String(data)) - }, - Value::Values(vec) | Value::ArrayDefinition(vec) => { - let mut out = Vec::new(); - for val in vec { - out.push(val.get(ctx)?); - } - 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 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 Context) -> Result { - match self { - Expression::LetExpression(expr) => expr.exec(ctx), - Expression::Command(expr) => expr.exec(ctx), - Expression::JobCommand(_) => todo!("Jobs"), - Expression::Function(_) => todo!("Function definition"), - Expression::IfExpression(expr) => expr.exec(ctx), - Expression::WhileExpression(expr) => expr.exec(ctx), - Expression::ForExpression(expr) => expr.exec(ctx), - Expression::RedirectTargetExpression(expr) => expr.exec(ctx), - Expression::FileTargetExpression(expr) => expr.exec(ctx), - Expression::FileSourceExpression(expr) => expr.exec(ctx), - Expression::Expressions(expr) => expr.exec(ctx), - Expression::OrExpression(expr) => expr.exec(ctx), - Expression::AndExpression(expr) => expr.exec(ctx), - Expression::BreakExpression(expr) => expr.exec(ctx) - } - } -} - -impl ExecExpression for BreakExpression { - fn exec(self: &mut BreakExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { ctx.break_num -= 1; return Ok(ExecResult::default()) } - let val = self.num.get(ctx)?.to_string(); - let num: u16 = if !val.is_empty() { val.parse()? } else { 1 }; - ctx.break_num = if num == 0 { 1 } else { num }; - Ok(ExecResult::default()) - } -} - -impl ExecExpression for WhileExpression { - fn exec(self: &mut WhileExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { ctx.break_num -= 1; return Ok(ExecResult::default()) } - ctx.add_scope(); - let mut res: Option = None; - loop { - let condition = self.condition.exec(ctx)?; - let condition_res = condition.exec(ctx)?; - let code = condition_res.unwrap_or(1); - - if code == 0 { - if let Some(result) = res { - result.exec(ctx)?; - } - res = Some(self.contents.exec(ctx)?); - } else { - res = None; - break; - } - if ctx.break_num > 0 { - ctx.break_num -= 1; - break; - } - } - ctx.pop_scope(); - - Ok(res.unwrap_or(ExecResult::default())) - } -} - -impl ExecExpression for ForExpression { - fn exec<'a>(&mut self, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { ctx.break_num -= 1; return Ok(ExecResult::default()) } - 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)?; - if let Some(res) = res { - res.exec(ctx)?; - } - res = Some(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)?; - if let Some(res) = res { - res.exec(ctx)?; - } - res = Some(self.contents.exec(ctx)?); - ctx.pop_scope(); - if ctx.break_num > 0 { - ctx.break_num -= 1; - break; - } - } - } - }, - _ => bail!("Invalid for expression") - }; - - Ok(res.unwrap_or(ExecResult::default())) - } -} - -impl ExecExpression for IfExpression { - fn exec(self: &mut IfExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - let condition = self.condition.exec(ctx)?; - ctx.add_scope(); - let condition_result = condition.exec(ctx)?; - let code = condition_result.unwrap_or(1); - let res= if code == 0 { - self.contents.exec(ctx)? - } else { - self.else_contents.exec(ctx)? - }; - ctx.pop_scope(); - - Ok(res) - } -} - -impl ExecExpression for LetExpression { - fn exec(self: &mut LetExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - let key = self.key.get(ctx)?; - let val = self.value.get(ctx)?; - ctx.set_var(key.to_string(), val); - Ok(ExecResult::default()) - } -} - -impl ExecExpression for Vec { - fn exec(self: &mut Vec, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - if self.is_empty() { bail!("Command with 0 length"); } - 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 &mut self[1..] { - cmd.arg(value.get(ctx)?.to_string()); - } - let overrides = ctx.get_overrides()?; - if let Some(stdout) = overrides.stdout { cmd.stdout(stdout); } - if let Some(stderr) = overrides.stderr { cmd.stderr(stderr); } - if let Some(stdin) = overrides.stdin { cmd.stdin(stdin); } - Ok(ExecResult { - commands: vec![cmd] - }) - } -} - -impl ExecExpression for RedirectTargetExpression { - fn exec(self: &mut RedirectTargetExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - let (reader, writer) = os_pipe::pipe()?; - - ctx.add_scope(); - ctx.scopes.last_mut().unwrap().stdout_override = Some(WriterOverride::Pipe(writer)); - let mut src = self.source.exec(ctx)?; - ctx.pop_scope(); - ctx.add_scope(); - ctx.scopes.last_mut().unwrap().stdin_override = Some(ReaderOverride::Pipe(reader)); - let target = self.target.exec(ctx)?; - ctx.pop_scope(); - src.merge(target); - - Ok(src) - } -} - -impl ExecExpression for FileTargetExpression { - fn exec(self: &mut FileTargetExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - let src = &mut self.source; - let target = self.target.get(ctx)?; - - ctx.add_scope(); - - let file = File::create(target.to_string())?; - ctx.scopes.last_mut().unwrap().stdout_override = Some(WriterOverride::File(file)); - - let src = match src { - Some(expr) => expr.exec(ctx)?, - None => { - bail!("Redirect without target file"); - } - }; - - ctx.pop_scope(); - Ok(src) - } -} - -impl ExecExpression for FileSourceExpression { - fn exec(self: &mut FileSourceExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - let source = self.source.get(ctx)?.to_string(); - let source = File::open(source).with_context(|| "Couldn't open file to read")?; - let target = &mut self.target; - - ctx.add_scope(); - ctx.scopes.last_mut().unwrap().stdin_override = Some(ReaderOverride::File(source)); - let target = match target { - Some(expr) => expr.exec(ctx)?, - None => { - vec![CommandValue::Value(Value::Literal(String::from("less")))].exec(ctx)? - } - }; - ctx.pop_scope(); - - Ok(target) - } -} - -impl ExecExpression for Vec { - fn exec(self: &mut Vec, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - let mut last: Option = None; - for expr in self { - if let Some(last) = last { - last.exec(ctx)?; - } - last = Some(expr.exec(ctx)?); - if ctx.break_num > 0 { return Ok(last.unwrap()) } - } - Ok(last.unwrap_or(ExecResult::default())) - } -} - -impl ExecExpression for OrExpression { - fn exec(self: &mut OrExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - let first = self.first.exec(ctx)?; - let code = first.exec(ctx)?; - let code = code.unwrap_or(1); - if code == 0 { - Ok(ExecResult::default()) - } else { - self.second.exec(ctx) - } - } -} - -impl ExecExpression for AndExpression { - fn exec(self: &mut AndExpression, ctx: &mut Context) -> Result { - if ctx.break_num > 0 { return Ok(ExecResult::default()) } - let first = self.first.exec(ctx)?; - let code = first.exec(ctx)?; - let code = code.unwrap_or(1); - if code == 0 { - self.second.exec(ctx) - } else { - Ok(ExecResult::default()) - } - } -} - -pub fn exec_tree(tree: Vec, ctx: &mut Context) -> Result<()> { - for mut expression in tree { - let cmd = expression.exec(ctx)?; - cmd.exec(ctx)?; - if ctx.break_num > 0 { bail!("Too many break statements") } - } - Ok(()) -} diff --git a/src/parser/mod.rs b/src/parser/mod.rs deleted file mode 100644 index 5e29ec5..0000000 --- a/src/parser/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub mod vars; -pub mod ast; -pub mod tokens; -mod exec; - -use crate::parser::ast::{build_tree}; -use crate::parser::exec::exec_tree; -use crate::parser::tokens::{tokenize}; -use anyhow::Result; - -pub fn exec(reader: &mut dyn std::io::BufRead, ctx: &mut vars::Context) -> Result<()> { - let tokens = tokenize(reader)?; - - let expressions = build_tree(tokens)?; - - exec_tree(expressions, ctx)?; - Ok(()) -} - -pub fn escape(str: String) -> String { - str -} diff --git a/src/parser/tokens.rs b/src/parser/tokens.rs deleted file mode 100644 index bd1f12e..0000000 --- a/src/parser/tokens.rs +++ /dev/null @@ -1,278 +0,0 @@ -use anyhow::{Result, bail}; - -#[derive(Debug)] -pub struct Token { - pub token: Tokens, - pub start: usize, - pub end: usize -} - -#[derive(Debug)] -pub enum Tokens { - Space, - Literal(String), - Let, - ExportSet, - StringVariable(String, bool), - ArrayVariable(String, bool), - ArrayFunction(String), - StringFunction(String), - ParenthesisStart, - ParenthesisEnd, - ArrayStart, - ArrayEnd, - CommandEnd(char), - If, - Else, - While, - For, - Function, - End, - SubStart, - RedirectInto, - FileRead, - FileWrite, - And, - Or, - Break, - JobCommandEnd -} - -impl Tokens { - fn detect(str: String) -> Tokens { - match str.as_str() { - "if" => Tokens::If, - "while" => Tokens::While, - "for" => Tokens::For, - "let" => Tokens::Let, - " " => Tokens::Space, - "else" => Tokens::Else, - "end" => Tokens::End, - "$(" => Tokens::SubStart, - "(" => Tokens::ParenthesisStart, - ")" => Tokens::ParenthesisEnd, - "[" => Tokens::ArrayStart, - "]" => Tokens::ArrayEnd, - ">" => Tokens::FileWrite, - "<" => Tokens::FileRead, - "|" => Tokens::RedirectInto, - "\r\n" | "\n" | ";" => Tokens::CommandEnd(str.chars().next().unwrap()), - "&&" => Tokens::And, - "||" => Tokens::Or, - "=" => Tokens::ExportSet, - "break" => Tokens::Break, - _ => Tokens::Literal(str) - } - } - - pub(crate) fn to_str(&self) -> String { - match self { - Tokens::Space => " ".to_string(), - Tokens::Literal(str) => str.clone(), - Tokens::Let => "let".to_string(), - Tokens::StringVariable(str, bool) => format!("${}{}{}", match bool { true => "{", false => ""}, str.as_str(), match bool { true => "{", false => "" }), - Tokens::ArrayVariable(str, bool) => format!("@{}{}{}", match bool { true => "{", false => ""}, str.as_str(), match bool { true => "{", false => "" }), - Tokens::ArrayFunction(str) => format!("@{}", str.as_str()), - Tokens::StringFunction(str) => format!("${}", str.as_str()), - Tokens::CommandEnd(str) => str.to_string(), - Tokens::ExportSet => "=".to_string(), - Tokens::Function => "function".to_string(), - Tokens::If => "if".to_string(), - Tokens::Else => "else".to_string(), - Tokens::While => "while".to_string(), - Tokens::For => "for".to_string(), - Tokens::End => "end".to_string(), - Tokens::SubStart => "$(".to_string(), - Tokens::ParenthesisStart => "(".to_string(), - Tokens::ParenthesisEnd => ")".to_string(), - Tokens::ArrayStart => "[".to_string(), - Tokens::ArrayEnd => "]".to_string(), - Tokens::RedirectInto => "|".to_string(), - Tokens::FileRead => "<".to_string(), - Tokens::FileWrite => ">".to_string(), - Tokens::And => "&&".to_string(), - Tokens::Or => "||".to_string(), - Tokens::Break => "break".to_string(), - Tokens::JobCommandEnd => "&".to_string() - } - } -} - - -fn read_var_ahead(i: usize, text: &str) -> Result<(usize, Token)> { - let mut x = i; - let mut buf = String::new(); - let parens_mode = text.chars().nth(x + 1).unwrap() == '{'; - if parens_mode { x += 1 } - loop { - x += 1; - let letter: char = text.chars().nth(x).unwrap(); - match letter { - 'a'..='z' | 'A'..='Z' | '0'..='9' | ':' | '_' => { - buf.push(letter); - } - '}' => { - if parens_mode { - x += 1; - } - break; - } - '?' => { - buf.push(letter); - x += 1; - break; - } - l => { if !parens_mode { break } else { bail!("Invalid variable name (starting with '{}{}')", buf, l) } } - } - } - let token = match text.chars().nth(i).unwrap() { - '$' => Token { token: Tokens::StringVariable(buf, parens_mode), start: i, end: i + x }, - '@' => Token { token: Tokens::ArrayVariable(buf, parens_mode), start:i , end: i+x }, - a => bail!("Invalid value {}", a) - }; - Ok((x - i - 1, token)) -} - -pub fn tokenize(reader: &mut dyn std::io::BufRead) -> Result> { - let mut quote_active = false; - let mut double_quote_active = false; - let mut escape_active = false; - let mut text = String::new(); - reader.read_to_string(&mut text)?; - let text_length = text.len(); - - let mut tokens: Vec = Vec::new(); - - fn save_buf(buf: &mut String, tokens: &mut Vec, i: usize) { - if !buf.is_empty() { tokens.push(Token { token: Tokens::detect(std::mem::take(buf)), end: i, start: i - buf.len() }) } - } - - let mut buf = String::new(); - let mut skipper = 0; - for i in 0..text_length { - if skipper > 0 { - skipper -= 1; - continue; - } - let letter: &char = &text.chars().nth(i).unwrap(); - let mut buf_add = true; - match letter { - '"' => if !escape_active && !quote_active { double_quote_active = !double_quote_active; buf_add = false }, - '\'' => if !escape_active && !double_quote_active { quote_active = !quote_active; buf_add = false }, - '$' | '@' => if !escape_active && !quote_active { - save_buf(&mut buf, &mut tokens, i); - if *letter == '$' && text_length > i && text.chars().nth(i + 1).unwrap() == '(' { - tokens.push(Token { token: Tokens::SubStart, start: i, end: i+1 }); - skipper = 1; - buf_add = false; - } else { - let (mut skippers, mut token) = read_var_ahead(i, &text)?; - match token.token { - 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 + 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") - } - tokens.push(token); - skipper = skippers; - buf_add = false; - } - }, - ';' | '\r' | '\n' => if !escape_active && !quote_active && !double_quote_active { - save_buf(&mut buf, &mut tokens, i); - tokens.push(Token { token: Tokens::CommandEnd(*letter), start: i, end: i }); - let mut x = 0; - while x < text.len() - 1 && matches!(text.chars().nth(x).unwrap(), '\n' | '\r' | ';' | ' ') { - x += 1; - } - if x > 0 { - skipper = x - 1; - } - buf_add = false; - }, - '&' => if !escape_active && !quote_active && !double_quote_active { - save_buf(&mut buf, &mut tokens, i); - if i + 1 < text.len() && text.chars().nth(i+1).unwrap() == '&' { - tokens.push(Token { token: Tokens::And, start: i, end: i+1 }); - skipper = 1; - } else { - tokens.push(Token { token: Tokens::JobCommandEnd, start: i , end: i }); - } - buf_add = false; - }, - '|' => if !escape_active && !quote_active && !double_quote_active { - save_buf(&mut buf, &mut tokens, i); - if i + 1 < text.len() && text.chars().nth(i+1).unwrap() == '|' { - tokens.push(Token { token: Tokens::Or, start: i, end: i+1 }); - skipper = 1; - } else { - tokens.push(Token { token: Tokens::RedirectInto, start: i, end: i }); - } - buf_add = false; - }, - ' ' => if !escape_active && !quote_active && !double_quote_active { - save_buf(&mut buf, &mut tokens, i); - tokens.push(Token { token: Tokens::Space, start: i, end: i }); - let mut x = i; - while text.chars().nth(x).unwrap() == ' ' { - x += 1; - } - skipper = x - i - 1; - buf_add = false; - }, - '(' => if !quote_active && !double_quote_active && !escape_active { - save_buf(&mut buf, &mut tokens, i); - tokens.push(Token { token: Tokens::ParenthesisStart, start: i, end: i }); - buf_add = false; - } - ')' => if !quote_active && !double_quote_active && !escape_active { - save_buf(&mut buf, &mut tokens, i); - tokens.push(Token { token: Tokens::ParenthesisEnd, start: i, end: i }); - buf_add = false; - }, - '[' => if !quote_active && !double_quote_active && !escape_active { - save_buf(&mut buf, &mut tokens, i); - tokens.push(Token { token: Tokens::ArrayStart, start: i, end: i }); - buf_add = false; - }, - ']' => if !quote_active && !double_quote_active && !escape_active { - save_buf(&mut buf, &mut tokens, i); - tokens.push(Token { token: Tokens::ArrayEnd, start: i, end: i }); - buf_add = false; - }, - '\\' => if !escape_active { - escape_active = true; - buf_add = false; - } else { - escape_active = false; - }, - '=' => if !escape_active && !quote_active && !double_quote_active { - save_buf(&mut buf, &mut tokens, i); - tokens.push(Token { token: Tokens::ExportSet, start: i, end: i }); - buf_add = false; - }, - '#' => if !escape_active && !quote_active && !double_quote_active { - save_buf(&mut buf, &mut tokens, i); - buf_add = false; - let mut x = 0; - while x + i + 1 < text.len() && text.chars().nth(x + i + 1).unwrap() != '\n' { - x += 1; - } - skipper = x; - } - _ => {} - } - if *letter != '\\' { escape_active = false; } - if buf_add { - buf.push(*letter); - } - } - save_buf(&mut buf, &mut tokens, text.len()); - - Ok(tokens) -} diff --git a/src/parser/vars.rs b/src/parser/vars.rs deleted file mode 100644 index 1f96ea9..0000000 --- a/src/parser/vars.rs +++ /dev/null @@ -1,393 +0,0 @@ -use std::collections::HashMap; -use std::fmt::{Debug, Display, Formatter}; -use std::fs::File; -use std::io::{Read, Write}; -use std::process::Stdio; -use std::sync::Arc; -use anyhow::{bail, Result}; -use os_pipe::{PipeReader, PipeWriter}; -use crate::parser::ast::FunctionDefinitionExpression; - -#[derive(Debug, Clone)] -pub enum Variable { - String(String), - I32(i32), - I64(i64), - I128(i128), - U32(u32), - U64(u64), - U128(u128), - F32(f32), - F64(f64), - HMap(HashMap), - Array(Vec), - Bool(bool) -} - -impl Display for Variable { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", match self { - Variable::String(var) => { - var.clone() - }, - Variable::I32(num) => num.to_string(), - Variable::I64(num) => num.to_string(), - Variable::I128(num) => num.to_string(), - Variable::U32(num) => num.to_string(), - Variable::U64(num) => num.to_string(), - 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]") - }, - Variable::Array(vars) => { - let len = vars.len(); - if len == 1 { - return match vars.get(0) { - Some(var) => write!(f, "{}", var), - None => write!(f, "[]") - } - } - let mut str = String::new(); - for (i, var) in vars.iter().enumerate() { - str += &*var.clone().to_string(); - if i < len - 1 { - str += " "; - } - } - str - } - }) - } -} - -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 { - Variable::HMap(map) => { - match index { - Variable::String(key) => { - match map.get(key) { - Some(val) => Ok(val), - None => bail!("Key not found") - } - } - _ => bail!("Cannot index with non-string") - } - }, - Variable::Array(arr) => { - match index { - Variable::I32(idx) => { - match arr.get(*idx as usize) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - Variable::I64(idx) => { - match arr.get(*idx as usize) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - Variable::I128(idx) => { - match arr.get(*idx as usize) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - Variable::F32(idx) => { - match arr.get(*idx as usize) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - Variable::F64(idx) => { - match arr.get(*idx as usize) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - Variable::U32(idx) => { - match arr.get(*idx as usize) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - Variable::U64(idx) => { - match arr.get(*idx as usize) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - Variable::U128(idx) => { - match arr.get(*idx as usize) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - Variable::String(idx) => { - match arr.get(idx.parse::()?) { - Some(val) => Ok(val), - None => bail!("Index out of bounds") - } - } - _ => bail!("Cannot index with non-integer") - } - }, - _ => bail!("Cannot index unsupported types") - } - } -} - -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) -} - - -trait TryClone { - fn try_clone(&self) -> Result; -} - -#[derive(Debug)] -pub enum WriterOverride { - Pipe(PipeWriter), - File(File) -} -#[derive(Debug)] -pub enum ReaderOverride { - Pipe(PipeReader), - File(File) -} - -impl TryClone for WriterOverride { - fn try_clone(&self) -> Result { - Ok(match self { - WriterOverride::Pipe(pipe) => WriterOverride::Pipe(pipe.try_clone()?), - WriterOverride::File(file) => WriterOverride::File(file.try_clone()?) - }) - } -} -impl TryClone for ReaderOverride { - fn try_clone(&self) -> Result { - Ok(match self { - ReaderOverride::Pipe(pipe) => ReaderOverride::Pipe(pipe.try_clone()?), - ReaderOverride::File(file) => ReaderOverride::File(file.try_clone()?) - }) - } -} -impl From for Stdio { - fn from(value: WriterOverride) -> Self { - match value { - WriterOverride::Pipe(pipe) => pipe.into(), - WriterOverride::File(file) => file.into() - } - } -} -impl From for Stdio { - fn from(value: ReaderOverride) -> Self { - match value { - ReaderOverride::Pipe(pipe) => pipe.into(), - ReaderOverride::File(file) => file.into() - } - } -} - -pub struct Overrides { - pub stdin: Option, - pub stdout: Option, - pub stderr: Option -} - -#[derive(Debug)] -pub struct Scope { - /// list of variables - pub vars: HashMap, - /// list of functions - pub func: HashMap, - /// list of file descriptors, to be closed when the scope is left - pub fd: Vec, - pub stdin_override: Option, - pub stdout_override: Option, - pub stderr_override: Option -} - -#[derive(Debug)] -pub struct Context { - pub scopes: Vec, - /// 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 -} - -impl Context { - pub fn new() -> 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(&mut self) -> Option { - self.scopes.pop() - } - pub fn add_scope(&mut self) { - let scope = Scope { - func: HashMap::new(), - vars: HashMap::new(), - fd: Vec::new(), - stdin_override: None, - stdout_override: None, - stderr_override: None - }; - self.scopes.push(scope); - } - - 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); - match val { - None => {} - Some(val) => { - return Some(val); - } - } - } - None - } - - pub fn get_last_exit_code(&mut self) -> Option { - let var = self.get_var("?"); - match var { - Some(Variable::I32(int)) => Some(*int), - _ => None, - } - } - - 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(&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(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 func = &mut self.scopes.last_mut().unwrap().func; - func.insert(key, val); - } - - /// Gets relevant overrides. Should only be used before running a command, as it will clone all pipes - pub fn get_overrides(&self) -> Result { - let mut overrides = Overrides { - stdin: None, - stdout: None, - stderr: None - }; - - for scope in self.scopes.iter().rev() { - match overrides.stdin { - Some(_) => {} - None => { - match &scope.stdin_override { - Some(stdin) => overrides.stdin = Some(stdin.try_clone()?), - None => {} - } - } - } - match overrides.stderr { - Some(_) => {} - None => { - match &scope.stderr_override { - Some(stderr) => overrides.stderr = Some(stderr.try_clone()?), - None => {} - } - } - } - match overrides.stdout { - Some(_) => {} - None => { - match &scope.stdout_override { - Some(stdout) => overrides.stdout = Some(stdout.try_clone()?), - None => {} - } - } - } - } - - Ok(overrides) - } -} diff --git a/test/base_if.rush b/test/base_if.rush index af4f3c2..a784d55 100644 --- a/test/base_if.rush +++ b/test/base_if.rush @@ -1,3 +1,3 @@ -if true +if true { echo condition true -end \ No newline at end of file +} \ No newline at end of file diff --git a/test/builtins.rush b/test/builtins.rush index 6ec704e..ccbb27b 100644 --- a/test/builtins.rush +++ b/test/builtins.rush @@ -1 +1 @@ -test $trim("test ") = "test" \ No newline at end of file +test (trim "test ") = "test" \ No newline at end of file diff --git a/test/if_else.rush b/test/if_else.rush index 7d357fe..80e4f03 100644 --- a/test/if_else.rush +++ b/test/if_else.rush @@ -1,11 +1,13 @@ -if true - echo condition true -else - echo else -end +if true { -if false +} else { + echo condition true + echo else +} + +if false { echo condition false -else if true + +} else if true { echo condition else true -end \ No newline at end of file +} \ No newline at end of file diff --git a/test/var.rush b/test/var.rush index 0097dd3..50ee77e 100644 --- a/test/var.rush +++ b/test/var.rush @@ -1,7 +1,7 @@ let test = val echo var $test let test = $test -echo var2 ${test} +echo var2 f"$test" echo last exit code $? false echo last false exit code $? \ No newline at end of file diff --git a/test/while.rush b/test/while.rush index ab0a9e2..5f13441 100644 --- a/test/while.rush +++ b/test/while.rush @@ -1,4 +1,4 @@ -while true +while true { echo loop break -end \ No newline at end of file +} \ No newline at end of file