mirror of
https://github.com/danbulant/rush
synced 2026-05-19 04:18:35 +00:00
various improvements, basic (currently failing) tests, improved lexer
This commit is contained in:
parent
b43959fa79
commit
aa33fb90ea
12 changed files with 439 additions and 300 deletions
88
Cargo.lock
generated
88
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
42
src/main.rs
42
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<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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
20
test/if.rush
Normal 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
|
||||
|
|
@ -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
7
test/var.rush
Normal 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
4
test/while.rush
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
while true
|
||||
echo loop
|
||||
break
|
||||
end
|
||||
Loading…
Reference in a new issue