///|
/// A position in the input: a UTF-16 code-unit offset plus 1-based line and
/// column (lines are only broken by `\n`, as in PEG.js).
pub(all) struct Pos {
offset : Int
line : Int
column : Int
} derive(Eq, Debug)
///|
/// A span of the input, optionally tagged with the source file name.
pub(all) struct Location {
filename : String?
start : Pos
end : Pos
} derive(Eq, Debug)
///|
pub impl ToJson for Pos with fn to_json(self) {
{ "offset": self.offset, "line": self.line, "column": self.column }
}
///|
pub impl ToJson for Location with fn to_json(self) {
let obj : Map[String, Json] = Map([])
if self.filename is Some(f) {
obj["filename"] = f.to_json()
}
obj["start"] = self.start.to_json()
obj["end"] = self.end.to_json()
Json::object(obj)
}
///|
/// Incremental offset -> line/column computation with the same caching
/// strategy as the generated `peg$computePosDetails`.
pub struct PosCache {
input : String
details : Map[Int, (Int, Int)]
filename : String?
}
///|
pub fn PosCache::new(input : String, filename? : String) -> PosCache {
{ input, details: { 0: (1, 1) }, filename, }
}
///|
pub fn PosCache::pos(self : PosCache, offset : Int) -> Pos {
match self.details.get(offset) {
Some((line, column)) => { offset, line, column, }
None => {
let mut p = offset - 1
while !self.details.contains(p) {
p -= 1
}
let (line0, column0) = self.details[p]
let mut line = line0
let mut column = column0
while p < offset {
if p < self.input.length() && self.input.code_unit_at(p) == '\n' {
line += 1
column = 1
} else {
column += 1
}
p += 1
}
self.details[offset] = (line, column)
{ offset, line, column, }
}
}
}
///|
pub fn PosCache::location(self : PosCache, start : Int, end : Int) -> Location {
{ filename: self.filename, start: self.pos(start), end: self.pos(end), }
}
///|
/// The location as a `Value` object, shaped like PEG.js' location objects.
pub fn[T] Location::to_value(self : Location) -> Value[T] {
fn pos(p : Pos) -> Value[T] {
Obj({
"offset": Num(p.offset.to_double()),
"line": Num(p.line.to_double()),
"column": Num(p.column.to_double()),
})
}
let obj : Map[String, Value[T]] = Map([])
if self.filename is Some(f) {
obj["filename"] = Str(f)
}
obj["start"] = pos(self.start)
obj["end"] = pos(self.end)
Obj(obj)
}
///|
pub extend Pos with ToJson::{to_json}
///|
pub extend Location with ToJson::{to_json}