various improvements, basic (currently failing) tests, improved lexer

This commit is contained in:
Daniel Bulant 2022-02-20 18:39:23 +01:00
parent b43959fa79
commit aa33fb90ea
12 changed files with 439 additions and 300 deletions

88
Cargo.lock generated
View file

@ -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"

View file

@ -10,4 +10,7 @@ edition = "2018"
utf8-chars = "1.0.0"
termion = "1.5.6"
filedescriptor = "0.8.1"
anyhow = "1.0.54"
[dependencies.anyhow]
version = "1.0.54"
features = ["backtrace"]

View file

@ -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<P: AsRef<Path>>(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();

View file

@ -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<Value>,
pub vartype: Option<String>,
pub value: Box<Value>
}
@ -22,7 +23,8 @@ pub struct OrExpression {
#[derive(Debug)]
pub struct IfExpression {
pub condition: Box<Expression>,
pub contents: Vec<Expression>
pub contents: Vec<Expression>,
pub else_contents: Vec<Expression>
}
#[derive(Debug)]
@ -62,9 +64,16 @@ pub enum Value {
Values(Vec<Value>)
}
#[derive(Debug)]
pub struct FunctionVariable {
pub name: String,
pub vartype: Option<String>
}
#[derive(Debug)]
pub struct FunctionDefinitionExpression {
pub args: Vec<String>,
pub name: String,
pub args: Vec<FunctionVariable>,
pub body: Box<Expression>
}
@ -92,6 +101,11 @@ pub enum CommandValue {
Var(String, Value)
}
#[derive(Debug)]
pub struct BreakExpression {
pub num: Box<Value>
}
#[derive(Debug)]
pub enum Expression {
LetExpression(LetExpression),
@ -106,7 +120,8 @@ pub enum Expression {
FileSourceExpression(FileSourceExpression),
Expressions(Vec<Expression>),
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<Expression>, _end: usize) -> Result<Expression> {
@ -241,28 +256,55 @@ impl Tree {
Ok(Expression::FileTargetExpression(FileTargetExpression { source, target }))
}
fn parse_function(&self, _end: usize) -> Result<FunctionDefinitionExpression> {
fn parse_function(&mut self, _end: usize) -> Result<FunctionDefinitionExpression> {
bail!("Functions not yet implemented")
}
fn parse_array_func(&self, _str: String, _end: usize) -> Result<DefinedFunction> {
fn parse_array_func(&mut self, _str: String, _end: usize) -> Result<DefinedFunction> {
bail!("Array functions not yet implemented");
}
fn parse_string_func(&self, _str: String, _end: usize) -> Result<DefinedFunction> {
fn parse_string_func(&mut self, _str: String, _end: usize) -> Result<DefinedFunction> {
bail!("Array functions not yet implemented");
}
fn parse_for(&self, _end: usize) -> Result<ForExpression> {
fn parse_for(&mut self, _end: usize) -> Result<ForExpression> {
bail!("For loop not yet implemented");
}
fn parse_if(&self, _end: usize) -> Result<IfExpression> {
bail!("If not yet implemented");
fn parse_if(&mut self, end: usize) -> Result<IfExpression> {
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<WhileExpression> {
bail!("While not yet implemented");
fn parse_while(&mut self, end: usize) -> Result<WhileExpression> {
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<Vec<Expression>> {
@ -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<Tokens>) -> Result<Vec<Expression>> {
let mut expressions: Vec<Expression> = 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)
}

View file

@ -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<Command>;
fn exec(&mut self, ctx: &mut vars::Context) -> Result<Option<Command>>;
}
trait GetValue {
fn get(self, ctx: &mut vars::Context) -> Variable;
fn get(&mut self, ctx: &mut vars::Context) -> Result<Variable>;
}
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<Variable> {
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<Variable> {
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<Command> {
fn exec(self: &mut Expression, ctx: &mut vars::Context) -> Result<Option<Command>> {
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<Option<Command>> {
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<Option<Command>> {
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<Option<Command>> {
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<Command> {
let key = self.key.get(ctx);
let val = self.value.get(ctx);
fn exec(self: &mut LetExpression, ctx: &mut vars::Context) -> Result<Option<Command>> {
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<CommandValue> {
fn exec(mut self, ctx: &mut vars::Context) -> Option<Command> {
if self.len() == 0 { panic!("Command with 0 length"); }
fn exec(self: &mut Vec<CommandValue>, ctx: &mut vars::Context) -> Result<Option<Command>> {
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<Command> {
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<Option<Command>> {
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<Command> {
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<Option<Command>> {
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<Command> {
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<Option<Command>> {
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<Expression> {
fn exec(self, ctx: &mut Context) -> Option<Command> {
fn exec(self: &mut Vec<Expression>, ctx: &mut Context) -> Result<Option<Command>> {
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<Command> {
let mut first = match self.first.exec(ctx) {
None => panic!("Invalid OR expression"),
fn exec(self: &mut OrExpression, ctx: &mut Context) -> Result<Option<Command>> {
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<Command> {
let mut first = match self.first.exec(ctx) {
None => panic!("Invalid AND expression"),
fn exec(self: &mut AndExpression, ctx: &mut Context) -> Result<Option<Command>> {
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<Expression>, ctx: &mut vars::Context) {
pub fn exec_tree(tree: Vec<Expression>, 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<Expression>, 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(())
}

View file

@ -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(())
}

View file

@ -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::<String>() == keyword
}
pub fn tokenize(reader: &mut dyn std::io::BufRead) -> io::Result<Vec<Tokens>> {
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<Vec<Tokens>> {
let mut tokens: Vec<Tokens> = Vec::new();
fn save_buf(buf: &mut String, tokens: &mut Vec<Tokens>) {
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<Vec<Tokens>> {
'"' => 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<Vec<Tokens>> {
}
},
' ' => 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<Vec<Tokens>> {
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<Vec<Tokens>> {
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;
},

View file

@ -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<String, Variable>,
pub func: HashMap<String, FunctionDefinitionExpression>
/// list of functions
pub func: HashMap<String, FunctionDefinitionExpression>,
/// list of file descriptors, to be closed when the scope is left
pub fd: Vec<usize>
}
#[derive(Debug)]
pub struct Context {
pub scopes: Vec<Scope>,
pub exports: HashMap<String, String>,
pub fd: Vec<usize>
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<Scope> {
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);
}

20
test/if.rush Normal file
View file

@ -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

View file

@ -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
grep echo < test/simple.rush

7
test/var.rush Normal file
View file

@ -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 $?

4
test/while.rush Normal file
View file

@ -0,0 +1,4 @@
while true
echo loop
break
end