From aa33fb90ea336a6e93132b214205b7c28b98ec77 Mon Sep 17 00:00:00 2001 From: Daniel Bulant Date: Sun, 20 Feb 2022 18:39:23 +0100 Subject: [PATCH] various improvements, basic (currently failing) tests, improved lexer --- Cargo.lock | 88 +++++++++++++++ Cargo.toml | 5 +- src/main.rs | 42 ++++++-- src/parser/ast.rs | 99 ++++++++++++----- src/parser/exec.rs | 251 +++++++++++++++++++++++++++++-------------- src/parser/mod.rs | 4 +- src/parser/tokens.rs | 153 +++----------------------- src/parser/vars.rs | 30 ++++-- test/if.rush | 20 ++++ test/simple.rush | 36 +------ test/var.rush | 7 ++ test/while.rush | 4 + 12 files changed, 439 insertions(+), 300 deletions(-) create mode 100644 test/if.rush create mode 100644 test/var.rush create mode 100644 test/while.rush diff --git a/Cargo.lock b/Cargo.lock index 4f362c8..ff95f77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,29 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "anyhow" version = "1.0.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a99269dff3bc004caa411f38845c20303f1e393ca2bd6581576fa3a7f59577d" +dependencies = [ + "backtrace", +] [[package]] name = "arrayvec" @@ -14,12 +32,45 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "filedescriptor" version = "0.8.1" @@ -31,18 +82,49 @@ dependencies = [ "winapi", ] +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + [[package]] name = "libc" version = "0.2.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + [[package]] name = "numtoa" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +[[package]] +name = "object" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +dependencies = [ + "memchr", +] + [[package]] name = "proc-macro2" version = "1.0.32" @@ -89,6 +171,12 @@ dependencies = [ "utf8-chars", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "syn" version = "1.0.82" diff --git a/Cargo.toml b/Cargo.toml index 7d01f37..a85958d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,7 @@ edition = "2018" utf8-chars = "1.0.0" termion = "1.5.6" filedescriptor = "0.8.1" -anyhow = "1.0.54" \ No newline at end of file + +[dependencies.anyhow] +version = "1.0.54" +features = ["backtrace"] diff --git a/src/main.rs b/src/main.rs index 7a327c5..b5166e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,11 +59,7 @@ impl Shell { fn new() -> Shell { return Shell { term: Term::new(), - ctx: parser::vars::Context { - scopes: Vec::new(), - fd: Vec::new(), - exports: HashMap::new() - }, + ctx: parser::vars::Context::new() }; } @@ -76,7 +72,6 @@ impl Shell { fn main() { let mut shell = Shell::new(); - shell.ctx.add_scope(true); loop { print!("$: "); io::stdout().flush().unwrap(); @@ -90,6 +85,41 @@ fn main() { } } +#[cfg(test)] +mod test { + use std::fs::File; + use std::io::BufReader; + use std::path::Path; + use crate::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") + } + + #[test] + fn var() -> Result<()> { + load_and_run("test/var.rush") + } + + #[test] + fn if_expr() -> Result<()> { + load_and_run("test/if.rush") + } + + #[test] + fn while_expr() -> Result<()> { + load_and_run("test/while.rush") + } +} + fn editor() -> Shell { let stdin = io::stdin(); let mut stdout = io::stdout().into_raw_mode().unwrap(); diff --git a/src/parser/ast.rs b/src/parser/ast.rs index abd8417..aa22e7b 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -1,9 +1,10 @@ use crate::parser::tokens::Tokens; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; #[derive(Debug)] pub struct LetExpression { pub key: Box, + pub vartype: Option, pub value: Box } @@ -22,7 +23,8 @@ pub struct OrExpression { #[derive(Debug)] pub struct IfExpression { pub condition: Box, - pub contents: Vec + pub contents: Vec, + pub else_contents: Vec } #[derive(Debug)] @@ -62,9 +64,16 @@ pub enum Value { Values(Vec) } +#[derive(Debug)] +pub struct FunctionVariable { + pub name: String, + pub vartype: Option +} + #[derive(Debug)] pub struct FunctionDefinitionExpression { - pub args: Vec, + pub name: String, + pub args: Vec, pub body: Box } @@ -92,6 +101,11 @@ pub enum CommandValue { Var(String, Value) } +#[derive(Debug)] +pub struct BreakExpression { + pub num: Box +} + #[derive(Debug)] pub enum Expression { LetExpression(LetExpression), @@ -106,7 +120,8 @@ pub enum Expression { FileSourceExpression(FileSourceExpression), Expressions(Vec), OrExpression(OrExpression), - AndExpression(AndExpression) + AndExpression(AndExpression), + BreakExpression(BreakExpression) } #[derive(Debug)] @@ -190,7 +205,7 @@ impl Tree { self.inc(); // ???? self.inc(); let value = Box::new(self.get_value(end)?); - Ok(Expression::LetExpression(LetExpression { key, value })) + Ok(Expression::LetExpression(LetExpression { key, vartype: None, value })) } fn parse_read(&mut self, target: Option, _end: usize) -> Result { @@ -241,28 +256,55 @@ impl Tree { Ok(Expression::FileTargetExpression(FileTargetExpression { source, target })) } - fn parse_function(&self, _end: usize) -> Result { + fn parse_function(&mut self, _end: usize) -> Result { bail!("Functions not yet implemented") } - fn parse_array_func(&self, _str: String, _end: usize) -> Result { + fn parse_array_func(&mut self, _str: String, _end: usize) -> Result { bail!("Array functions not yet implemented"); } - fn parse_string_func(&self, _str: String, _end: usize) -> Result { + fn parse_string_func(&mut self, _str: String, _end: usize) -> Result { bail!("Array functions not yet implemented"); } - fn parse_for(&self, _end: usize) -> Result { + fn parse_for(&mut self, _end: usize) -> Result { bail!("For loop not yet implemented"); } - fn parse_if(&self, _end: usize) -> Result { - bail!("If not yet implemented"); + fn parse_if(&mut self, end: usize) -> Result { + self.inc(); + let condition = self.get_expression(end).with_context(|| "Error getting condition for if expression")?; + dbg!(&condition); + let mut contents = Vec::new(); + loop { + match self.get_next_token() { + Tokens::End => break, + Tokens::Space => { self.inc(); }, + _ => contents.push(self.get_expression(end).with_context(|| "Error getting contents for if expression")?) + }; + } + let mut else_contents = Vec::new(); + Ok(IfExpression { condition: Box::new(condition), contents, else_contents }) } - fn parse_while(&self, _end: usize) -> Result { - bail!("While not yet implemented"); + fn parse_while(&mut self, end: usize) -> Result { + self.inc(); + let condition = self.get_expression(end).with_context(|| "Error getting condition for while expression")?; + dbg!(&condition); + dbg!(self.i); + let mut contents = Vec::new(); + loop { + let token = self.get_next_token(); + dbg!(token); + match token { + Tokens::End => break, + Tokens::Space => { self.inc(); }, + _ => contents.push(self.get_expression(end).with_context(|| "Error getting contents for while expression")?) + }; + dbg!(&contents); + } + Ok(WhileExpression { condition: Box::new(condition), contents }) } fn parse_sub(&mut self, end: usize) -> Result> { @@ -319,7 +361,7 @@ impl Tree { } // self.inc(); if lvl != 0 { - panic!("Parenthesis do not match"); + bail!("Parenthesis do not match"); } dbg!(&self, len); let val = Value::Expressions(self.parse_sub(self.i + len)?); @@ -349,6 +391,7 @@ impl Tree { }, 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 } @@ -367,7 +410,7 @@ impl Tree { match token { Tokens::Space => {self.inc();}, Tokens::CommandEnd(_) => { if matches!(expr, Some(_)) { break }; self.inc();}, - Tokens::Literal(t) => if matches!(expr, Some(_)) { + 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)?); @@ -416,7 +459,7 @@ impl Tree { _ => expr = Some(self.parse_call(end)?) }, Tokens::Else => bail!("Unexpected token ELSE"), - Tokens::End => bail!("Unexpected token END"), + Tokens::End => bail!("Unexpected token END\nCurrent expression:{:?}", expr), Tokens::For => match expr { Some(_) => bail!("Commands must be ended properly"), None => expr = Some(Expression::ForExpression(self.parse_for(end)?)), @@ -447,6 +490,12 @@ impl Tree { expr = Some(Expression::OrExpression(OrExpression { first: Box::new(expr.unwrap()), second: Box::new(self.get_expression(end)?) })); } }, + Tokens::Break => match expr { + None => { + expr = Some(Expression::BreakExpression(BreakExpression { num: Box::new(self.get_value(end)?)})); + }, + Some(_) => bail!("Unexpected break") + } Tokens::JobCommandEnd => bail!("Jobs not yet implemented") } if self.i >= end - 1 { break } @@ -462,21 +511,23 @@ impl Tree { self.i += 1; self } - fn get_current_token(&self) -> &Tokens { - self.tokens.get(self.i).unwrap() - } - fn get_next_token(&self) -> &Tokens { - self.tokens.get(self.i + 1).unwrap() - } + fn get_current_token(&self) -> &Tokens { self.tokens.get(self.i).unwrap() } + fn get_next_token(&self) -> &Tokens { self.tokens.get(self.i + 1).unwrap() } } pub fn build_tree(tokens: Vec) -> Result> { let mut expressions: Vec = Vec::new(); let mut tree = Tree { tokens, i: 0 }; loop { - if tree.i == tree.tokens.len() - 1 { break; } + if tree.i >= tree.tokens.len() - 1 { break; } let val = tree.get_expression(tree.tokens.len()); - expressions.push(val?); + match val { + Ok(val) => expressions.push(val), + Err(error) => { + dbg!(tree); + return Err(error); + } + } } Ok(expressions) } diff --git a/src/parser/exec.rs b/src/parser/exec.rs index 197b5b1..c45ce2c 100644 --- a/src/parser/exec.rs +++ b/src/parser/exec.rs @@ -1,16 +1,17 @@ use std::fs::File; use std::process::{Child, Command, Stdio}; use std::io; -use crate::parser::ast::{AndExpression, CommandValue, Expression, FileSourceExpression, FileTargetExpression, LetExpression, OrExpression, RedirectTargetExpression, Value}; +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 anyhow::{Result, bail, Context as AnyhowContext}; trait ExecExpression { - fn exec(self, ctx: &mut vars::Context) -> Option; + fn exec(&mut self, ctx: &mut vars::Context) -> Result>; } trait GetValue { - fn get(self, ctx: &mut vars::Context) -> Variable; + fn get(&mut self, ctx: &mut vars::Context) -> Result; } struct ExecResult { @@ -97,123 +98,200 @@ impl ExecResult { } impl GetValue for CommandValue { - fn get(self, ctx: &mut vars::Context) -> Variable { + fn get(self: &mut CommandValue, ctx: &mut vars::Context) -> Result { match self { CommandValue::Value(val) => val.get(ctx), - CommandValue::Var(_, _) => panic!("Broken executor") + CommandValue::Var(_, _) => bail!("Broken executor") } } } impl GetValue for Value { - fn get(self, ctx: &mut vars::Context) -> Variable { + fn get(self: &mut Value, ctx: &mut vars::Context) -> Result { match self { Value::Literal(str) => { - Variable::String(str) + Ok(Variable::String(str.clone())) }, - Value::Variable(str) => ctx.get_var(&str).unwrap_or(&mut Variable::String(String::from(""))).clone(), - Value::ArrayVariable(str) => ctx.get_var(&str).unwrap_or(&mut Variable::Array(Vec::new())).clone(), - Value::ArrayFunction(_) => panic!("Not implemented yet"), - Value::StringFunction(_) => panic!("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::ArrayFunction(_) => todo!("Not implemented yet"), + Value::StringFunction(_) => todo!("Not implemented yet"), Value::Expressions(expressions) => { let mut out = String::new(); - ctx.add_scope(true); - for mut expr in expressions { - let res = expr.exec(ctx); + ctx.add_scope(); + for expr in expressions { + let res = expr.exec(ctx)?; match res { None => {}, Some(mut cmd) => { - out += &*String::from_utf8_lossy(&cmd.output().expect("Failed to read output of command").stdout); + out += &*String::from_utf8_lossy(&cmd.output().with_context(|| "Failed to read output of command")?.stdout); } } } ctx.pop_scope(); - Variable::String(out) + Ok(Variable::String(out)) }, Value::Values(vec) => { let mut out = Vec::new(); - for mut val in vec { - out.push(val.get(ctx)); + for val in vec { + out.push(val.get(ctx)?); } - Variable::Array(out) + Ok(Variable::Array(out)) } } } } impl ExecExpression for Expression { - fn exec(self, ctx: &mut vars::Context) -> Option { + fn exec(self: &mut Expression, ctx: &mut vars::Context) -> Result> { match self { Expression::LetExpression(expr) => expr.exec(ctx), Expression::Command(expr) => expr.exec(ctx), - Expression::JobCommand(_) => todo!(), - Expression::Function(_) => todo!(), - Expression::IfExpression(_) => todo!(), - Expression::WhileExpression(_) => todo!(), - Expression::ForExpression(_) => todo!(), + Expression::JobCommand(_) => todo!("Jobs"), + Expression::Function(_) => todo!("Function definition"), + Expression::IfExpression(expr) => expr.exec(ctx), + Expression::WhileExpression(expr) => expr.exec(ctx), + Expression::ForExpression(_) => todo!("For expression"), 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::AndExpression(expr) => expr.exec(ctx), + Expression::BreakExpression(expr) => expr.exec(ctx) } } } +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)?; + let num: u16 = val.to_string().parse()?; + ctx.break_num = num; + Ok(None) + } +} + +impl ExecExpression for WhileExpression { + fn exec(self: &mut WhileExpression, ctx: &mut vars::Context) -> Result> { + if ctx.break_num > 0 { ctx.break_num -= 1; return Ok(None) } + let mut condition = match self.condition.exec(ctx)? { + None => bail!("Invalid while expression"), + Some(cmd) => cmd + }; + ctx.add_scope(); + let mut res; + loop { + match condition.spawn() { + Result::Err(_) => { + res = Some(condition); + break; + }, + Result::Ok(mut child) => { + if !child.wait()?.success() { + res = Some(condition); + break + } else { + res = self.contents.exec(ctx)? + } + } + }; + if ctx.break_num > 0 { + ctx.break_num -= 1; + break; + } + } + ctx.pop_scope(); + + Ok(res) + } +} + +impl ExecExpression for IfExpression { + fn exec(self: &mut IfExpression, ctx: &mut vars::Context) -> Result> { + if ctx.break_num > 0 { return Ok(None) } + let mut condition = match self.condition.exec(ctx)? { + None => bail!("Invalid IF expression"), + Some(cmd) => cmd + }; + let res = match condition.spawn() { + Result::Err(_) => { + Some(condition) + }, + Result::Ok(mut res) => { + if !res.wait()?.success() { + Some(condition) + } else { + ctx.add_scope(); + let res = self.contents.exec(ctx)?; + ctx.pop_scope(); + res + } + } + }; + + Ok(res) + } +} + impl ExecExpression for LetExpression { - fn exec(mut self, ctx: &mut vars::Context) -> Option { - let key = self.key.get(ctx); - let val = self.value.get(ctx); + fn exec(self: &mut LetExpression, ctx: &mut vars::Context) -> Result> { + if ctx.break_num > 0 { return Ok(None) } + let key = self.key.get(ctx)?; + let val = self.value.get(ctx)?; ctx.set_var(key.to_string(), val); - None + Ok(None) } } impl ExecExpression for Vec { - fn exec(mut self, ctx: &mut vars::Context) -> Option { - if self.len() == 0 { panic!("Command with 0 length"); } + 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"); } let mut first = self.remove(0); - let command_name = first.get(ctx).to_string(); + let command_name = first.get(ctx)?.to_string(); let mut cmd = Command::new(command_name); - for mut value in self { - cmd.arg(value.get(ctx).to_string()); + for value in self { + cmd.arg(value.get(ctx)?.to_string()); } - Some(cmd) + Ok(Some(cmd)) } } impl ExecExpression for RedirectTargetExpression { - fn exec(mut self, ctx: &mut vars::Context) -> Option { - let mut src = self.source.exec(ctx).unwrap(); - let mut target = self.target.exec(ctx).unwrap(); + fn exec(self: &mut RedirectTargetExpression, ctx: &mut vars::Context) -> Result> { + if ctx.break_num > 0 { return Ok(None) } + let mut src = self.source.exec(ctx)?.unwrap(); + let mut target = self.target.exec(ctx)?.unwrap(); src.stdout(Stdio::piped()); match src.spawn() { Result::Err(e) => { println!("Error executing: {}", e)}, - Result::Ok(mut res) => { + Result::Ok(res) => { target.stdin(res.stdout.unwrap()); } } - Some(target) + Ok(Some(target)) } } impl ExecExpression for FileTargetExpression { - fn exec(mut self, ctx: &mut vars::Context) -> Option { - let mut src = self.source; - let mut target = self.target.get(ctx); - let mut src = match src { - Some(expr) => expr.exec(ctx), + fn exec(self: &mut FileTargetExpression, ctx: &mut vars::Context) -> Result> { + if ctx.break_num > 0 { return Ok(None) } + let src = &mut self.source; + let target = self.target.get(ctx)?; + let src = match src { + Some(expr) => expr.exec(ctx)?, None => { - todo!(); + todo!("Redirect without target file"); } }; let command; match src { Some(mut cmd) => { cmd.stdout(Stdio::piped()); - let mut file = File::create(target.to_string()); + let file = File::create(target.to_string()); match file { Result::Err(e) => println!("Error: {}", e), Result::Ok(mut file) => { @@ -222,102 +300,107 @@ impl ExecExpression for FileTargetExpression { println!("Error executing command: {}", e); }, Result::Ok(res) => { - io::copy(&mut res.stdout.unwrap(), &mut file); + io::copy(&mut res.stdout.unwrap(), &mut file)?; } } } } command = cmd; }, - None => { panic!("Invalid command provided for file target"); } + None => { bail!("Invalid command provided for file target"); } }; - Some(command) + Ok(Some(command)) } } impl ExecExpression for FileSourceExpression { - fn exec(self, ctx: &mut Context) -> Option { - let mut source = self.source.get(ctx).to_string(); - let mut target = self.target; - let mut target = match target { - Some(expr) => expr.exec(ctx), + fn exec(self: &mut FileSourceExpression, ctx: &mut Context) -> Result> { + if ctx.break_num > 0 { return Ok(None) } + let source = self.source.get(ctx)?.to_string(); + let target = &mut self.target; + let target = match target { + Some(expr) => expr.exec(ctx)?, None => { Some(Command::new("less")) } }; let mut command = match target { - None => { panic!("Invalid command") }, + None => { bail!("Invalid command") }, Some(cmd) => cmd }; - let mut source = match File::open(source) { - Result::Err(e) => panic!("Cannot open file: {}", e), + let source = match File::open(source) { + Result::Err(e) => bail!("Cannot open file: {}", e), Result::Ok(file) => file }; command.stdin(source); - Some(command) + Ok(Some(command)) } } impl ExecExpression for Vec { - fn exec(self, ctx: &mut Context) -> Option { + fn exec(self: &mut Vec, ctx: &mut Context) -> Result> { + if ctx.break_num > 0 { return Ok(None) } let mut last = None; for expr in self { - last = expr.exec(ctx); + last = expr.exec(ctx)?; + if ctx.break_num > 0 { return Ok(last) } } - last + Ok(last) } } impl ExecExpression for OrExpression { - fn exec(self, ctx: &mut Context) -> Option { - let mut first = match self.first.exec(ctx) { - None => panic!("Invalid OR expression"), + fn exec(self: &mut OrExpression, ctx: &mut Context) -> Result> { + if ctx.break_num > 0 { return Ok(None) } + let mut first = match self.first.exec(ctx)? { + None => bail!("Invalid OR expression"), Some(cmd) => cmd }; - let mut res = match first.spawn() { - Result::Err(e) => { - self.second.exec(ctx) + let res = match first.spawn() { + Result::Err(_) => { + self.second.exec(ctx)? }, Result::Ok(mut res) => { - if res.wait().unwrap().success() { + if res.wait()?.success() { Some(first) } else { - self.second.exec(ctx) + self.second.exec(ctx)? } } }; - res + Ok(res) } } impl ExecExpression for AndExpression { - fn exec(self, ctx: &mut Context) -> Option { - let mut first = match self.first.exec(ctx) { - None => panic!("Invalid AND expression"), + fn exec(self: &mut AndExpression, ctx: &mut Context) -> Result> { + if ctx.break_num > 0 { return Ok(None) } + let mut first = match self.first.exec(ctx)? { + None => bail!("Invalid AND expression"), Some(cmd) => cmd }; - let mut res = match first.spawn() { - Result::Err(e) => { + let res = match first.spawn() { + Result::Err(_) => { Some(first) }, Result::Ok(mut res) => { - if !res.wait().unwrap().success() { + if !res.wait()?.success() { Some(first) } else { - self.second.exec(ctx) + self.second.exec(ctx)? } } }; - res + Ok(res) } } -pub fn exec_tree(tree: Vec, ctx: &mut vars::Context) { +pub fn exec_tree(tree: Vec, ctx: &mut vars::Context) -> Result<()> { for mut expression in tree { - let mut cmd = expression.exec(ctx); + let cmd = expression.exec(ctx)?; match cmd { None => {}, Some(mut cmd) => match cmd.spawn() { @@ -325,10 +408,12 @@ pub fn exec_tree(tree: Vec, ctx: &mut vars::Context) { println!("Error executing: {}", e); }, Result::Ok(mut res) => { - let out = res.wait().unwrap(); + let out = res.wait()?; ctx.set_var(String::from("?"), Variable::I32(out.code().unwrap_or(1))); } } } + if ctx.break_num > 0 { bail!("Too many break statements") } } + Ok(()) } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1181bb1..56f6205 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -13,11 +13,11 @@ pub fn exec(reader: &mut dyn std::io::BufRead, ctx: &mut vars::Context) -> Resul dbg!(&tokens); - let expressions = build_tree(tokens); + let expressions = build_tree(tokens)?; dbg!(&expressions); - exec_tree(expressions?, ctx); + exec_tree(expressions, ctx)?; Ok(()) } diff --git a/src/parser/tokens.rs b/src/parser/tokens.rs index 7a2d65d..527895a 100644 --- a/src/parser/tokens.rs +++ b/src/parser/tokens.rs @@ -25,6 +25,7 @@ pub enum Tokens { FileWrite, And, Or, + Break, JobCommandEnd } @@ -44,8 +45,9 @@ impl Tokens { ">" => Tokens::FileWrite, "<" => Tokens::FileRead, "|" => Tokens::RedirectInto, - "\n" | ";" => Tokens::CommandEnd(str.chars().nth(0).unwrap()), + "\r\n" | "\n" | ";" => Tokens::CommandEnd(str.chars().nth(0).unwrap()), "=" => Tokens::ExportSet, + "break" => Tokens::Break, _ => Tokens::Literal(str) } } @@ -75,6 +77,7 @@ impl Tokens { Tokens::FileWrite => ">".to_string(), Tokens::And => "&&".to_string(), Tokens::Or => "||".to_string(), + Tokens::Break => "break".to_string(), Tokens::JobCommandEnd => "&".to_string() } } @@ -114,12 +117,6 @@ fn read_var_ahead(i: usize, text: &String) -> (usize, Tokens) { (x - i - 1, token) } -fn check_keyword(text: &String, i: usize, keyword: &str) -> bool { - let text_length = text.len(); - if text_length < i + keyword.len() + 1 { return false; } - text.chars().skip(i).take(keyword.len()).collect::() == keyword -} - pub fn tokenize(reader: &mut dyn std::io::BufRead) -> io::Result> { let mut quote_active = false; let mut double_quote_active = false; @@ -130,6 +127,10 @@ pub fn tokenize(reader: &mut dyn std::io::BufRead) -> io::Result> { let mut tokens: Vec = Vec::new(); + fn save_buf(buf: &mut String, tokens: &mut Vec) { + if buf.len() > 0 { tokens.push(Tokens::detect(std::mem::take(buf))) } + } + let mut buf = String::new(); let mut skipper = 0; for i in 0..text_length { @@ -143,10 +144,7 @@ pub fn tokenize(reader: &mut dyn std::io::BufRead) -> io::Result> { '"' => 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 { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } + save_buf(&mut buf, &mut tokens); if text_length > i && text.chars().nth(i + 1).unwrap() == '(' { tokens.push(Tokens::SubStart); skipper = 1; @@ -172,10 +170,7 @@ pub fn tokenize(reader: &mut dyn std::io::BufRead) -> io::Result> { } }, ' ' => if !escape_active && !quote_active && !double_quote_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } + save_buf(&mut buf, &mut tokens); tokens.push(Tokens::Space); let mut x = i; while text.chars().nth(x).unwrap() == ' ' { @@ -185,134 +180,15 @@ pub fn tokenize(reader: &mut dyn std::io::BufRead) -> io::Result> { buf_add = false; }, '(' => if !quote_active && !double_quote_active && !escape_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } + save_buf(&mut buf, &mut tokens); tokens.push(Tokens::ParenthesisStart); buf_add = false; } ')' => if !quote_active && !double_quote_active && !escape_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } + save_buf(&mut buf, &mut tokens); tokens.push(Tokens::ParenthesisEnd); buf_add = false; }, - 'i' => if !quote_active && !double_quote_active && !escape_active { - if check_keyword(&text, i, "if") { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - skipper = 2; - tokens.push(Tokens::If); - buf_add = false; - } - }, - 'e' => if !quote_active && !double_quote_active && !escape_active { - if check_keyword(&text, i, "else") { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - skipper = 4; - tokens.push(Tokens::Else); - buf_add = false; - } else if check_keyword(&text, i , "end") { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - skipper = 3; - tokens.push(Tokens::End); - buf_add = false; - } - }, - 'l' => if !quote_active && !double_quote_active && !escape_active { - if check_keyword(&text, i, "let") { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - skipper = 3; - tokens.push(Tokens::Let); - buf_add = false; - } - }, - 'w' => if !quote_active && !double_quote_active && !escape_active { - if check_keyword(&text, i, "while") { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - skipper = 5; - tokens.push(Tokens::While); - buf_add = false; - } - }, - 'f' => if !quote_active && !double_quote_active && !escape_active { - if check_keyword(&text, i, "for") { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - skipper = 3; - tokens.push(Tokens::If); - buf_add = false; - } - }, - '|' => if !escape_active && !quote_active && !double_quote_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - if check_keyword(&text, i, "||") { - skipper = 1; - tokens.push(Tokens::Or); - } else { - tokens.push(Tokens::RedirectInto); - } - buf_add = false; - }, - '&' => if !escape_active && !quote_active && !double_quote_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - if check_keyword(&text, i, "&&") { - skipper = 1; - tokens.push(Tokens::And); - } else { - tokens.push(Tokens::JobCommandEnd); - } - buf_add = false; - }, - '>' => if !escape_active && !quote_active && !double_quote_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - tokens.push(Tokens::FileWrite); - buf_add = false; - }, - '<' => if !escape_active && !quote_active && !double_quote_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - tokens.push(Tokens::FileRead); - buf_add = false; - }, - ';' | '\n' => if !escape_active && !quote_active && !double_quote_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } - tokens.push(Tokens::CommandEnd(letter.clone())); - buf_add = false; - }, '\\' => if !escape_active { escape_active = true; buf_add = false; @@ -320,10 +196,7 @@ pub fn tokenize(reader: &mut dyn std::io::BufRead) -> io::Result> { escape_active = false; }, '=' => if !escape_active && !quote_active && !double_quote_active { - if buf.len() > 0 { - tokens.push(Tokens::Literal(buf)); - buf = String::new(); - } + save_buf(&mut buf, &mut tokens); tokens.push(Tokens::ExportSet); buf_add = false; }, diff --git a/src/parser/vars.rs b/src/parser/vars.rs index ea46273..b726a26 100644 --- a/src/parser/vars.rs +++ b/src/parser/vars.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use anyhow::{bail, Result}; use crate::parser::ast::FunctionDefinitionExpression; #[derive(Debug, Clone)] @@ -50,43 +51,50 @@ impl Variable { } } - pub fn index(self: &Self) -> &Variable { + pub fn index(self: &Self, index: &Variable) -> Result<&Variable> { match self { - _ => panic!("Cannot index unsupported types") + _ => bail!("Cannot index unsupported types") } } } #[derive(Debug)] pub struct Scope { - pub active: bool, + /// list of variables pub vars: HashMap, - pub func: HashMap + /// list of functions + pub func: HashMap, + /// list of file descriptors, to be closed when the scope is left + pub fd: Vec } #[derive(Debug)] pub struct Context { pub scopes: Vec, pub exports: HashMap, - pub fd: Vec + pub break_num: u16, + pub continue_num: u16 } impl Context { pub fn new() -> Context { - Context { + let mut res = Context { scopes: Vec::new(), exports: HashMap::new(), - fd: Vec::new() - } + break_num: 0, + continue_num: 0 + }; + res.add_scope(); + res } pub fn pop_scope(self: &mut Self) -> Option { self.scopes.pop() } - pub fn add_scope(self: &mut Self, active: bool) { + pub fn add_scope(self: &mut Self) { let scope = Scope { - active, func: HashMap::new(), - vars: HashMap::new() + vars: HashMap::new(), + fd: Vec::new() }; self.scopes.push(scope); } diff --git a/test/if.rush b/test/if.rush new file mode 100644 index 0000000..23be651 --- /dev/null +++ b/test/if.rush @@ -0,0 +1,20 @@ + +if true + echo condition true +end + +if false + echo condition false +end + +if true + echo condition true +else + echo else +end + +if false + echo condition false +else if true + echo condition else true +end \ No newline at end of file diff --git a/test/simple.rush b/test/simple.rush index e78de8a..d846d06 100644 --- a/test/simple.rush +++ b/test/simple.rush @@ -3,37 +3,7 @@ echo test echo single; echo line -echo test > cat +echo test > cats +rm cats -grep echo < simple.rush - -let test = val -echo var $test -let test = $test -echo var2 ${test} -echo last exit code $? - -if true - echo condition true -end - -if false - echo condition false -end - -if true - echo condition true -else - echo else -end - -if false - echo condition false -else if true - echo condition else true -end - -while true - echo loop - break -end \ No newline at end of file +grep echo < test/simple.rush \ No newline at end of file diff --git a/test/var.rush b/test/var.rush new file mode 100644 index 0000000..0097dd3 --- /dev/null +++ b/test/var.rush @@ -0,0 +1,7 @@ +let test = val +echo var $test +let test = $test +echo var2 ${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 new file mode 100644 index 0000000..ab0a9e2 --- /dev/null +++ b/test/while.rush @@ -0,0 +1,4 @@ +while true + echo loop + break +end \ No newline at end of file