mirror of
https://github.com/danbulant/rush
synced 2026-05-24 12:35:38 +00:00
parser including redirects and formatstrings
This commit is contained in:
parent
1cda409a65
commit
b454c95be5
3 changed files with 110 additions and 69 deletions
|
|
@ -61,8 +61,7 @@ set obj = ${
|
||||||
$dynkey: $value2
|
$dynkey: $value2
|
||||||
}
|
}
|
||||||
set literal = "$var"
|
set literal = "$var"
|
||||||
# or perhaps ` ?
|
set formatted = `$var`
|
||||||
set formatted = f"$var"
|
|
||||||
|
|
||||||
# these are builtin commands rather than syntax structures (unlike set/while etc)
|
# these are builtin commands rather than syntax structures (unlike set/while etc)
|
||||||
# they simply accept arguments and work with them as with any other
|
# they simply accept arguments and work with them as with any other
|
||||||
|
|
|
||||||
167
src/parser.rs
167
src/parser.rs
|
|
@ -8,10 +8,21 @@ pub struct Index {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Primitive {
|
pub enum FormatStringPart {
|
||||||
Number(f64),
|
|
||||||
String(String),
|
String(String),
|
||||||
Variable(String),
|
Variable(String),
|
||||||
|
// could be globs in the future
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FormatString {
|
||||||
|
values: Vec<FormatStringPart>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Primitive {
|
||||||
|
Number(f64),
|
||||||
|
FormatString(FormatString),
|
||||||
Index(Index)
|
Index(Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,7 +184,7 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
.ignored()
|
.ignored()
|
||||||
.boxed();
|
.boxed();
|
||||||
|
|
||||||
let direct_string = none_of("$()[]{}\\\"\n;|&")
|
let direct_string = none_of("$()[]{}\\\"\n;|&<>#")
|
||||||
.and_is(text::whitespace().at_least(1).not())
|
.and_is(text::whitespace().at_least(1).not())
|
||||||
.ignored()
|
.ignored()
|
||||||
.or(escape.clone())
|
.or(escape.clone())
|
||||||
|
|
@ -202,23 +213,32 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
|
|
||||||
let comment = just('#').then(any().and_is(just('\n').not()).repeated());
|
let comment = just('#').then(any().and_is(just('\n').not()).repeated());
|
||||||
|
|
||||||
let empty = text::inline_whitespace().ignored()
|
let empty_block = text::whitespace().ignored()
|
||||||
.or(comment.ignored())
|
.or(comment.ignored())
|
||||||
.or(eol.ignored());
|
.or(eol.ignored());
|
||||||
|
|
||||||
let and = just("&&");
|
let and = just("&&");
|
||||||
let or = just("||");
|
let or = just("||");
|
||||||
let not = just('!');
|
|
||||||
let pipe = just('|');//.then(just('|').rewind().not());
|
let pipe = just('|');//.then(just('|').rewind().not());
|
||||||
// let pipe_target = just('>');
|
let pipe_target = just(">");
|
||||||
// let pipe_target_append = just(">>");
|
let pipe_target_append = just(">>");
|
||||||
// let pipe_source = just('<');
|
let pipe_source = just("<");
|
||||||
|
|
||||||
recursive(|expr| {
|
recursive(|expr| {
|
||||||
|
let format_string_part = choice((
|
||||||
|
variable.map(|s: &str| FormatStringPart::Variable(s.to_string())),
|
||||||
|
string.map(FormatStringPart::String),
|
||||||
|
))
|
||||||
|
.repeated()
|
||||||
|
.at_least(1)
|
||||||
|
.collect()
|
||||||
|
.map(|v| FormatString {
|
||||||
|
values: v
|
||||||
|
});
|
||||||
|
|
||||||
let primitive = choice((
|
let primitive = choice((
|
||||||
number.map(Primitive::Number),
|
number.map(Primitive::Number),
|
||||||
variable.map(|s: &str| Primitive::Variable(s.to_string())),
|
format_string_part.map(Primitive::FormatString),
|
||||||
string.map(Primitive::String),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
let group = expr.clone()
|
let group = expr.clone()
|
||||||
|
|
@ -256,9 +276,12 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
.collect()
|
.collect()
|
||||||
.delimited_by(just('('), just(')'));
|
.delimited_by(just('('), just(')'));
|
||||||
|
|
||||||
let block = expr.clone()
|
let block = choice((
|
||||||
.or(empty.to(vec![]))
|
expr.clone(),
|
||||||
.delimited_by(just('{'), just('}'));
|
empty_block.to(vec![]),
|
||||||
|
))
|
||||||
|
.delimited_by(just('{'), just('}'))
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let cmdname = value.clone()
|
let cmdname = value.clone()
|
||||||
.and_is(choice((
|
.and_is(choice((
|
||||||
|
|
@ -270,8 +293,7 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
just("break"),
|
just("break"),
|
||||||
just("continue"),
|
just("continue"),
|
||||||
just("return"),
|
just("return"),
|
||||||
just("fn"),
|
just("fn")
|
||||||
just("!")
|
|
||||||
)).then(end()).not());
|
)).then(end()).not());
|
||||||
|
|
||||||
let args = value.clone()
|
let args = value.clone()
|
||||||
|
|
@ -281,15 +303,16 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let command =
|
let command =
|
||||||
cmdname
|
text::inline_whitespace().ignore_then(cmdname)
|
||||||
.padded_by(text::inline_whitespace())
|
.then_ignore(text::inline_whitespace().at_least(1))
|
||||||
.then(args)
|
.then(args)
|
||||||
.map(|(name, args): (Value, Vec<Value>)| {
|
.map(|(name, args): (Value, Vec<Value>)| {
|
||||||
Command {
|
Command {
|
||||||
name: Box::new(name),
|
name: Box::new(name),
|
||||||
args: args.into_iter().map(|v| v.clone()).collect()
|
args: args.into_iter().map(|v| v.clone()).collect()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let set = just("set")
|
let set = just("set")
|
||||||
.then_ignore(text::inline_whitespace().at_least(1))
|
.then_ignore(text::inline_whitespace().at_least(1))
|
||||||
|
|
@ -301,7 +324,8 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
name: Box::new(name),
|
name: Box::new(name),
|
||||||
value: Box::new(value)
|
value: Box::new(value)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let else_ = just("else")
|
let else_ = just("else")
|
||||||
.then(text::inline_whitespace().at_least(1))
|
.then(text::inline_whitespace().at_least(1))
|
||||||
|
|
@ -321,7 +345,8 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
body,
|
body,
|
||||||
else_body
|
else_body
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let while_ = just("while")
|
let while_ = just("while")
|
||||||
.then_ignore(text::inline_whitespace().at_least(1))
|
.then_ignore(text::inline_whitespace().at_least(1))
|
||||||
|
|
@ -334,7 +359,8 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
body,
|
body,
|
||||||
else_body
|
else_body
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let for_ = just("for")
|
let for_ = just("for")
|
||||||
.then_ignore(text::inline_whitespace().at_least(1))
|
.then_ignore(text::inline_whitespace().at_least(1))
|
||||||
|
|
@ -350,7 +376,8 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
body,
|
body,
|
||||||
else_body
|
else_body
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let loop_ = just("loop")
|
let loop_ = just("loop")
|
||||||
.ignore_then(block.clone().padded_by(text::inline_whitespace()))
|
.ignore_then(block.clone().padded_by(text::inline_whitespace()))
|
||||||
|
|
@ -358,14 +385,16 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
Loop {
|
Loop {
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let return_ = just("return")
|
let return_ = just("return")
|
||||||
.then_ignore(text::inline_whitespace().at_least(1))
|
.then_ignore(text::inline_whitespace().at_least(1))
|
||||||
.ignore_then(value.clone().or_not())
|
.ignore_then(value.clone().or_not())
|
||||||
.map(|v: Option<Value>| {
|
.map(|v: Option<Value>| {
|
||||||
Statement::Return(v)
|
Statement::Return(v)
|
||||||
});
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let function = just("fn")
|
let function = just("fn")
|
||||||
.then_ignore(text::inline_whitespace().at_least(1))
|
.then_ignore(text::inline_whitespace().at_least(1))
|
||||||
|
|
@ -378,72 +407,86 @@ pub fn parse<'a>() -> impl Parser<'a, &'a str, Vec<Statement>, chumsky::extra::D
|
||||||
args: args,
|
args: args,
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.boxed();
|
||||||
|
|
||||||
let statement = choice((
|
let command = command.map(Statement::Command);
|
||||||
set.map(Statement::Set),
|
|
||||||
|
let mapable = choice((
|
||||||
if_.map(Statement::If),
|
if_.map(Statement::If),
|
||||||
while_.map(Statement::While),
|
while_.map(Statement::While),
|
||||||
for_.map(Statement::For),
|
for_.map(Statement::For),
|
||||||
loop_.map(Statement::Loop),
|
loop_.map(Statement::Loop),
|
||||||
function.map(Statement::Function),
|
command,
|
||||||
return_,
|
|
||||||
just("break").to(Statement::Break),
|
|
||||||
just("continue").to(Statement::Continue),
|
|
||||||
command.map(Statement::Command),
|
|
||||||
)).padded_by(text::inline_whitespace().ignored().or(comment.ignored())).boxed();
|
)).padded_by(text::inline_whitespace().ignored().or(comment.ignored())).boxed();
|
||||||
|
|
||||||
let not = not.repeated().at_least(1)
|
let pipe_target = mapable.clone()
|
||||||
.foldr(
|
|
||||||
text::inline_whitespace()
|
|
||||||
.ignore_then(statement.clone()),
|
|
||||||
|_, rhs| Statement::Not(Not {
|
|
||||||
value: Box::new(rhs)
|
|
||||||
})
|
|
||||||
).boxed();
|
|
||||||
|
|
||||||
let or = statement.clone()
|
|
||||||
.foldl(
|
.foldl(
|
||||||
or
|
pipe_target.or(pipe_target_append)
|
||||||
.padded_by(text::inline_whitespace())
|
.padded_by(text::inline_whitespace())
|
||||||
.ignore_then(statement.clone())
|
.ignore_then(value.clone())
|
||||||
.repeated(),
|
.repeated(),
|
||||||
|lhs, rhs| Statement::Or(Or {
|
|lhs, rhs| Statement::TargetFilePipe(TargetFilePipe {
|
||||||
lhs: Box::new(lhs),
|
cmd: Some(Box::new(lhs)),
|
||||||
rhs: Box::new(rhs)
|
target: Box::new(rhs),
|
||||||
|
overwrite: false
|
||||||
})).boxed();
|
})).boxed();
|
||||||
|
|
||||||
let and = statement.clone()
|
let pipe_source = pipe_target.clone()
|
||||||
.foldl(
|
.foldl(
|
||||||
and
|
pipe_source
|
||||||
.padded_by(text::inline_whitespace())
|
.padded_by(text::inline_whitespace())
|
||||||
.ignore_then(statement.clone())
|
.ignore_then(value.clone())
|
||||||
.repeated(),
|
.repeated(),
|
||||||
|lhs, rhs| Statement::And(And {
|
|lhs, rhs| Statement::SourceFilePipe(SourceFilePipe {
|
||||||
lhs: Box::new(lhs),
|
cmd: Some(Box::new(lhs)),
|
||||||
rhs: Box::new(rhs)
|
source: Box::new(rhs)
|
||||||
})).boxed();
|
})).boxed();
|
||||||
|
|
||||||
let pipe = statement.clone()
|
let pipe = pipe_source.clone()
|
||||||
.foldl(
|
.foldl(
|
||||||
pipe
|
pipe
|
||||||
.padded_by(text::inline_whitespace())
|
.padded_by(text::inline_whitespace())
|
||||||
.ignore_then(statement.clone())
|
.ignore_then(pipe_source)
|
||||||
.repeated(),
|
.repeated(),
|
||||||
|lhs, rhs| Statement::CommandPipe(CommandPipe {
|
|lhs, rhs| Statement::CommandPipe(CommandPipe {
|
||||||
lhs: Box::new(lhs),
|
lhs: Box::new(lhs),
|
||||||
rhs: Box::new(rhs)
|
rhs: Box::new(rhs)
|
||||||
})).boxed();
|
})).boxed();
|
||||||
|
|
||||||
let statement_pair = choice((
|
let or = pipe.clone()
|
||||||
or,
|
.foldl(
|
||||||
and,
|
or
|
||||||
not,
|
.padded_by(text::inline_whitespace())
|
||||||
pipe,
|
.ignore_then(pipe)
|
||||||
statement
|
.repeated(),
|
||||||
|
|lhs, rhs| Statement::Or(Or {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
rhs: Box::new(rhs)
|
||||||
|
})).boxed();
|
||||||
|
|
||||||
|
let and = or.clone()
|
||||||
|
.foldl(
|
||||||
|
and
|
||||||
|
.padded_by(text::inline_whitespace())
|
||||||
|
.ignore_then(or)
|
||||||
|
.repeated(),
|
||||||
|
|lhs, rhs| Statement::And(And {
|
||||||
|
lhs: Box::new(lhs),
|
||||||
|
rhs: Box::new(rhs)
|
||||||
|
})).boxed();
|
||||||
|
|
||||||
|
let statement = choice((
|
||||||
|
set.map(Statement::Set),
|
||||||
|
function.map(Statement::Function),
|
||||||
|
return_,
|
||||||
|
just("break").to(Statement::Break),
|
||||||
|
just("continue").to(Statement::Continue),
|
||||||
|
and
|
||||||
));
|
));
|
||||||
|
|
||||||
statement_pair
|
statement
|
||||||
|
.padded_by(text::inline_whitespace().ignored().or(comment.ignored()))
|
||||||
.separated_by(eol.repeated().at_least(1))
|
.separated_by(eol.repeated().at_least(1))
|
||||||
.at_least(1)
|
.at_least(1)
|
||||||
.allow_trailing()
|
.allow_trailing()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
let test = val
|
set test = val
|
||||||
echo var $test
|
echo var $test
|
||||||
let test = $test
|
set test = $test
|
||||||
echo var2 f"$test"
|
echo last exit code $status
|
||||||
echo last exit code $?
|
|
||||||
false
|
false
|
||||||
echo last false exit code $?
|
echo last false exit code $status
|
||||||
Loading…
Reference in a new issue