mirror of
https://github.com/danbulant/rush
synced 2026-05-19 20:38:35 +00:00
feat: some progress
This commit is contained in:
parent
d04d10cb9a
commit
845f00df0b
12 changed files with 404 additions and 43 deletions
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
7
.idea/discord.xml
Normal file
7
.idea/discord.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/fileColors.xml
Normal file
7
.idea/fileColors.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SharedFileColors">
|
||||
<fileColor scope="Non-Project Files" color="Yellow" />
|
||||
<fileColor scope="Tests" color="Green" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/rush.iml" filepath="$PROJECT_DIR$/.idea/rush.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
14
.idea/rush.iml
Normal file
14
.idea/rush.iml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
7
.idea/vagrant.xml
Normal file
7
.idea/vagrant.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VagrantProjectSettings">
|
||||
<option name="instanceFolder" value="" />
|
||||
<option name="provider" value="" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
130
src/main.rs
130
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<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 {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
59
src/parser/ast.rs
Normal file
59
src/parser/ast.rs
Normal file
|
|
@ -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<Expression>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WhileExpression {
|
||||
pub condition: Expression,
|
||||
pub contents: Vec<Expression>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ForExpression {
|
||||
pub key: String,
|
||||
pub list: Value,
|
||||
pub contents: Vec<Expression>
|
||||
}
|
||||
|
||||
#[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<Value>,
|
||||
pub body: Expression
|
||||
}
|
||||
|
||||
pub struct RedirectTargetExpression {
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Expression {
|
||||
LetExpression(LetExpression),
|
||||
Command(Vec<Value>),
|
||||
Function(FunctionExpression),
|
||||
IfExpression(IfExpression),
|
||||
WhileExpression(WhileExpression),
|
||||
ForExpression(ForExpression)
|
||||
}
|
||||
|
|
@ -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<String> = Vec::new();
|
||||
let mut var_name = String::new();
|
||||
let mut prev_char: Option<char> = 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
|
||||
}
|
||||
|
|
@ -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<String, Variable>),
|
||||
array(Vec<Variable>)
|
||||
HMap(HashMap<String, Variable>),
|
||||
Array(Vec<Variable>)
|
||||
}
|
||||
|
||||
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<String, Variable>
|
||||
pub active: bool,
|
||||
pub vars: HashMap<String, Variable>,
|
||||
pub func: HashMap<String, FunctionExpression>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
pub scopes: Vec<Scope>,
|
||||
pub parent_context: Option<Box<Context>>
|
||||
pub exports: HashMap<String, String>
|
||||
}
|
||||
|
||||
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<Scope> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
39
test/simple.rush
Normal file
39
test/simple.rush
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue