teacat_lib 0.5.2

Tools for working with TeaCat files
Documentation
// 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
}