shellharden/src/commonargcmd.rs
2020-02-23 21:38:38 +01:00

401 lines
9.7 KiB
Rust

/*
* Copyright 2016 - 2020 Andreas Nordal
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
use crate::situation::Transition;
use crate::situation::WhatNow;
use crate::situation::flush;
use crate::situation::COLOR_KWD;
use crate::situation::COLOR_MAGIC;
use crate::situation::COLOR_HERE;
use crate::situation::COLOR_VAR;
use crate::microparsers::prefixlen;
use crate::microparsers::predlen;
use crate::microparsers::identifierlen;
use crate::microparsers::is_whitespace;
use crate::microparsers::is_word;
use crate::commonstrcmd::CommonStrCmdResult;
use crate::commonstrcmd::common_str_cmd;
use crate::sitcase::SitIn;
use crate::sitcmd::SitNormal;
use crate::sitcmd::SitCmd;
use crate::sitcmd::SitDeclare;
use crate::sitcomment::SitComment;
use crate::sitextent::SitExtent;
use crate::sitrvalue::SitRvalue;
use crate::sitstrdq::SitStrDq;
use crate::sitstrphantom::SitStrPhantom;
use crate::sitstrsqesc::SitStrSqEsc;
use crate::situntilbyte::SitUntilByte;
use crate::sitvec::SitVec;
pub fn keyword_or_command(
end_trigger :u16,
horizon: &[u8],
i: usize,
is_horizon_lengthenable: bool,
) -> WhatNow {
if horizon[i] == b'(' {
return WhatNow{
tri: Transition::Push(Box::new(SitNormal{
end_trigger: u16::from(b')'), end_replace: None
})), pre: i, len: 1, alt: None
};
}
let (found, len) = find_lvalue(&horizon[i..]);
if found == Tri::Maybe && (i > 0 || is_horizon_lengthenable) {
return flush(i);
}
if found == Tri::Yes {
return WhatNow{
tri: Transition::Push(Box::new(SitRvalue{end_trigger})),
pre: i + len, len: 0, alt: None
};
}
let len = predlen(is_word, &horizon[i..]);
if i + len == horizon.len() && (i > 0 || is_horizon_lengthenable) {
return flush(i);
}
let word = &horizon[i..i+len];
match word {
b"[[" => WhatNow{
tri: Transition::Push(Box::new(
SitVec{terminator: vec!{b']', b']'}, color: COLOR_MAGIC}
)),
pre: i, len, alt: None
},
b"case" => WhatNow{
tri: Transition::Push(Box::new(SitIn{})),
pre: i, len, alt: None
},
b"do" |
b"done" |
b"elif" |
b"else" |
b"esac" |
b"fi" |
b"for" |
b"if" |
b"select" |
b"then" |
b"until" |
b"while" |
b"{" |
b"}" => WhatNow{
tri: Transition::Push(Box::new(SitExtent{
len,
color: COLOR_KWD,
end_insert: None
})), pre: i, len: 0, alt: None
},
b"declare" |
b"local" |
b"readonly" => WhatNow{
tri: Transition::Push(Box::new(SitDeclare{end_trigger})),
pre: i, len, alt: None
},
_ => WhatNow{
tri: Transition::Push(Box::new(SitCmd{end_trigger})),
pre: i, len: 0, alt: None
},
}
}
pub fn common_arg_cmd(
end_trigger :u16,
horizon :&[u8],
i :usize,
is_horizon_lengthenable :bool,
) -> Option<WhatNow> {
if let Some(res) = find_command_enders(horizon, i, is_horizon_lengthenable) {
return Some(res);
}
common_no_cmd(end_trigger, horizon, i, is_horizon_lengthenable)
}
pub fn common_no_cmd(
end_trigger :u16,
horizon :&[u8],
i :usize,
is_horizon_lengthenable :bool,
) -> Option<WhatNow> {
if let Some(res) = find_usual_suspects(end_trigger, horizon, i, is_horizon_lengthenable) {
return Some(res);
}
match common_str_cmd(&horizon, i, is_horizon_lengthenable, true) {
CommonStrCmdResult::None => None,
CommonStrCmdResult::Some(x) => Some(x),
CommonStrCmdResult::OnlyWithQuotes(_) => {
Some(WhatNow{
tri: Transition::Push(Box::new(SitStrPhantom{
cmd_end_trigger: end_trigger,
})), pre: i, len: 0, alt: Some(b"\"")
})
}
}
}
pub fn common_quoting_unneeded(
end_trigger :u16,
horizon :&[u8],
i :usize,
is_horizon_lengthenable :bool,
) -> Option<WhatNow> {
if let Some(res) = find_command_enders(horizon, i, is_horizon_lengthenable) {
return Some(res);
}
common_no_cmd_quoting_unneeded(end_trigger, horizon, i, is_horizon_lengthenable)
}
pub fn common_no_cmd_quoting_unneeded(
end_trigger :u16,
horizon :&[u8],
i :usize,
is_horizon_lengthenable :bool,
) -> Option<WhatNow> {
if let Some(res) = find_usual_suspects(end_trigger, horizon, i, is_horizon_lengthenable) {
return Some(res);
}
match common_str_cmd(&horizon, i, is_horizon_lengthenable, false) {
CommonStrCmdResult::None => None,
CommonStrCmdResult::Some(x) => Some(x),
CommonStrCmdResult::OnlyWithQuotes(x) => {
if horizon[i] == b'`' {
Some(WhatNow{
tri: Transition::Push(Box::new(SitNormal{
end_trigger: u16::from(b'`'), end_replace: None
})), pre: i, len: 1, alt: None
})
} else {
Some(x)
}
}
}
}
// Does not pop on eof → Callers must use flush_or_pop
fn find_command_enders(
horizon :&[u8],
i :usize,
is_horizon_lengthenable :bool,
) -> Option<WhatNow> {
let plen = prefixlen(&horizon[i..], b">&");
if plen == 2 {
return Some(flush(i + 2));
}
if i + plen == horizon.len() && (i > 0 || is_horizon_lengthenable) {
return Some(flush(i));
}
let a = horizon[i];
if a == b'\n' || a == b';' || a == b'|' || a == b'&' {
return Some(WhatNow{
tri: Transition::Pop, pre: i, len: 0, alt: None
});
}
None
}
fn find_usual_suspects(
end_trigger :u16,
horizon :&[u8],
i :usize,
is_horizon_lengthenable :bool,
) -> Option<WhatNow> {
let a = horizon[i];
if u16::from(a) == end_trigger {
return Some(WhatNow{
tri: Transition::Pop, pre: i, len: 0, alt: None
});
}
if a == b'#' {
return Some(WhatNow{
tri: Transition::Push(Box::new(SitComment{})),
pre: i, len: 1, alt: None
});
}
if a == b'\'' {
return Some(WhatNow{
tri: Transition::Push(Box::new(SitUntilByte{
until: b'\'', color: 0x00_ffff00, end_replace: None
})),
pre: i, len: 1, alt: None
});
}
if a == b'\"' {
return Some(WhatNow{
tri: Transition::Push(Box::new(SitStrDq{})),
pre: i, len: 1, alt: None
});
}
if a == b'$' {
if i+1 >= horizon.len() {
if i > 0 || is_horizon_lengthenable {
return Some(flush(i));
}
return None;
}
let b = horizon[i+1];
if b == b'\'' {
return Some(WhatNow{
tri: Transition::Push(Box::new(SitStrSqEsc{})),
pre: i, len: 2, alt: None
});
} else if b == b'*' {
// $* → "$@" but not "$*" → "$@"
let ext = Box::new(SitExtent{
len: 0,
color: COLOR_VAR,
end_insert: None
});
return Some(WhatNow{
tri: Transition::Push(ext),
pre: i, len: 2, alt: Some(b"\"$@\"")
});
}
}
let (ate, delimiter) = find_heredoc(&horizon[i ..]);
if i + ate == horizon.len() {
if i > 0 || is_horizon_lengthenable {
return Some(flush(i));
}
} else if !delimiter.is_empty() {
return Some(WhatNow{
tri: Transition::Push(Box::new(
SitVec{terminator: delimiter, color: COLOR_HERE}
)),
pre: i, len: ate, alt: None
});
} else if ate > 0 {
return Some(flush(i + ate));
}
None
}
#[derive(PartialEq)]
#[derive(Clone)]
#[derive(Copy)]
pub enum Tri {
No,
Maybe,
Yes,
}
pub fn find_lvalue(horizon: &[u8]) -> (Tri, usize) {
let mut ate = identifierlen(&horizon);
if ate == 0 {
return (Tri::No, ate);
}
#[derive(Clone)]
#[derive(Copy)]
enum Lex {
Ident,
Brack,
Pluss,
}
let mut state = Lex::Ident;
loop {
if ate == horizon.len() {
return (Tri::Maybe, ate);
}
let byte :u8 = horizon[ate];
ate += 1;
// TODO: Recursion: Expression tracker
match (state, byte) {
(Lex::Ident, b'=') => return (Tri::Yes, ate),
(Lex::Pluss, b'=') => return (Tri::Yes, ate),
(Lex::Ident, b'[') => state = Lex::Brack,
(Lex::Brack, b']') => state = Lex::Ident,
(Lex::Ident, b'+') => state = Lex::Pluss,
(Lex::Ident, _) => return (Tri::No, ate),
(Lex::Pluss, _) => return (Tri::No, ate),
(Lex::Brack, _) => {}
}
}
}
fn find_heredoc(horizon: &[u8]) -> (usize, Vec<u8>) {
let mut ate = predlen(|x| x == b'<', &horizon);
let mut found = Vec::<u8>::new();
if ate != 2 {
return (ate, found);
}
ate += predlen(|x| x == b'-', &horizon[ate ..]);
ate += predlen(is_whitespace, &horizon[ate ..]);
// Lex one word.
let herein = &horizon[ate ..];
found.reserve(herein.len());
#[derive(Clone)]
#[derive(Copy)]
enum DelimiterSyntax {
WORD,
WORDESC,
SQ,
DQ,
DQESC,
}
let mut state = DelimiterSyntax::WORD;
for byte_ref in herein {
let byte: u8 = *byte_ref;
state = match (state, byte) {
(DelimiterSyntax::WORD, b' ' ) => break,
(DelimiterSyntax::WORD, b'\n') => break,
(DelimiterSyntax::WORD, b'\t') => break,
(DelimiterSyntax::WORD, b'\\') => DelimiterSyntax::WORDESC,
(DelimiterSyntax::WORD, b'\'') => DelimiterSyntax::SQ,
(DelimiterSyntax::WORD, b'\"') => DelimiterSyntax::DQ,
(DelimiterSyntax::SQ, b'\'') => DelimiterSyntax::WORD,
(DelimiterSyntax::DQ, b'\"') => DelimiterSyntax::WORD,
(DelimiterSyntax::DQ, b'\\') => DelimiterSyntax::DQESC,
(DelimiterSyntax::WORDESC, b'\n') => DelimiterSyntax::WORD,
(DelimiterSyntax::WORDESC, _) => {
found.push(byte);
DelimiterSyntax::WORD
}
(DelimiterSyntax::DQESC, b'\n') => DelimiterSyntax::DQ,
(DelimiterSyntax::DQESC, _) => {
if byte != b'\"' && byte != b'\\' {
found.push(b'\\');
}
found.push(byte);
DelimiterSyntax::DQ
}
(_, _) => {
found.push(byte);
state
}
};
ate += 1;
}
(ate, found)
}
#[test]
fn test_find_lvalue() {
assert!(find_lvalue(b"") == (Tri::No, 0));
assert!(find_lvalue(b"=") == (Tri::No, 0));
assert!(find_lvalue(b"[]") == (Tri::No, 0));
assert!(find_lvalue(b"esa") == (Tri::Maybe, 3));
assert!(find_lvalue(b"esa+") == (Tri::Maybe, 4));
assert!(find_lvalue(b"esa[]") == (Tri::Maybe, 5));
assert!(find_lvalue(b"esa[]+") == (Tri::Maybe, 6));
assert!(find_lvalue(b"esa ") == (Tri::No, 4));
assert!(find_lvalue(b"esa]") == (Tri::No, 4));
assert!(find_lvalue(b"esa=") == (Tri::Yes, 4));
assert!(find_lvalue(b"esa+=") == (Tri::Yes, 5));
assert!(find_lvalue(b"esa[]=") == (Tri::Yes, 6));
assert!(find_lvalue(b"esa[]+=") == (Tri::Yes, 7));
}