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