sangria.parser.PositionTracking.scala Maven / Gradle / Ivy
The newest version!
package sangria.parser
import org.parboiled2._
import sangria.ast.AstLocation
import scala.annotation.tailrec
private[parser] trait PositionTracking { this: Parser =>
/** The indices (offsets into the source code) of the first characters of each line. */
private[this] var lineIdx = Array(0)
def parseLocations: Boolean
def sourceId: String
def trackNewLine: Rule0 = rule {
test(parseLocations) ~ run {
if (!contains(lineIdx, cursor)) lineIdx = lineIdx :+ cursor
} | MATCH
}
def trackPos: Rule1[Option[AstLocation]] = rule {
test(parseLocations) ~ push {
val (line, lastCursor) = findLastItem(lineIdx, cursor)
Some(AstLocation(sourceId, cursor, line, cursor - lastCursor + 1))
} | push(None)
}
/** Returns true if the array contains the given item.
*
* Assumes that the array is sorted in ascending order.
*/
private def contains(arr: Array[Int], item: Int) = {
@tailrec def go(i: Int): Boolean =
if (i < 0) false
else if (arr(i) < item) false // no remaining values will match as the array is sorted
else if (arr(i) == item) true
else go(i - 1)
go(arr.length - 1)
}
/** Returns the first item that is less than or equal to the given item along with the number of
* elements that come before it.
*
* Assumes that the array is sorted in ascending order.
*/
private def findLastItem(arr: Array[Int], item: Int) = {
@tailrec def go(i: Int, last: Int): (Int, Int) =
if (i < 0) (i + 1, last)
else if (arr(i) <= item) (i + 1, arr(i))
else go(i - 1, arr(i))
go(arr.length - 1, 0)
}
}