///|
/// The kind of a token in the lossless syntax tree. All four body languages
/// share these kinds.
pub(all) enum TokenKind {
/// `@startregex`, `@endjson` and similar block markers.
Marker
/// A statement keyword such as `title` or `skinparam`.
Keyword
/// A rule name, member key or group name.
Name
/// Free text: a title, a note or an unquoted value.
Text
/// A quoted string, including its quotes.
String
Number
/// `true`, `false` or `null`.
Literal
/// An escape sequence.
Escape
/// An operator or a quantifier.
Operator
/// Brackets, separators and other punctuation.
Punctuation
Comment
Whitespace
Newline
/// A preprocessor directive or function call.
Directive
/// Text that could not be read.
Error
} derive(Eq, Debug)
///|
pub extend TokenKind with Eq::{not_equal, equal}
///|
pub extend TokenKind with @debug.Debug::{to_repr}
///|
/// One token. `offset` is the UTF-16 offset of its first code unit in the
/// source. The concatenated token texts of a tree are equal to the source.
pub struct Token {
kind : TokenKind
text : String
offset : Int
} derive(Eq, Debug)
///|
pub extend Token with Eq::{not_equal, equal}
///|
pub extend Token with @debug.Debug::{to_repr}
///|
pub fn Token::new(kind : TokenKind, text : String, offset : Int) -> Token {
{ kind, text, offset }
}
///|
/// The kind of a node. Each source line, or each multi-line statement, is one
/// node under the document.
pub(all) enum NodeKind {
Document
Start
End
Title
Caption
Header
Footer
Legend
Skinparam
Style
Pragma
Scale
Hide
Comment
Directive
Highlight
/// A regex pattern line.
Pattern
/// An EBNF rule.
Rule
/// An EBNF note.
Note
/// JSON or YAML data.
Data
Blank
/// Text after the end marker.
Trailing
Error
} derive(Eq, Debug)
///|
pub extend NodeKind with Eq::{not_equal, equal}
///|
pub extend NodeKind with @debug.Debug::{to_repr}
///|
pub(all) enum Element {
Token(Token)
Node(Node)
} derive(Eq, Debug)
///|
/// A node of the lossless syntax tree.
pub struct Node {
kind : NodeKind
children : ReadOnlyArray[Element]
} derive(Eq, Debug)
///|
pub extend Element with Eq::{not_equal, equal}
///|
pub extend Element with @debug.Debug::{to_repr}
///|
pub extend Node with Eq::{not_equal, equal}
///|
pub extend Node with @debug.Debug::{to_repr}
///|
pub fn Node::new(kind : NodeKind, children : ArrayView[Element]) -> Node {
{ kind, children: ReadOnlyArray::from_array(children) }
}
///|
/// All tokens of the node in source order, without recursion.
pub fn Node::tokens(self : Node) -> Array[Token] {
let out = []
let stack : Array[Element] = self.children.rev_iter().collect()
while stack.pop() is Some(element) {
match element {
Token(token) => out.push(token)
Node(node) => for child in node.children.rev_iter() { stack.push(child) }
}
}
out
}
///|
/// The source text of the node.
pub fn Node::text(self : Node) -> String {
let out = StringBuilder()
for token in self.tokens() {
out.write_string(token.text)
}
out.to_string()
}
///|
/// The child nodes of the node.
pub fn Node::nodes(self : Node) -> Array[Node] {
let out = []
for child in self.children {
if child is Node(node) {
out.push(node)
}
}
out
}
///|
/// The half-open UTF-16 range of the node, or `None` for an empty node.
pub fn Node::range(self : Node) -> (Int, Int)? {
let tokens = self.tokens()
match (tokens.get(0), tokens.last()) {
(Some(first), Some(last)) =>
Some((first.offset, last.offset + last.text.length()))
_ => None
}
}
///|
/// A line index of a source text for UTF-16 positions.
pub struct Lines {
priv starts : Array[Int]
priv length : Int
}
///|
pub fn Lines::new(source : String) -> Lines {
let starts = [0]
for i in 0.. @diagrams.Position {
let offset = offset.max(0).min(self.length)
let mut low = 0
let mut high = self.starts.length() - 1
while low < high {
let middle = (low + high + 1) / 2
if self.starts[middle] <= offset {
low = middle
} else {
high = middle - 1
}
}
{ offset, line: low + 1, column: offset - self.starts[low] + 1 }
}
///|
pub fn Lines::span(self : Lines, start : Int, end : Int) -> @diagrams.Span {
{ start: self.position(start), end: self.position(end) }
}
///|
/// The span of a half-open UTF-16 range of `source`.
pub fn span(source : String, start : Int, end : Int) -> @diagrams.Span {
Lines::new(source).span(start, end)
}
///|
/// The owned text of the half-open UTF-16 range `start` to `end` of `text`.
pub fn slice(text : String, start : Int, end : Int) -> String {
text.view(start_offset=start, end_offset=end).to_owned()
}