single_line_comment = _{ "#" ~ (!NEWLINE ~ ANY)* }
block_comment = _{ "<#" ~ (!"#>" ~ ANY)* ~ "#>" }
COMMENT = _{ single_line_comment | block_comment }
line_continuation = { "`" ~ NEWLINE }
WHITESPACE = _{ " " | "\t" | line_continuation | NEWLINE }
program = { SOI ~ script_param_block ~ named_blocks? ~statements? ~ EOI }
//---------------------------------- BLOCKS
param_block = { attribute_list? ~ ^"param" ~ "(" ~ parameter_list? ~ ")" }
script_param_block ={param_block?}
parameter_list = { script_parameter ~ ("," ~ script_parameter)* }
script_parameter = { attribute_list? ~ variable ~ script_parameter_default? }
script_parameter_default = { "=" ~ primary_expression }
script_block_body = { named_blocks | statements_block }
statements_block = { statements }
named_blocks = { named_block+ }
named_block = { block_name ~ statement_block }
block_name = { ^"dynamicparam" | ^"begin" | ^"process" | ^"end" }
statement_block = { "{" ~ statements? ~ "}" }
//------------------------------------- STATEMENTS
statements = _{ statement+ }
statement = _{
if_statement
| labeled_statement
| function_statement
| class_statement
| enum_statement
| flow_control_statement
| trap_statement
| try_statement
| data_statement
| inlinescript_statement
| parallel_statement
| sequence_statement
| pipeline_statement
| statement_terminator
}
statement_terminator = { (";" | NEWLINE)+ }
if_statement = {
^"if" ~ "(" ~ pipeline ~ ")" ~ statement_block ~ elseif_clauses? ~ else_condition?
}
elseif_clauses = {else_if+}
else_if = { ^"elseif" ~ "(" ~ pipeline ~ ")" ~ statement_block }
else_condition = { ^"else" ~ statement_block }
//------------------------------- LABELED STATEMENTS
label = @{ ":" ~ ASCII_ALPHANUMERIC* }
labeled_statement = _{ label? ~ (
switch_statement
| foreach_statement
| for_statement
| while_statement
| do_statement
) }
label_exp = { label | unary_exp }
switch_statement = { ^"switch" ~ switch_parameters? ~ switch_condition ~ switch_body }
switch_parameters = { switch_parameter+ }
switch_parameter = { ^"-regex" | ^"-wildcard" | ^"-exact" | ^"-casesensitive" | ^"-parallel" }
switch_condition = _{ "(" ~ pipeline ~ ")" | ^"-file" ~ switch_filename }
switch_filename = { command_token | primary_expression }
switch_body = _{ "{" ~ switch_clause+ ~ "}" }
switch_clause = { switch_clause_condition ~ statement_block }
switch_clause_condition = _{ primary_expression | command_token }
foreach_statement = { ^"foreach" ~ foreach_parameter? ~ "(" ~ variable ~ ^"in" ~ pipeline ~ ")" ~ statement_block }
foreach_parameter = { ^"-parallel" }
while_statement = { ^"while" ~ "(" ~ while_condition ~ ")" ~ statement_block }
while_condition = { pipeline }
do_statement = { ^"do" ~ statement_block ~ ( ^"while" | ^"until" ) ~ "(" ~ while_condition ~ ")"}
for_statement = {
^"for" ~ "(" ~
for_initializer_block? ~
")" ~
statement_block
}
for_initializer_block = {
for_initializer? ~ statement_terminator ~
for_condition? ~ statement_terminator ~
for_iterator? ~ statement_terminator?
}
for_initializer = { pipeline }
for_condition = { pipeline }
for_iterator = { pipeline }
//---------------------------------- OTHER STATEMENTS
function_statement = {
function_keyword ~ (scope_keyword ~ ":")? ~ function_name ~ function_parameter_declaration? ~ "{" ~ script_block? ~ "}"
}
function_keyword = { ^"function" | ^"filter" | ^"workflow" }
function_name = @{ generic_token }
function_parameter_declaration = _{ "(" ~ parameter_list? ~ ")" }
flow_control_statement = {
flow_control_label_statement
| flow_control_pipeline_statement
}
flow_control_label_statement = { break_statement | continue_statement }
break_statement = { ^"break" ~ label_exp? }
continue_statement = { ^"continue" ~ label_exp? }
flow_control_pipeline_statement = { throw_statement | return_statement | exit_statement }
throw_statement = { ^"throw" ~ pipeline?}
return_statement = { ^"return" ~ pipeline?}
exit_statement = { ^"exit" ~ pipeline?}
trap_statement = { ^"trap" ~ type_literal? ~ statement_block}
inlinescript_statement = { ^"inlinescript" ~ statement_block}
parallel_statement = { ^"parallel" ~ statement_block }
sequence_statement = { ^"sequence" ~ statement_block }
//--------------------------------- TRY CATCH
try_statement = { ^"try" ~ statement_block ~ (catch_clauses ~ finally_clause? | finally_clause?) }
catch_clauses = { catch_clause+ }
catch_clause = { ^"catch" ~ catch_type_list? ~ statement_block }
catch_type_list = { type_literal ~ ("," ~ type_literal)* }
finally_clause = { ^"finally" ~ statement_block }
//----------------------------------- DATA STATEMENTS
data_statement = { ^"data" ~ simple_name? ~ data_commands_allowed? ~ statement_block }
data_commands_allowed = { ^"-supportedcommand" ~ data_commands_list }
data_commands_list = { data_command ~ ("," ~ data_command)* }
data_command = { invocation_command_exp }
//--------------------------------- CLASS STATEMENTS
class_attribute_static = { ^"static" }
class_attribute_hidden = { ^"hidden" }
class_property_definition = {
attribute_info? ~
class_attribute_static? ~
class_attribute_hidden? ~
type_literal? ~
variable ~
("=" ~ expression)?
}
class_method_definition = {
attribute_info? ~
class_attribute_static? ~
class_attribute_hidden? ~
type_literal? ~
simple_name ~
"(" ~ parameter_list? ~ ")" ~
"{" ~ script_block ~ "}"
}
class_statement = {
^"class" ~ simple_name ~ (":" ~ simple_name ~ ("," ~ simple_name)*)? ~
"{" ~
(
class_property_definition ~ statement_terminator*
| class_method_definition
)* ~
"}"
}
// -----------------------ENUM
enum_statement = { ^"enum" ~ simple_name ~ "{" ~ (enum_member ~ statement_terminator ~ ";"*)* ~ "}" }
enum_member = { simple_name ~ (("=" ~ decimal_integer) | ("=" ~ hex_integer) )? }
// ---------------------- EXPRESSSION
expression = { bitwise_exp ~ (logical_operator ~ bitwise_exp)* }
logical_operator = { ^"-and" | ^"-or" | ^"-xor" }
bitwise_exp = { as_expression ~ (bitwise_operator ~ as_expression)* }
bitwise_operator = { ^"-band" | ^"-bor" | ^"-bxor" | ^"-shl" | ^"-shr" }
as_expression = {comparison_exp ~ (^"-as" ~ primary_expression)*}
// strange case. -split and -join can be invoke without previous expression, eg. "-join 'some'"
comparison_exp = {
additive_exp ~split_op ~ script_block_expression |
(additive_exp ~ (string_op ~ additive_exp)*) |
empty ~ ((join_op | split_op) ~ additive_exp)+
}
empty = {""}
additive_exp = { multiplicative_exp ~ (WHITESPACE* ~ additive_op ~ ((WHITESPACE+ ~ pre_arithmetic) | (!pre_arithmetic ~ WHITESPACE* ~ multiplicative_exp)))* }
additive_op = _{ plus | minus }
plus = { "+" }
minus = {"-"}
multiplicative_exp = { format_exp ~ (WHITESPACE* ~ multiplicative_op ~ WHITESPACE* ~ format_exp)* }
multiplicative_op = _{ mul | divide | mod }
mul = { "*" }
divide = { "/" }
mod = { "%" }
format_exp = { range_exp ~ ( format_op ~ range_exp)* }
format_op = _{ ^"-f" }
range_exp = {
decimal_integer ~ ".." ~ WHITESPACE* ~ array_literal_exp |
array_literal_exp ~ (WHITESPACE* ~ ".." ~ WHITESPACE* ~ array_literal_exp)? }
array_literal_exp = { unary_exp ~ ("," ~ unary_exp)* | array_literal_exp_special_case }
array_literal_exp_typical_case = {unary_exp ~ ("," ~ unary_exp)*}
array_literal_exp_special_case = {"," ~ array_literal_exp }
unary_exp = { expression_with_unary_operator | primary_expression }
negate_op = { "!" | ^"-not" }
bitwise_negate_op = { ^"-bnot" }
expression_with_unary_operator = {
(negate_op ~ unary_exp)
| (bitwise_negate_op ~ unary_exp)
| pre_arithmetic
| cast_expression
}
pre_arithmetic = {
pre_inc_expression
| pre_dec_expression
| pre_plus_expression
| pre_minus_expression
}
pre_plus_expression = { "+" ~ (variable | parenthesized_expression) }
pre_minus_expression = { !"--" ~ "-" ~ (variable | parenthesized_expression) }
pre_inc_expression = { "++" ~ variable }
pre_dec_expression = { "--" ~ variable }
cast_expression = { !WHITESPACE ~ type_literal ~(unary_exp | parenthesized_expression) }
//------------------------------------PRIMARY EXPRESSSION
primary_expression = {
value_access
| post_inc_expression
| post_dec_expression
| value
}
post_inc_expression = ${ variable ~ "++" ~ !plus }
post_dec_expression = { variable ~ "--" }
value_access = { value ~
(
method_invocation
| member_access
| static_access
| element_access
)+
}
member_access = { "." ~ member_name }
static_access = { "::" ~ member_name }
member_name = {
simple_name
}
element_access = { "[" ~ expression ~ "]" }
method_invocation = { (member_access | static_access) ~ "(" ~ argument_list? ~ ")" }
// --------------- PIPELINE
pipeline_statement = _{ pipeline ~ statement_terminator? }
pipeline = { assignment_exp | pipeline_with_tail }
pipeline_with_tail = {(redirected_expression | command) ~ pipeline_tail?}
redirected_expression = { expression ~ redirection? }
pipeline_tail = { ("|" ~ command)+ }
assignment_exp = { type_literal? ~ assignable_variable ~ assignement_op ~ (if_statement | pipeline) }
assignable_variable = { variable_access | value_access | variable}
variable_access = { variable ~
(
member_access
| static_access
| element_access
)+
}
prefix_assign_op = { (additive_op | multiplicative_op) }
assign_op = { "=" }
assignement_op = {prefix_assign_op? ~ assign_op }
// ---------------- COMMAND
command = ${(
invocation_command
| cmdlet_command) ~ command_element*
}
foreach_command_name = { "%" | ^"foreach-object" | ^"foreach" }
where_command_name = { "?" | ^"where-object" | ^"where" }
powershell_command_name = { (^"powershell" | ^"pwsh") ~ ^".exe"? }
cmdlet_command = ${ (powershell_command_name | where_command_name | foreach_command_name | command_name) }
invocation_command = !{ current_scope_invocation_command | new_scope_invocation_command }
current_scope_invocation_command = { "." ~ invocation_command_exp }
new_scope_invocation_command = { "&" ~ invocation_command_exp }
BREAK = _{ " " | "\t" }
command_element = _{
BREAK
| stop_parsing
| command_parameter
| redirection
| command_argument ~ argument_list?
| splatten_arg
}
command_parameter = @{
"-"+ ~ command_parameter_chars+
| "--"
}
splatten_arg = ${ "@" ~ (scope_keyword ~ ":")? ~ var_name }
command_parameter_chars = _{ ASCII_ALPHA | ASCII_DIGIT | "_" | "?" | "-" | "`" }
stop_parsing = @{ "--%" ~ (!(NEWLINE) ~ ANY)* }
command_argument_sep = { ":" }
command_argument = {
parenthesized_expression
| script_block_expression
| array_literal_exp
| generic_token
| command_argument_sep ~ array_literal_exp
| command_argument_sep ~ generic_token?
}
invocation_command_exp = _{ cmdlet_command | path_command_name | primary_expression }
command_call = { command_ident ~ expression* }
command_name = { command_name_base ~ extra_command_token? }
command_name_base = @{ (!FORBIDDEN_CHARS ~ ANY)+ }
FORBIDDEN_CHARS = _{
"{" | "}" | "(" | ")" | ";" | "," | "|" | "&" | "$" | "`" | "\"" | "'" | WHITESPACE | NEWLINE
| "[" | "]" | "+" | "-" | "*" | "/" | "@" | "<" | "!" | "%" | "="
}
SECOND_FORBIDDEN_CHARS = _{
"{" | "}" | "(" | ")" | ";" | "," | "|" | "&" | "\"" | "'" | WHITESPACE | "\r" | "\n"
}
extra_command_token = {
immediate_chars
| doublequoted_string_literal
| "\"\""
| "''"
}
immediate_chars = @{ (!SECOND_FORBIDDEN_CHARS ~ ANY)+ }
path_command_name = @{ (ASCII_ALPHANUMERIC | "_" | "?" | "-" | "." | "\\")+ }
//-------------------------------------STRING OPERATORS
string_op = { replace_op | split_op | cmp_op | contain_op | join_op | type_check_op }
replace_op = { ^"-replace" | ^"-ireplace" | ^"-creplace" }
split_op = { ^"-split" | ^"-isplit" | ^"-csplit" }
join_op = { ^"-join" }
cmp_op = {
^"-eq" | ^"-ieq" | ^"-ceq" | ^"-ge" | ^"-cge" | ^"-ige" | ^"-gt" | ^"-cgt" | ^"-igt"
| ^"-le" | ^"-cle" | ^"-ile" | ^"-lt" | ^"-clt" | ^"-ilt" | ^"-ne" | ^"-cne" | ^"-ine"
| ^"-match" | ^"-cmatch" | ^"-imatch" | ^"-notmatch" | ^"-cnotmatch" | ^"-inotmatch"
| ^"-like"| ^"-clike" | ^"-ilike" | ^"-notlike" | ^"-inotlike" | ^"-cnotlike" }
contain_op = { ^"-inotin" | ^"-notin" | ^"-inotin" | ^"-cnotin" | ^"-contains" | ^"-ccontains" | ^"-icontains" | ^"-notcontains" | ^"-inotcontains" | ^"-cnotcontains" | ^"-in" | ^"-iin" | ^"-cin" }
type_check_op = { ^"-isnot" | ^"-is" }
simple_operator = _{ "-" | "+" | "*" | "/" | "%" }
value = {
parenthesized_expression
| sub_expression
| array_expression
| script_block_expression
| hash_literal_expression
| literal
| type_literal
| variable
}
parenthesized_expression = !{ "(" ~ pipeline ~ ")"}
sub_expression = !{ "$(" ~ pipeline? ~ ")" }
array_expression = !{ "@(" ~ statements? ~ ")" }
script_block_expression = !{ "{" ~ script_block ~ "}" }
script_block = { script_param_block ~ ";"* ~ script_block_body? }
hash_literal_expression = { "@{" ~ hash_literal_body? ~ "}" }
hash_literal_body = _{ hash_entry* }
hash_entry = { key_expression ~ "=" ~ (type_literal | statement) ~ ";"*}
key_expression = { simple_name | unary_exp }
variable = {
parenthesized_variable
| special_variable
| scoped_variable
| braced_variable
}
parenthesized_variable = { "(" ~ variable ~ ")" }
special_variable = { "$$" | "$^" | "$?" | "$_" }
scoped_variable = { "$" ~ (scope_keyword ~ ":")? ~ var_name }
braced_variable = { "${" ~ braced_variable_content ~ "}" }
var_name = @{ identifier | "?" }
scope_keyword = { ^"global" | ^"local" | ^"private" | ^"script" | ^"using" | ^"workflow" | ^"env" | ^"alias" | ^"function" | ^"variable" }
braced_variable_content = @{ (!"}" ~ ANY)+ }
argument_list = { !redirection ~ expression }
command_ident = @{ ASCII_ALPHANUMERIC ~ (ASCII_ALPHANUMERIC | "-")* }
identifier = @{ (ASCII_ALPHANUMERIC | "_")+ }
qualified_ident = { identifier ~ ("." ~ identifier)* }
simple_name = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
redirections = { redirection+ }
redirection = { merging_redirection_operator | (file_redirection_operator ~ redirected_file_name)}
redirected_file_name = { primary_expression | command_argument }
file_redirection_operator = { ">" | ">>" | "2>" | "2>>" | "3>" | "3>>" | "4>" | "4>>" | "5>" | "5>>" | "6>" | "6>>" | "*>" | "*>>" | "<" }
merging_redirection_operator = { "*>&1" | "2>&1" | "3>&1" | "4>&1" | "5>&1" | "6>&1" | "*>&2" | "1>&2" | "3>&2" | "4>&2" | "5>&2" | "6>&2" }
verbatim_command_arg= { "--%" ~ verbatim_command_argument_chars }
verbatim_command_argument_chars = { verbatim_piece+ }
verbatim_piece = _{
quoted_string
| ampersand_chunk
| generic_verbatim_chunk
}
quoted_string = @{ "\"" ~ (!"\"" ~ ANY)* ~ "\"" }
ampersand_chunk = @{ "&" ~ (!"&" ~ ANY)* }
generic_verbatim_chunk = @{ (!("|" | "\r" | "\n") ~ ANY)+ }
//--------------------STRING LITERAL
string_literal = {
doublequoted_string_literal
| singlequoted_string_literal
| doublequoted_multiline_string_literal
| singlequoted_multiline_string_literal
}
doublequoted_string_literal = ${
"\"" ~ dq_string_content* ~ "\""
}
dq_string_content = ${
dq_escape
| variable
| dq_text_chunk
| sub_expression
| dollar_escape
| backtick_escape
| WHITESPACE
}
dq_escape = @{ "\"\"" }
dollar_escape = @{ "$" ~ (backtick_escape | "\\" )? }
backtick_escape = @{ "`" ~ non_whitespace_char ? } // handle backtick+char like `n
non_whitespace_char = { !WHITESPACE ~ ANY }
dq_text_chunk = { (!("$" | "\"" | "`") ~ ANY)+ }
doublequoted_multiline_string_literal = @{
dq_multiline_string_start ~ dq_multiline_content* ~ dq_multiline_string_end
}
dq_multiline_content = ${
variable
| sub_expression
| backtick_escape
| dollar_escape
| dq_ml_text_chunk
| dq_newline
}
dq_newline = {NEWLINE ~ !"\"@"}
dq_multiline_string_start = @{ "@\"" ~ NEWLINE? }
dq_ml_text_chunk = ${ (!("\"@" | "$" | "`" | NEWLINE) ~ ANY)+ }
dq_multiline_string_end = @{ NEWLINE ~ "\"@" }
singlequoted_string_literal = @{ "'" ~ sq_string_content* ~ "'" }
sq_string_content = _{ "''" | (!"'" ~ ANY)+ }
singlequoted_multiline_string_literal = ${
sq_multiline_start ~ sq_multiline_content* ~ sq_multiline_end
}
sq_multiline_start = _{ "@'" ~ NEWLINE }
sq_multiline_content = @{ ((!("'@" | "\r" | "\n") ~ ANY)+) | sq_newline }
sq_multiline_end = _{ NEWLINE ~ "'@" }
sq_newline = {NEWLINE ~ !"'@"}
//----------------------LITERAL
literal = _{
number_literal
| string_literal
}
number_literal = { additive_op? ~ number ~ (unit ~ ^"b")? }
unit = { ^"k" | ^"m" | ^"g" | ^"t" | ^"p"}
number = ${ float | hex_integer| decimal_integer }
decimal_int = @{ ASCII_DIGIT+}
hex_int = @{HEX_DIGIT+}
decimal_integer = { decimal_int ~ (^"l" | ^"d" )? }
hex_integer = { ^"0x" ~ hex_int ~ ^"l"? }
float = {float_1 | float_2 | float_3}
float_1 = { ASCII_DIGIT+ ~ "." ~ !"." ~ ASCII_DIGIT* ~ float_mantis? }
float_2 = @{ "." ~ ASCII_DIGIT+ ~ float_mantis? }
float_3 = @{ ASCII_DIGIT+ ~ float_mantis }
float_mantis = {^"e" ~ ("+" | "-") ~ ASCII_DIGIT+}
//----------------------TYPE LITERAL
type_literal = { "[" ~ type_spec ~ "]" }
type_name = { type_identifier ~ ("." ~ type_identifier)* }
type_identifier = @{ (ASCII_ALPHANUMERIC | "_")+ }
type_spec = @{
type_name ~ "[" ~ dimension? ~ "]"
| type_name ~ "[" ~ generic_type_arguments ~ "]"
| type_name
}
dimension = { ","+ }
generic_type_arguments = { type_spec ~ ("," ~ type_spec)* }
//-----------------------ATTRIBUTES
attribute_list = { attribute+ }
attribute = { attribute_info | type_literal }
attribute_info = {"[" ~ attribute_name ~ "(" ~ attribute_arguments? ~ ")" ~ "]"}
attribute_name = { type_spec }
attribute_arguments = { attribute_argument ~ ("," ~ attribute_argument)* }
attribute_argument = { expression | (simple_name ~ ("=" ~ expression)?) }
//-----------------------GENERIC TOKEN
generic_token = @{ generic_token_start ~ generic_token_rest* }
generic_token_start = _{
!( ";" | "(" | ")" | "$" | "\"" | "'" | "-" | "{" | "}" | "@" | "|" | "[" | "`" | WHITESPACE | NEWLINE ) ~ ANY
}
generic_token_rest = _{
!(WHITESPACE | "(" | ")" | "{" | "}" | "|" | ";" | "," ) ~ ANY
}
command_token = @{ command_char+ }
command_char = _{ !( "(" | ")" | "{" | "}" | ";" | NEWLINE ) ~ ANY }