implement string and array native functions

This commit is contained in:
Daniel Bulant 2023-08-05 16:09:09 +02:00
parent b0a774994f
commit 2b5d7d52cd
8 changed files with 257 additions and 87 deletions

13
src/env.rs Normal file
View file

@ -0,0 +1,13 @@
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
}

View file

@ -1,4 +1,6 @@
mod parser;
mod env;
mod nativeFunctions;
use std::io::{self, BufRead, Stdout, Write};
use std::cmp;
@ -13,6 +15,8 @@ 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,
@ -21,10 +25,10 @@ struct Term {
impl Term {
fn new() -> Term {
return Term {
Term {
input: String::new(),
idx: 0,
};
}
}
fn print(self: &Self, stdout: &mut RawTerminal<Stdout>) {
@ -61,10 +65,10 @@ struct Shell {
impl Shell {
fn new() -> Shell {
return Shell {
Shell {
term: Term::new(),
ctx: parser::vars::Context::new()
};
}
}
fn collect(&mut self) {
@ -88,7 +92,7 @@ impl Shell {
}
}
Key::Backspace => {
if self.term.input.len() > 0 && self.term.idx > 0 {
if !self.term.input.is_empty() && self.term.idx > 0 {
if self.term.idx == self.term.input.len() - 1 {
self.term.input.pop();
} else {
@ -138,6 +142,7 @@ impl Shell {
fn start() {
let mut shell = Shell::new();
shell.ctx.native_func = get_native_functions();
loop {
print!("$: ");
io::stdout().flush().unwrap();
@ -146,11 +151,9 @@ impl Shell {
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);
match res {
Err(err) => eprintln!("rush: {}", err),
Ok(_) => {}
}
if let Err(err) = res { eprintln!("rush: {}", err) }
}
}
}
@ -202,15 +205,8 @@ mod test {
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use crate::parser;
use crate::{load_and_run, 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")

60
src/nativeFunctions.rs Normal file
View file

@ -0,0 +1,60 @@
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
});
map
}

View file

@ -47,10 +47,9 @@ pub enum ForValue {
}
#[derive(Debug)]
pub struct DefinedFunction {
name: String,
args: Vec<Value>,
body: Vec<Expression>
pub struct DefinedFunctionCall {
pub name: String,
pub args: Vec<Value>
}
#[derive(Debug)]
@ -59,8 +58,7 @@ pub enum Value {
Variable(String),
ArrayVariable(String),
ArrayDefinition(Vec<Value>),
ArrayFunction(DefinedFunction),
StringFunction(DefinedFunction),
ValueFunction(DefinedFunctionCall),
Expressions(Vec<Expression>),
Values(Vec<Value>)
}
@ -74,6 +72,8 @@ pub struct FunctionVariable {
#[derive(Debug)]
pub struct FunctionDefinitionExpression {
pub name: String,
pub description: Option<String>,
pub on_event: Option<String>,
pub args: Vec<FunctionVariable>,
pub body: Box<Expression>
}
@ -139,7 +139,11 @@ impl Tree {
loop {
if matches!(token, Tokens::Space) {
if !buf.is_empty() {
values.push(CommandValue::Value(Value::Values(buf)));
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 }
@ -171,6 +175,11 @@ impl Tree {
}
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())
}
@ -187,7 +196,11 @@ impl Tree {
}
// self.next();
if !buf.is_empty() {
values.push(CommandValue::Value(Value::Values(buf)));
if buf.len() == 1 {
values.push(CommandValue::Value(buf.pop().unwrap()));
} else {
values.push(CommandValue::Value(Value::Values(buf)));
}
}
Ok(Expression::Command(values))
}
@ -255,12 +268,26 @@ impl Tree {
bail!("Functions not yet implemented")
}
fn parse_array_func(&mut self, _str: String, _end: usize) -> Result<DefinedFunction> {
bail!("Array 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")
}.clone();
let mut args = Vec::new();
self.inc();
loop {
args.push(self.get_value(end, false)?);
self.inc();
if self.i >= end { break }
}
fn parse_string_func(&mut self, _str: String, _end: usize) -> Result<DefinedFunction> {
bail!("Array functions not yet implemented");
Ok(DefinedFunctionCall { name, args })
}
fn parse_for(&mut self, _end: usize) -> Result<ForExpression> {
@ -360,6 +387,27 @@ impl Tree {
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();
@ -381,8 +429,16 @@ impl Tree {
Tokens::FileWrite => buf.push(Value::Literal(token.to_str())),
Tokens::RedirectInto => bail!("Unexpected token REDIRECT (|)"),
Tokens::ParenthesisEnd => bail!("Unexpected token FUNCTION CALL END ())"),
Tokens::ArrayFunction(_) => bail!("Unexpected array function"),
Tokens::StringFunction(_) => bail!("Unexpected string function"),
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;
@ -410,26 +466,8 @@ impl Tree {
},
Tokens::ArrayEnd => bail!("Unexpected token ARRAY END (])"),
Tokens::SubStart => {
let mut len = 0;
let mut lvl = 1;
let (len, lvl) = self.get_parens_vals(end);
self.inc();
for token in &self.tokens[self.i..] {
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;
}
if len + self.i == end { break }
len += 1;
}
// self.inc();
if lvl != 0 {
bail!("Parenthesis do not match");
}
@ -466,7 +504,11 @@ impl Tree {
token = self.inc().get_current_token();
}
if !buf.is_empty() {
values.push(Value::Values(buf));
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());
@ -500,22 +542,8 @@ impl Tree {
Tokens::ParenthesisStart => if matches!(expr, Some(_)) {
bail!("Unexpected parenthesis. After file redirect, you need to use a semicolon or newline.");
} else {
let mut len = 1;
let mut lvl = 1;
self.inc();
for token in &self.tokens[self.i..] {
match token.token {
Tokens::ParenthesisStart => lvl += 1,
Tokens::ParenthesisEnd => lvl -= 1,
_ => {}
}
if lvl == 0 {
break;
}
if len + self.i == end { break }
len += 1;
}
let (len, lvl) = self.get_parens_vals(end);
if lvl != 0 {
bail!("Parenthesis not ended properly.");
}
@ -590,7 +618,7 @@ impl Tree {
}
pub fn build_tree(tokens: Vec<Token>) -> Result<Vec<Expression>> {
dbg!(&tokens);
// dbg!(&tokens);
let mut expressions: Vec<Expression> = Vec::new();
let mut tree = Tree { tokens, i: 0 };
loop {
@ -604,5 +632,6 @@ pub fn build_tree(tokens: Vec<Token>) -> Result<Vec<Expression>> {
}
}
}
// dbg!(&expressions);
Ok(expressions)
}

View file

@ -3,7 +3,7 @@ use std::process::{Child, Command, Stdio};
use std::io;
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 crate::parser::vars::{AnyFunction, Context, Variable};
use anyhow::{Result, bail, Context as AnyhowContext};
trait ExecExpression {
@ -112,10 +112,8 @@ impl GetValue for Value {
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::ArrayFunction(_) => todo!("Not implemented yet"),
Value::StringFunction(_) => todo!("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::Expressions(expressions) => {
let mut out = String::new();
ctx.add_scope();
@ -138,10 +136,28 @@ impl GetValue for Value {
}
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 vars::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 vars::Context) -> Result<Option<Command>> {
match self {
@ -167,7 +183,7 @@ 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)?.to_string();
let num: u16 = if val.len() > 0 { val.parse()? } else { 1 };
let num: u16 = if !val.is_empty() { val.parse()? } else { 1 };
ctx.break_num = if num == 0 { 1 } else { num };
Ok(None)
}
@ -247,7 +263,7 @@ impl ExecExpression for LetExpression {
impl ExecExpression for Vec<CommandValue> {
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"); }
if self.is_empty() { bail!("Command with 0 length"); }
let mut first = self.remove(0);
let command_name = first.get(ctx)?.to_string();
let mut cmd = Command::new(command_name);
@ -286,8 +302,7 @@ impl ExecExpression for FileTargetExpression {
todo!("Redirect without target file");
}
};
let command;
match src {
let command = match src {
Some(mut cmd) => {
cmd.stdout(Stdio::piped());
let file = File::create(target.to_string());
@ -304,7 +319,7 @@ impl ExecExpression for FileTargetExpression {
}
}
}
command = cmd;
cmd
},
None => { bail!("Invalid command provided for file target"); }
};

View file

@ -166,7 +166,8 @@ pub fn tokenize(reader: &mut dyn std::io::BufRead) -> Result<Vec<Token>> {
skipper = 1;
buf_add = false;
} else {
let (skippers, mut token) = read_var_ahead(i, &text)?;
let (mut skippers, mut token) = read_var_ahead(i, &text)?;
skippers += 1;
match token.token {
Tokens::StringVariable(ref str, bool) => if !bool && !double_quote_active && text.len() > i + skippers && text.chars().nth(i + skippers).unwrap() == '(' {
token = Token { token: Tokens::StringFunction(str.clone()), end: i + skippers, start: i };

View file

@ -1,5 +1,5 @@
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::fmt::{Debug, Display, Formatter};
use anyhow::{bail, Result};
use crate::parser::ast::FunctionDefinitionExpression;
@ -56,6 +56,17 @@ impl Display for Variable {
}
}
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 {
@ -134,6 +145,24 @@ impl Variable {
}
}
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)
}
#[derive(Debug)]
pub struct Scope {
/// list of variables
@ -147,8 +176,13 @@ pub struct Scope {
#[derive(Debug)]
pub struct Context {
pub scopes: Vec<Scope>,
pub exports: HashMap<String, String>,
/// 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
}
@ -157,16 +191,17 @@ impl 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(self: &mut Self) -> Option<Scope> {
pub fn pop_scope(&mut self) -> Option<Scope> {
self.scopes.pop()
}
pub fn add_scope(self: &mut Self) {
pub fn add_scope(&mut self) {
let scope = Scope {
func: HashMap::new(),
vars: HashMap::new(),
@ -175,7 +210,16 @@ impl Context {
self.scopes.push(scope);
}
pub fn get_var(self: &mut Self, var: &str) -> Option<&mut Variable> {
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);
@ -191,25 +235,36 @@ impl Context {
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(self: &mut Self, key: &str) -> Option<&mut FunctionDefinitionExpression> {
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(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 mut func = &mut self.scopes.last_mut().unwrap().func;
let func = &mut self.scopes.last_mut().unwrap().func;
func.insert(key, val);
}
}

1
test/builtins.rush Normal file
View file

@ -0,0 +1 @@
test $trim("test ") = "test"