// Comment handling
COMMENT = _{ multi_line_comment | single_line_comment }
multi_line_comment = _{ "#" ~ ws_sameline* ~ multi_line_comment_inner }
multi_line_comment_inner = _{
"["
~ ( (!("[" | "]") ~ ANY) | multi_line_comment_inner )*
~ "]"
}
single_line_comment = _{ "#" ~ (!ws_newline ~ ANY)* ~ &eol }
/// Whitespace that does not introduce a newline
ws_sameline = _{ SPACE_SEPARATOR | "\t" }
/// Whitespace that introduces newlines
ws_newline = _{ "\n" }
/// All whitespace, including (but not limited to) newlines
ws_all = _{ WHITE_SPACE }
/// End of the line, either through an EOI or a newline
eol = _{ ws_newline | EOI }
esc_unicode = { "u[" ~ ASCII_HEX_DIGIT{1, 6} ~ "]" }
esc_ascii = { "n" | "t" | "s" }
esc_tc = {
esc_tc_required
| markup_symbol
| header_symbol
| "("
| ")"
| "{"
| "}"
| "|"
| ":"
| "@"
| "."
| "\""
}
esc_tc_required = ${
"#"
| "["
| "]"
| (":" ~ &ident)
| ("@" ~ &ident)
}
esc = { "\\" ~ (esc_unicode | esc_ascii | esc_tc) }
char = { (!ws_all ~ ANY) }
/// Sequence of non-whitespace characters
word = ${ (!(esc_tc_required | markup | PEEK) ~ (esc | char))+ }
/// Sequence of non-newline whitespace characters
space = { ws_sameline+ }
/// A double-quoted string for use in tag arguments
string = ${ "\"" ~ string_inner ~ "\"" }
string_inner = @{ (!"\"" ~ (esc | ANY))* }
/// Symbols that can be used for inline markup
markup_symbol = { "+" | "*" | "~" | "_" }
/// Symbol that denotes a header
header_symbol = { "="{1, 6} }
/// A header that can only appear at the start of a line
header = { PUSH(header_symbol) ~ line_content ~ PEEK? ~ DROP ~ &eol }
/// Text using markup syntax rather than tag-based syntax
markup = { PUSH(markup_symbol) ~ line_content ~ POP }
/// A unicode-xid identifier
ident = ${ XID_START ~ XID_CONTINUE* }
/// A tag opening that's allowed to stretch over multiple lines
tag_op_multiline = ${ ":" ~ ident ~ ws_sameline* ~ tag_args_multiline? ~ ws_sameline* }
/// A collection of multiline tag arguments, enclosed in parentheses
tag_args_multiline = !{ "(" ~ (tag_arg_multiline ~ ("," ~ tag_arg_multiline)* ~ ","?)? ~ ")" }
/// A single multiline tag argument
tag_arg_multiline = { ws_all* ~ ident ~ ws_all* ~ string ~ ws_all* }
/// A tag opening that may only cover a single line
tag_op_sameline = ${ ":" ~ ident ~ ws_sameline* ~ tag_args_sameline? ~ ws_sameline* }
/// A collection of sameline tag arguments, enclosed in parentheses
tag_args_sameline = !{ "(" ~ (tag_arg_sameline ~ ("," ~ tag_arg_sameline)* ~ ","?)? ~ ")" }
/// A single sameline tag argument
tag_arg_sameline = { ws_sameline* ~ ident ~ ws_sameline* ~ string ~ ws_sameline* }
/// A tag that excludes square brackets and ends with a newline
tag_sameline = { tag_op_multiline ~ line_content ~ &eol }
/// The "standard" tag - can contain multiple lines, including all other tag types
tag_multiline = { tag_op_multiline ~ "[" ~ line_sequence ~ "]" }
/// A tag that has square brackets, but no newlines
tag_inline = { tag_op_sameline ~ "[" ~ line_content ~ "]" }
/// A tag type which is simply treated as a raw sequence of text
tag_raw = ${
tag_op_multiline
~ (PUSH( "|"+ ) ~ "[")
~ tag_raw_inner
~ ("]" ~ POP)
}
tag_raw_inner = ${ ( !("]" ~ PEEK) ~ ANY )* }
/// A block of content, which is flattened when rendered
content_block = { ":[" ~ line_sequence ~ "]" }
/// A block of content which may not contain newlines
content_block_inline = { ":[" ~ line_content ~ "]" }
/// An @def statement
def = {
"@def" ~ ws_sameline+
~ ident ~ ws_sameline*
~ "<-" ~ ws_sameline*
~ def_content
}
def_content = { tag_raw | tag_multiline | content_block }
/// An @with statement
with = {
"@with" ~ ws_sameline+
~ (ident ~ "." ~ ws_sameline*)+
~ ("(" ~ ws_all* ~ with_content? ~ ws_all* ~ ")")
}
with_content = { ident ~ ws_all* ~ ("," ~ ws_all* ~ ident ~ ws_all*)* ~ ","? }
/// An @module statement
module = { ws_all* ~ "@module" ~ ws_sameline* ~ eol }
/// A variable use, such as `@my_variable`
var = ${ "@" ~ ident }
/// A single line
line = { ws_sameline* ~ (
header
| def
| with
| tag_raw
| tag_multiline
| tag_sameline
| content_block
| line_content
) }
/// Content of a single line
line_content = { (
var
| tag_inline
| content_block_inline
| markup
| word
| space
)* }
/// A sequence of lines separated by newlines
line_sequence = { line ~ ("\n" ~ line)* ~ "\n"? }
teacat = {
SOI
~ PUSH_LITERAL("\0") // Literal must be pushed here, empty stack will panic
~ module?
~ line_sequence
~ EOI
}