diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..b58b603
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..8cf359d
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/fileColors.xml b/.idea/fileColors.xml
new file mode 100644
index 0000000..6c33d23
--- /dev/null
+++ b/.idea/fileColors.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..5966e0f
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/rush.iml b/.idea/rush.iml
new file mode 100644
index 0000000..b4691a7
--- /dev/null
+++ b/.idea/rush.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vagrant.xml b/.idea/vagrant.xml
new file mode 100644
index 0000000..a5aa786
--- /dev/null
+++ b/.idea/vagrant.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index ab92897..4e43920 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,73 +1,139 @@
mod parser;
-use std::io::{self, Read, Write};
+use std::io::{self, Read, Stdout, Write};
use std::cmp;
use std::convert::TryInto;
+use std::ops::Add;
+use std::process;
-use termion::raw::IntoRawMode;
+use termion::raw::{IntoRawMode, RawTerminal};
use termion::input::TermRead;
use termion::cursor::{self, DetectCursorPos};
use termion::event::*;
use termion::input::{MouseTerminal};
+struct Term {
+ input: String,
+ idx: usize,
+}
+
+impl Term {
+ fn new() -> Term {
+ return Term {
+ input: String::new(),
+ idx: 0,
+ };
+ }
+
+ fn print(self: &Self, stdout: &mut RawTerminal) {
+ print!("{}", self.format(stdout));
+ }
+ fn format(self: &Self, stdout: &mut RawTerminal) -> 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 {
+ return Shell {
+ term: Term::new(),
+ ctx: parser::vars::Context {
+ scopes: Vec::new(),
+ parent_context: None,
+ },
+ };
+ }
+}
+
fn main() {
+ loop {
+ let mut shell = editor();
+ shell.term.input += "\n";
+ parser::exec(&mut shell.term.input.as_bytes(), shell.ctx);
+ }
+}
+
+fn editor() -> Shell {
let stdin = io::stdin();
let mut stdout = io::stdout().into_raw_mode().unwrap();
- let ctx = parser::vars::Context {
- scopes: Vec::new(),
- parent_context: None
- };
- let mut output = String::new();
- let mut idx = 0;
+ let mut shell = Shell::new();
+ let mut should_execute = true;
for c in stdin.keys() {
let c = c.unwrap();
match c {
Key::Char('\n') => {
- break;
+ if shell.term.input.chars().nth(shell.term.idx).unwrap_or(' ') == '\\' {
+ shell.term.insert_str(shell.term.idx, "\\\n");
+ } else {
+ break;
+ }
}
Key::Backspace => {
- if output.len() > 0 && idx > 0 {
- output.remove(idx - 1);
- idx -= 1;
- print!("{}{} ", cursor::Left((output.len() - idx + 2).try_into().unwrap()), &output[idx..]);
+ if shell.term.input.len() > 0 && shell.term.idx > 0 {
+ if shell.term.idx == shell.term.input.len() - 1 {
+ shell.term.input.pop();
+ } else {
+ shell.term.remove(shell.term.idx - 1);
+ }
+ shell.term.idx -= 1;
}
}
Key::Delete => {
- if idx < output.len() {
- output.remove(idx);
- print!("{} {}", &output[idx..], cursor::Left((output.len() - idx + 1).try_into().unwrap()));
+ if shell.term.idx < shell.term.input.len() {
+ shell.term.remove(shell.term.idx);
}
}
Key::End => {
- let start = idx;
- idx = cmp::max(output.len(), 1) - 1;
- print!("{}", cursor::Right((idx - start).try_into().unwrap()));
+ shell.term.idx = cmp::max(shell.term.input.len(), 1) - 1;
}
Key::Home => {
- let start = idx;
- idx = 0;
- print!("{}", cursor::Left(start.try_into().unwrap()));
+ shell.term.idx = 0;
}
Key::Left => {
- if idx > 0 {
- idx -= 1;
+ if shell.term.idx > 0 {
+ shell.term.idx -= 1;
}
- print!("{}", cursor::Left(1));
}
Key::Right => {
- if idx < output.len() - 1 {
- idx += 1;
+ if shell.term.idx < shell.term.input.len() - 1 {
+ shell.term.idx += 1;
}
- print!("{}", cursor::Right(1));
+ }
+ Key::Ctrl('c') => {
+ process::exit(1);
}
Key::Char(char) => {
- output.insert(idx, char);
- idx += 1;
- print!("{}", char);
+ shell.term.insert(shell.term.idx, char);
+ shell.term.idx += 1;
}
_ => {}
}
+ shell.term.print(&mut stdout);
stdout.flush().unwrap();
}
- parser::read_parse(&mut output.as_bytes(), ctx).unwrap();
+ stdout.suspend_raw_mode();
+ shell
}
diff --git a/src/parser/ast.rs b/src/parser/ast.rs
new file mode 100644
index 0000000..ff22cac
--- /dev/null
+++ b/src/parser/ast.rs
@@ -0,0 +1,59 @@
+use crate::parser::vars::Variable;
+
+#[derive(Debug)]
+pub struct LetExpression {
+ pub key: String,
+ pub value: Value
+}
+
+#[derive(Debug)]
+pub struct IfExpression {
+ pub condition: Expression,
+ pub contents: Vec
+}
+
+#[derive(Debug)]
+pub struct WhileExpression {
+ pub condition: Expression,
+ pub contents: Vec
+}
+
+#[derive(Debug)]
+pub struct ForExpression {
+ pub key: String,
+ pub list: Value,
+ pub contents: Vec
+}
+
+#[derive(Debug)]
+pub enum ForValue {
+ Value(Value),
+ Range(Some(u32), Some(u32))
+}
+
+#[derive(Debug)]
+pub enum Value {
+ Literal(String),
+ Variable(Variable),
+ Expression(Expression)
+}
+
+#[derive(Debug)]
+pub struct FunctionExpression {
+ pub args: Vec,
+ pub body: Expression
+}
+
+pub struct RedirectTargetExpression {
+
+}
+
+#[derive(Debug)]
+pub enum Expression {
+ LetExpression(LetExpression),
+ Command(Vec),
+ Function(FunctionExpression),
+ IfExpression(IfExpression),
+ WhileExpression(WhileExpression),
+ ForExpression(ForExpression)
+}
\ No newline at end of file
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 4c27926..2bd790c 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -1,12 +1,96 @@
pub mod vars;
+pub mod ast;
use std::io;
//use std::io::prelude::*;
use utf8_chars::BufReadCharsExt;
-pub fn read_parse(reader: &mut dyn std::io::BufRead, ctx: vars::Context) -> io::Result<()> {
+pub fn exec(reader: &mut dyn std::io::BufRead, ctx: vars::Context) -> io::Result<()> {
+ println!("got result");
+ let mut word = String::new();
+ let mut if_active = false;
+ let mut quote_active = false;
+ let mut double_quote_active = false;
+ let mut var_active = false;
+ let mut var_parens_active = false;
+ let mut command = String::new();
+ let mut args: Vec = Vec::new();
+ let mut var_name = String::new();
+ let mut prev_char: Option = None;
+
for c in reader.chars().map(|x| x.unwrap()) {
- println!("char {}\r", c);
+ print!("{}\r", c);
+ // loop runs once, allows early exit
+ loop {
+ match c {
+ '$' => {
+ if quote_active {
+ word.push('$');
+ }
+ var_active = true;
+ }
+ '{' => {
+ if var_active {
+ var_parens_active = true;
+ } else {
+ word.push('{');
+ }
+ }
+ '}' => {
+ if !var_parens_active {
+ word.push('}');
+ }
+ }
+ ' ' => {
+ if quote_active || double_quote_active {
+ word.push(' ');
+ break;
+ }
+ if var_parens_active { break; }
+ if var_active {}
+ if command.len() == 0 {
+ if word == "if" {
+ if_active = true;
+ } else if word == "end" {
+ ctx.pop_scope();
+ } else {
+ command = word;
+ word = String::new();
+ }
+ } else {
+ args.push(word.clone());
+ word = String::new();
+ }
+ }
+ ';' | '\n' => {
+ let mut arg_strings: Vec<&str> = Vec::new();
+ for arg in &args {
+ arg_strings.push(&*arg);
+ }
+ println!("Will execute {} with {}", command, arg_strings.join(" "));
+ if if_active {
+ println!("Will execute below only if the previous command succeeded");
+ }
+ if_active = false;
+
+ let exit_code = 0;
+ ctx.add_scope(exit_code == 0);
+ }
+ char => {
+ if var_active {
+ var_name.push(char);
+ } else {
+ word.push(char);
+ }
+ }
+ };
+ break;
+ }
+ prev_char = Some(c);
}
Ok(())
}
+
+pub fn escape(str: String) -> String {
+ str
+}
\ No newline at end of file
diff --git a/src/parser/vars.rs b/src/parser/vars.rs
index b9bac1c..7d9b95a 100644
--- a/src/parser/vars.rs
+++ b/src/parser/vars.rs
@@ -1,4 +1,7 @@
use std::collections::HashMap;
+use std::ops::{Add, Deref};
+use crate::parser::ast::FunctionExpression;
+use crate::parser::escape;
#[derive(Debug)]
pub enum Variable {
@@ -11,34 +14,90 @@ pub enum Variable {
U128(u128),
F32(f32),
F64(f64),
- hmap(HashMap),
- array(Vec)
+ HMap(HashMap),
+ Array(Vec)
+}
+
+impl Variable {
+ pub fn to_string(self: &Self) -> String {
+ match self {
+ Variable::String(var) => String::from(var),
+ 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::HMap(map) => {
+ String::from("[Object object]")
+ },
+ Variable::Array(vars) => {
+ let mut str = String::new();
+ for var in vars {
+ str += &*var.clone().to_string();
+ str += " ";
+ }
+ str
+ }
+ }
+ }
+
+ pub fn index(self: &Self) -> &Variable {
+ match self {
+ _ => panic!("Cannot index unsupported types")
+ }
+ }
}
#[derive(Debug)]
pub struct Scope {
- active: bool,
- vars: HashMap
+ pub active: bool,
+ pub vars: HashMap,
+ pub func: HashMap
}
#[derive(Debug)]
pub struct Context {
pub scopes: Vec,
- pub parent_context: Option>
+ pub exports: HashMap
}
impl Context {
pub fn new() -> Context {
Context {
- scopes: Vec::new(),
- parent_context: None
+ scopes: Vec::new()
}
}
- pub fn add_scope(self: &mut Context, active: bool) {
+ pub fn pop_scope(self: &mut Self) -> Option {
+ self.scopes.pop()
+ }
+ pub fn add_scope(self: &mut Self, active: bool) {
let scope = Scope {
- active: active,
+ active,
+ func: HashMap::new(),
vars: HashMap::new()
};
self.scopes.push(scope);
}
+
+ pub fn get_var(self: &mut Self, var: &str) -> Option<&mut Variable> {
+ for mut scope in self.scopes.iter_mut().rev() {
+ let mut vars = &mut scope.vars;
+ let val = vars.get_mut(var);
+ match val {
+ None => {},
+ Some(val) => {
+ return Some(val);
+ }
+ }
+ }
+ None
+ }
+
+ pub fn set_var(&mut self, key: String, val: Variable) {
+ let mut vars = &mut self.scopes.last_mut().unwrap().vars;
+ vars.insert(key, val);
+ }
}
\ No newline at end of file
diff --git a/test/simple.rush b/test/simple.rush
new file mode 100644
index 0000000..e78de8a
--- /dev/null
+++ b/test/simple.rush
@@ -0,0 +1,39 @@
+
+echo test
+
+echo single; echo line
+
+echo test > cat
+
+grep echo < simple.rush
+
+let test = val
+echo var $test
+let test = $test
+echo var2 ${test}
+echo last exit code $?
+
+if true
+ echo condition true
+end
+
+if false
+ echo condition false
+end
+
+if true
+ echo condition true
+else
+ echo else
+end
+
+if false
+ echo condition false
+else if true
+ echo condition else true
+end
+
+while true
+ echo loop
+ break
+end
\ No newline at end of file