oxdock-parser 0.7.0-alpha

Parser and AST definitions for the OxDock DSL.
Documentation
WHITESPACE = _{ " " | "\t" }
linebreak = _{ "\r\n" | "\n" }
COMMENT = _{ line_comment | block_comment }
line_comment = _{ "//" ~ (!linebreak ~ ANY)* }
block_comment = _{ "/*" ~ (block_comment | !"*/" ~ ANY)* ~ "*/" }

// Hash comments are only valid at the start of a line (possibly indented)
hash_comment = _{ WHITESPACE* ~ "#" ~ (!linebreak ~ ANY)* }

script = { SOI ~ element* ~ EOI }

element = _{ blank | hash_comment | semicolon | guard_line | block_start | block_end | command }

blank = _{ linebreak+ }

semicolon = _{ ";" }

block_start = { "{" }

block_end = { "}" }

guard_line = { "[" ~ ws? ~ guard_expr ~ ws? ~ "]" }

guard_expr = { guard_seq }

guard_seq = { guard_factor ~ ( ws? ~ "," ~ ws? ~ guard_factor )* }

guard_factor = { guard_not }

guard_not = { (invert ~ ws?)* ~ guard_primary }

guard_primary = _{ guard_group | guard_or_call | guard_term }

guard_group = { "(" ~ ws? ~ guard_expr ~ ws? ~ ")" }

guard_or_call = { or_kw ~ "(" ~ ws? ~ guard_expr_list ~ ws? ~ ")" }

or_kw = _{ "or" }

guard_expr_list = { guard_expr ~ ( ws? ~ "," ~ ws? ~ guard_expr )* }

guard_term = { ws? ~ guard_predicate ~ ws? }

invert = { "!" }

guard_predicate = _{ env_guard | bare_platform }

env_guard = { env_prefix ~ ws? ~ env_key ~ env_comparison? }

env_prefix = { "env" ~ ":" }

env_key = @{ guard_key_char+ }

guard_key_char = _{ !( ":" | "=" | "!" | "," | "|" | ")" | "]" | linebreak | "//" | "/*" ) ~ ANY }

env_comparison = { not_equals_env | equals_env }

equals_env = { eq_op ~ env_value }

not_equals_env = { neq_op ~ env_value }

env_value = @{ guard_value_char+ }

guard_value_char = _{ !( "," | "|" | ")" | "]" | linebreak | "//" | "/*" ) ~ ANY }


eq_op = { "==" }

neq_op = { "!=" }

bare_platform = { platform_tag }

platform_tag = @{ ASCII_ALPHANUMERIC+ }

command = _{
    inherit_env_command |
    workdir_command |
    workspace_command |
    env_command |
    echo_command |
    run_bg_command | // Must come before run_command to avoid prefix match
    run_command |
    copy_git_command | // Must come before copy_command
    copy_command |
    with_io_command |
    hash_sha256_command |
    symlink_command |
    mkdir_command |
    ls_command |
    cwd_command |
    read_command |
    write_command |
    append_command |
    assert_file_command |
    assert_dir_command |
    assert_absent_command |
    assert_stdout_command |
    exit_command
}
// Commands
workdir_command = ${ "WORKDIR" ~ sep ~ argument }
workspace_command = ${ "WORKSPACE" ~ sep ~ workspace_target }
env_command = ${ "ENV" ~ sep ~ env_pair }
echo_command = ${ "ECHO" ~ sep ~ message }
run_command = ${ "RUN" ~ sep ~ run_args }
run_bg_command = ${ "RUN_BG" ~ sep ~ run_args }
from_current_workspace_flag = { "--from-current-workspace" }
copy_command = ${ "COPY" ~ sep ~ (from_current_workspace_flag ~ sep)? ~ argument ~ sep ~ argument }
with_io_command = ${ "WITH_IO" ~ sep ~ io_flags ~ (sep ~ command)? }
copy_git_command = ${ "COPY_GIT" ~ sep ~ (include_dirty_flag ~ sep)? ~ argument ~ sep ~ argument ~ sep ~ argument }
hash_sha256_command = ${ "HASH_SHA256" ~ sep ~ argument }
symlink_command = ${ "SYMLINK" ~ sep ~ argument ~ sep ~ argument }
mkdir_command = ${ "MKDIR" ~ sep ~ argument }
ls_command = ${ "LS" ~ (sep ~ argument)? }
cwd_command = ${ "CWD" }
read_command = ${ "READ" ~ (sep ~ argument)? }
write_command = ${ "WRITE" ~ sep ~ argument ~ (sep ~ message)? }
append_command = ${ "APPEND" ~ sep ~ argument ~ (sep ~ message)? }

// Verification commands. ASSERT_FILE alternates on explicit forms so the
// --hash digest can never be captured into the path slot, and a missing or
// malformed digest after --hash is a loud parse error rather than a silent skip.
assert_file_command = _{ assert_file_hash_command | assert_file_content_command }
assert_file_hash_command = ${ "ASSERT_FILE" ~ sep ~ "--hash" ~ sep ~ hash_digest ~ sep ~ argument }
assert_file_content_command = ${ "ASSERT_FILE" ~ sep ~ !hash_flag ~ argument ~ (sep ~ message)? }
assert_dir_command = ${ "ASSERT_DIR" ~ sep ~ argument }
assert_absent_command = ${ "ASSERT_ABSENT" ~ sep ~ argument }
assert_stdout_command = ${ "ASSERT_STDOUT" ~ sep ~ message }
hash_flag = { "--hash" }
hash_digest = @{ ASCII_HEX_DIGIT{64} }

exit_command = ${ "EXIT" ~ sep ~ exit_code }

// Inherit environment variables (selective list only). Placed here so it's available
// as a top-level command in the prelude. NOTE: `ALL` form is intentionally omitted.
inherit_env_command = ${ "INHERIT_ENV" ~ sep ~ inherit_list }

inherit_list = { "[" ~ ws? ~ env_key ~ (ws? ~ "," ~ ws? ~ env_key)* ~ ws? ~ "]" }

// Arguments
sep = _{ WHITESPACE+ }
argument = { quoted_string | templated_arg | unquoted_arg }
templated_arg = @{ "{{" ~ (!"}}" ~ ANY)* ~ "}}" }
unquoted_arg = @{ (!WHITESPACE ~ !linebreak ~ !";" ~ ANY)+ }
include_dirty_flag = { "--include-dirty" }
io_flags = { "[" ~ ws? ~ io_binding ~ (ws? ~ "," ~ ws? ~ io_binding)* ~ ws? ~ "]" }
io_binding = { io_stream ~ (ws? ~ "=" ~ ws? ~ pipe_binding)? }
io_stream = { "stdin" | "stdout" | "stderr" }
pipe_binding = { "pipe" ~ ":" ~ pipe_name }
pipe_name = @{ (ASCII_ALPHANUMERIC | "_" | "-")+ }

workspace_target = { "SNAPSHOT" | "LOCAL" | "snapshot" | "local" }
env_pair = { env_key_part ~ ws? ~ "=" ~ ws? ~ env_value_part }
env_key_part = @{ (!"=" ~ !WHITESPACE ~ !linebreak ~ ANY)+ }
env_value_part = { quoted_string | unquoted_env_value }
unquoted_env_value = @{ (!WHITESPACE ~ !linebreak ~ !";" ~ ANY)+ }

message = { (quoted_string | unquoted_msg_content)+ }
unquoted_msg_content = { !(linebreak | ";") ~ ANY }

run_args = { (quoted_string | unquoted_run_content)+ }
unquoted_run_content = { !(linebreak | "//" | "/*" | ";") ~ ANY }

exit_code = @{ ASCII_DIGIT+ }

quoted_string = @{
    "\"" ~ ( "\\\"" | (!"\"" ~ ANY) )* ~ "\"" |
    "'" ~ ( "\\'" | (!"'" ~ ANY) )* ~ "'"
}

ws = _{ (WHITESPACE | linebreak)* }