///|
/// A downstream event consumer used by parsers, rules, renderers, and transforms.
pub struct Sink {
priv emit_event : (Event) -> Unit
priv finish_stream : () -> Unit
}
///|
/// Creates an event sink.
pub fn Sink::Sink(
emit : (Event) -> Unit,
finish? : () -> Unit = fn() { () },
) -> Sink {
{ emit_event: emit, finish_stream: finish }
}
///|
/// Emits an event downstream.
pub fn Sink::emit(self : Sink, event : Event) -> Unit {
(self.emit_event)(event)
}
///|
/// Finishes the stream and lets downstream consumers flush buffered state.
pub fn Sink::finish(self : Sink) -> Unit {
(self.finish_stream)()
}
///|
/// Context passed to block rules.
pub struct BlockContext {
priv lines : Array[String]
priv line_starts : Array[Int]
priv index : Int
priv registry : RuleRegistry
priv plan : PreparedRulePlan
priv inline_rules : Array[RuleEntry]
priv inline_mode : InlineParseMode
priv inline_trigger_chars : String
priv state : ParseState
priv collect_only : Bool
}
///|
/// Context passed to inline and delimiter rules.
pub struct InlineContext {
priv text : String
priv mut index : Int
priv registry : RuleRegistry
priv plan : PreparedRulePlan
priv inline_rules : Array[RuleEntry]
priv inline_mode : InlineParseMode
priv inline_trigger_chars : String
priv state : ParseState
}
///|
/// Parser services exposed to rule implementations.
pub struct ParserServices {
priv registry : RuleRegistry
priv plan : PreparedRulePlan
priv inline_rules : Array[RuleEntry]
priv inline_mode : InlineParseMode
priv inline_trigger_chars : String
priv state : ParseState
}
///|
fn ParserServices::ParserServices(
registry : RuleRegistry,
plan : PreparedRulePlan,
inline_rules : Array[RuleEntry],
inline_mode : InlineParseMode,
inline_trigger_chars : String,
state : ParseState,
) -> ParserServices {
{ registry, plan, inline_rules, inline_mode, inline_trigger_chars, state }
}
///|
/// Returns the line currently being considered by a block rule.
pub fn BlockContext::current_line(self : BlockContext) -> String {
if self.index >= 0 && self.index < self.lines.length() {
self.lines[self.index]
} else {
""
}
}
///|
/// Returns the source line at `offset` relative to the current block line.
pub fn BlockContext::line(self : BlockContext, offset : Int) -> String? {
self.line_at(self.index + offset)
}
///|
/// Returns the source line at `index`, if it exists.
pub fn BlockContext::line_at(self : BlockContext, index : Int) -> String? {
if index >= 0 && index < self.lines.length() {
Some(self.lines[index])
} else {
None
}
}
///|
/// Returns the total number of source lines in this block parse.
pub fn BlockContext::line_count(self : BlockContext) -> Int {
self.lines.length()
}
///|
/// Returns all source lines visible to this block parse.
pub fn BlockContext::source_lines(self : BlockContext) -> Array[String] {
self.lines
}
///|
/// Returns parser offsets for all source lines visible to this block parse.
pub fn BlockContext::source_line_starts(self : BlockContext) -> Array[Int] {
self.line_starts
}
///|
/// Returns the current zero-based source line index.
pub fn BlockContext::line_index(self : BlockContext) -> Int {
self.index
}
///|
/// Returns the parser offset where the current source line starts.
pub fn BlockContext::current_line_start(self : BlockContext) -> Int {
if self.index >= 0 && self.index < self.line_starts.length() {
self.line_starts[self.index]
} else {
0
}
}
///|
/// Returns the parser offset where the source line at `offset` starts.
pub fn BlockContext::source_offset(self : BlockContext, offset : Int) -> Int {
let index = self.index + offset
if index >= 0 && index < self.line_starts.length() {
self.line_starts[index]
} else {
0
}
}
///|
/// Returns parser services for nested parsing from a block rule.
pub fn BlockContext::services(self : BlockContext) -> ParserServices {
ParserServices(
self.registry,
self.plan,
self.inline_rules,
self.inline_mode,
self.inline_trigger_chars,
self.state,
)
}
///|
/// Returns whether the parser is currently collecting definitions.
pub fn BlockContext::is_collecting(self : BlockContext) -> Bool {
self.collect_only
}
///|
/// Returns the full inline source being scanned.
pub fn InlineContext::source(self : InlineContext) -> String {
self.text
}
///|
/// Returns the current parser offset in the inline source.
pub fn InlineContext::offset(self : InlineContext) -> Int {
self.index
}
///|
/// Returns a scanner positioned at the current inline offset.
pub fn InlineContext::scanner(self : InlineContext) -> InlineScanner {
inline_scanner(self.text, self.index)
}
///|
/// Returns whether any inline rule declares the character at `index` as a
/// trigger. Text rules can use this to leave extension starts visible to the
/// extension rule instead of consuming them as ordinary text.
pub fn InlineContext::has_inline_trigger_at(
self : InlineContext,
index : Int,
) -> Bool {
if index < 0 || index >= self.text.length() {
return false
}
let c = char_at(self.text, index)
contains_char(self.inline_trigger_chars, c)
}
///|
/// Returns whether any inline rule declares a trigger outside `ignored`.
///
/// Text rules can use this to keep their own syntax characters on the fast
/// path while still yielding to extension rules with new trigger characters.
pub fn InlineContext::has_inline_triggers_other_than(
self : InlineContext,
ignored : String,
) -> Bool {
if ignored == commonmark_inline_trigger_chars() {
self.plan.inline_has_triggers_outside_commonmark
} else {
inline_has_triggers_other_than(self.inline_trigger_chars, ignored)
}
}
///|
/// Returns parser services for nested parsing from an inline rule.
pub fn InlineContext::services(self : InlineContext) -> ParserServices {
ParserServices(
self.registry,
self.plan,
self.inline_rules,
self.inline_mode,
self.inline_trigger_chars,
self.state,
)
}
///|
/// Parses inline text with the current inline rule set into `sink`.
pub fn ParserServices::parse_inline(
self : ParserServices,
text : String,
sink : Sink,
) -> Unit {
parse_inlines_with_rules(
text,
self.registry,
self.plan,
self.inline_rules,
self.inline_mode,
self.inline_trigger_chars,
sink,
self.state,
)
}
///|
/// Parses inline text with the current inline rule set and returns a fragment.
pub fn ParserServices::parse_inline_fragment(
self : ParserServices,
text : String,
) -> EventFragment {
let events = Events()
self.parse_inline(text, events.owned_sink())
events.into_fragment()
}
///|
/// Parses block lines with the current rule set into `sink`.
pub fn ParserServices::parse_block_lines(
self : ParserServices,
lines : Array[String],
line_starts : Array[Int],
sink : Sink,
) -> Unit {
parse_block_lines_into(lines, line_starts, self.plan, sink, self.state)
}
///|
/// Runs block rules in definition-collection mode over `lines`.
pub fn ParserServices::collect_block_lines(
self : ParserServices,
lines : Array[String],
line_starts : Array[Int],
) -> Unit {
parse_block_lines_into(
lines,
line_starts,
self.plan,
Sink(fn(_) { () }),
self.state,
collect_only=true,
)
}
///|
/// Returns a string value from parser state.
pub fn ParserServices::state_string(
self : ParserServices,
key : StateKey,
item : String,
) -> String? {
self.state.string_get(key, item)
}
///|
/// Stores a string value in parser state.
pub fn ParserServices::set_state_string(
self : ParserServices,
key : StateKey,
item : String,
value : String,
) -> Unit {
self.state.string_set(key, item, value)
}
///|
/// A small scanner for inline rule implementations.
pub struct InlineScanner {
priv text : String
priv index : Int
}
///|
/// Creates an inline scanner positioned at `index`.
pub fn InlineScanner::new(text : String, index : Int) -> InlineScanner? {
if index < 0 || index > text.length() {
None
} else {
Some(inline_scanner(text, index))
}
}
///|
fn inline_scanner(text : String, index : Int) -> InlineScanner {
{ text, index }
}
///|
/// Returns whether the scanner position starts with `prefix`.
pub fn InlineScanner::has_prefix(self : InlineScanner, prefix : String) -> Bool {
has_prefix_at(self.text, prefix, self.index)
}
///|
/// Returns the character code at the scanner position.
pub fn InlineScanner::char(self : InlineScanner) -> Int? {
if self.index >= 0 && self.index < self.text.length() {
Some(self.text[self.index].to_int())
} else {
None
}
}
///|
/// Returns the character code at an offset from the scanner position.
pub fn InlineScanner::char_at_offset(
self : InlineScanner,
offset : Int,
) -> Int? {
let target = self.index + offset
if target >= 0 && target < self.text.length() {
Some(self.text[target].to_int())
} else {
None
}
}
///|
/// Returns a slice relative to the scanner position.
pub fn InlineScanner::slice(
self : InlineScanner,
start_offset : Int,
end_offset : Int,
) -> String? {
let start = self.index + start_offset
let end = self.index + end_offset
if start < 0 || end < start || end > self.text.length() {
None
} else {
Some(self.text[start:end].to_owned())
}
}
///|
/// Finds `needle` from the scanner position and returns a relative offset.
pub fn InlineScanner::find_char(self : InlineScanner, needle : Int) -> Int? {
for i in self.index..