** Represents a PEG match result.
@Js
class Match {
private Result result
private Str in
** Storage for user data - use when investigating match results.
[Str:Obj?]? data
internal new make(Result result, Str in) {
this.result = result
this.in = in
}
** Returns the associated rule 'label' (or 'name' if 'label' is 'null').
Str? name() {
rule.label ?: rule.name
}
// Meh, it's too complicated distinguishing between the two in switch statements (see HTML parser)
// much easy to munge the two into "name" - users can always query the Rule if need be
// ** Returns the associated rule label, or name if there is no label.
// Str? label() {
// rule.label ?: rule.name
// }
** Returns 'true' if a there's a direct sub-match with the given rule label or name.
Bool contains(Str label) {
getMatch(label) != null
}
** Deeply searches for a labelled Match of the given name at the given index.
**
** Negative indexes can not be used.
Match? findMatch(Str label, Int index := 0) {
if (this.name == label)
return this
i := 0
return matches.eachWhile |match| {
gotOne := match.findMatch(label)
// keep counting until we find the match at the given index
if (gotOne != null && i++ < index)
gotOne = null
return gotOne
}
}
** Returns the direct sub-match with the given label (or rule name) at the given index.
**
** Use negative index to access from the end of the list.
@Operator
Match? getMatch(Str label) {
result.getMatch(label, in)
}
** Returns the direct sub-match at the given index.
@Operator
Match? getMatchAt(Int index) {
matches[index]
}
** Returns the first direct sub-match.
Match? firstMatch() {
matches.first
}
** Returns all direct sub-matches, optionally with the given label / name.
Match[] matches(Str? label := null) {
label == null ? result.matches(in) : result.getMatches(label, in)
}
** Returns the matched string.
Str matched() {
result.matchedStr(in)
}
** Returns the matched range; where this match lies within the input string.
Range matchedRange() {
result.matchedRange(in)
}
** Returns a snippet of source code, centred on this match.
Str snippet(Str? msg := null, Int? padding := null) {
result.matchedSnippet(in, msg, padding)
}
** Returns the '[x,y]' character location of this match in the input string.
** Coordinates are one-based.
Int[] location() {
result.matchedLocation(in)
}
** Returns the line number of this match in the input string.
** Number is one-based.
Int lineNum() {
result.matchedLineNum(in)
}
** Returns the parent Match in the tree, or 'null' if this is the root.
Match? parent() {
result.parent?.match(null, in) // null is fine, as we've already set the parent
}
** Returns the rule associated with this match.
Rule rule() {
result.rule
}
** Dumps the matched tree to std-out.
** Useful for debugging.
This dump(OutStream out := Env.cur.out) {
out.printLine(debugStr)
return this
}
** Returns the matched tree as a string.
Str debugStr() {
buf := StrBuf(in.size * 5)
doDump(buf, Bool[,])
return buf.toStr
}
// Meh - I don't actually like this - and it's often easier to just itr over the matches yourself
// ** Walks the match tree, calling the given funcs as it steps in to, and out of, a 'Match'.
// ** Start with *this* 'Match'.
// Void walk(|Match|? start, |Match|? end) {
// start?.call(this)
// matches.each { it.walk(start, end) }
// end?.call(this)
// }
@NoDoc
override Str toStr() { matched }
// chars from `https://atom.io/packages/ascii-tree`
private Void doDump(StrBuf buf, Bool[] indent) {
// tidy up dumps, so we exclude names (in special cases)
ruleName := rule is RuleRef && rule->realRule->useInResult == false ? null : rule.name
name := [rule.label, ruleName].exclude { it == null }.join(":").trimToNull ?: "???:???"
buf.add(name ?: rule.expression)
matches := matches
if (matches.isEmpty)
buf.addChar(' ').addChar(':').addChar(' ').add(matched.toCode)
buf.addChar('\n')
matches.each |match, x| {
indent.each { buf.addChar(' ').addChar(it ? '│' : ' ').addChar(' ').addChar(' ') }
buf.addChar(' ').addChar(matches.size-1 == x ? '└' : '├').addChar('─').addChar(' ')
indent.push(x != matches.size-1)
match.doDump(buf, indent)
indent.pop
}
}
}