mirror of https://github.com/sbt/sbt.git
work on parser error handling
This commit is contained in:
parent
d35d76d204
commit
daa58adc85
|
|
@ -29,8 +29,8 @@ object Act
|
|||
ScopedKey( Scope( toAxis(proj, Global), toAxis(conf map ConfigKey.apply, Global), task, extra), key )
|
||||
}
|
||||
}
|
||||
def examplesStrict(p: Parser[String], exs: Set[String]): Parser[String] =
|
||||
p examples exs filter exs
|
||||
def examplesStrict(p: Parser[String], exs: Set[String], label: String): Parser[String] =
|
||||
p !!! ("Expected " + label) examples exs filter(exs, Command.invalidValue(label, exs))
|
||||
|
||||
def optionalAxis[T](p: Parser[T], ifNone: ScopeAxis[T]): Parser[ScopeAxis[T]] =
|
||||
p.? map { opt => toAxis(opt, ifNone) }
|
||||
|
|
@ -40,7 +40,7 @@ object Act
|
|||
thisProject in project get data map( _.configurations.map(_.name)) getOrElse Nil
|
||||
|
||||
def config(confs: Set[String]): Parser[Option[String]] =
|
||||
token( (examplesStrict(ID, confs) | GlobalString) <~ ':' ).?
|
||||
token( (examplesStrict(ID, confs, "configuration") | GlobalString) <~ ':' ).?
|
||||
|
||||
def configs(explicit: Option[String], defaultConfigs: Option[ResolvedReference] => Seq[String], proj: Option[ResolvedReference]): List[Option[String]] =
|
||||
explicit match
|
||||
|
|
@ -54,7 +54,7 @@ object Act
|
|||
{
|
||||
val confMap = confs map { conf => (conf, index.keys(proj, conf)) } toMap;
|
||||
val allKeys = (Set.empty[String] /: confMap.values)(_ ++ _)
|
||||
token(ID examples allKeys).flatMap { keyString =>
|
||||
token(ID !!! "Expected key" examples allKeys).flatMap { keyString =>
|
||||
val conf = confMap.flatMap { case (key, value) => if(value contains keyString) key :: Nil else Nil } headOption;
|
||||
getKey(keyMap, keyString, k => (k, conf flatMap idFun))
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ object Act
|
|||
def getKey[T](keyMap: Map[String,AttributeKey[_]], keyString: String, f: AttributeKey[_] => T): Parser[T] =
|
||||
keyMap.get(keyString) match {
|
||||
case Some(k) => success(f(k))
|
||||
case None => failure("Invalid key: " + keyString)
|
||||
case None => failure(Command.invalidValue("key", keyMap.keys)(keyString))
|
||||
}
|
||||
|
||||
val spacedComma = token(OptSpace ~ ',' ~ OptSpace)
|
||||
|
|
@ -82,41 +82,44 @@ object Act
|
|||
}
|
||||
|
||||
def taskAxisParser(knownKeys: Map[String, AttributeKey[_]]): Parser[AttributeKey[_]] =
|
||||
token("for" ~ Space) ~> knownIDParser(knownKeys)
|
||||
token("for" ~ Space) ~> knownIDParser(knownKeys, "key")
|
||||
|
||||
def extrasParser(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[AttributeMap] =
|
||||
{
|
||||
val validKeys = knownKeys.filter { case (_, key) => knownValues get key exists(!_.isEmpty) }
|
||||
if(validKeys.isEmpty)
|
||||
failure("")
|
||||
failure("No valid extra keys.")
|
||||
else
|
||||
rep1sep( extraParser(validKeys, knownValues), spacedComma) map AttributeMap.apply
|
||||
}
|
||||
|
||||
def extraParser(knownKeys: Map[String, AttributeKey[_]], knownValues: IMap[AttributeKey, Set]): Parser[AttributeEntry[_]] =
|
||||
{
|
||||
val keyp = knownIDParser(knownKeys) <~ token(':' ~ OptSpace)
|
||||
val keyp = knownIDParser(knownKeys, "Not a valid extra key") <~ token(':' ~ OptSpace)
|
||||
keyp flatMap { case key: AttributeKey[t] =>
|
||||
val valueMap: Map[String,t] = knownValues(key).map( v => (v.toString, v)).toMap
|
||||
knownIDParser(valueMap) map { value => AttributeEntry(key, value) }
|
||||
knownIDParser(valueMap, "extra value") map { value => AttributeEntry(key, value) }
|
||||
}
|
||||
}
|
||||
def knownIDParser[T](knownKeys: Map[String, T]): Parser[T] =
|
||||
token(examplesStrict(ID, knownKeys.keys.toSet)) map knownKeys
|
||||
def knownIDParser[T](knownKeys: Map[String, T], label: String): Parser[T] =
|
||||
token(examplesStrict(ID, knownKeys.keys.toSet, label)) map knownKeys
|
||||
|
||||
def projectRef(index: KeyIndex, currentBuild: URI): Parser[Option[ResolvedReference]] =
|
||||
{
|
||||
def projectID(uri: URI) = token( examplesStrict(ID, index projects uri) <~ '/' )
|
||||
def projectID(uri: URI) = token( examplesStrict(ID, index projects uri, "project ID") )
|
||||
def some[T](p: Parser[T]): Parser[Option[T]] = p map { v => Some(v) }
|
||||
|
||||
val uris = index.buildURIs
|
||||
val resolvedURI = Uri(uris).map(uri => Scope.resolveBuild(currentBuild, uri))
|
||||
val buildRef = token( '[' ~> resolvedURI <~ "]/" ) map { uri => BuildRef(uri) }
|
||||
val buildRef = token( '{' ~> resolvedURI <~ '}' ).?
|
||||
val global = token(GlobalString <~ '/') ^^^ None
|
||||
val build = token( '(' ~> resolvedURI <~ ')') ?? currentBuild
|
||||
val projectRef = for(uri <- build; id <- projectID(uri)) yield ProjectRef(uri, id)
|
||||
def projectRef(uri: URI) = (projectID(uri) <~ '/') map { id => ProjectRef(uri, id) }
|
||||
val resolvedRef = buildRef flatMap {
|
||||
case None => projectRef(currentBuild)
|
||||
case Some(uri) => projectRef(uri) ?? BuildRef(uri)
|
||||
}
|
||||
|
||||
global | some(buildRef) | some(projectRef)
|
||||
global | some(resolvedRef <~ '/')
|
||||
}
|
||||
def optProjectRef(index: KeyIndex, current: ProjectRef): Parser[Option[ResolvedReference]] =
|
||||
projectRef(index, current.build) ?? Some(current)
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ final object Aggregation
|
|||
def valueParser(s: State, structure: BuildStructure, show: Boolean)(key: ScopedKey[_]): Parser[() => State] =
|
||||
getTasks(key, structure, true).toList match
|
||||
{
|
||||
case Nil => failure("Invalid setting or task")
|
||||
case Nil => failure("No such setting/task: " + (Project display key))
|
||||
case xs @ KeyValue(_, _: InputStatic[t]) :: _ => applyTasks(s, structure, maps(xs.asInstanceOf[Values[InputStatic[t]]])(_.parser(s)), show)
|
||||
case xs @ KeyValue(_, _: InputDynamic[t]) :: _ => applyDynamicTasks(s, structure, xs.asInstanceOf[Values[InputDynamic[t]]], show)
|
||||
case xs @ KeyValue(_, _: Task[t]) :: _ => applyTasks(s, structure, maps(xs.asInstanceOf[Values[Task[t]]])(x => success(x)), show)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import complete.{DefaultParsers, Parser}
|
||||
import complete.{DefaultParsers, EditDistance, Parser}
|
||||
import CommandSupport.logger
|
||||
|
||||
sealed trait Command {
|
||||
|
|
@ -77,7 +77,10 @@ object Command
|
|||
|
||||
def simpleParser(commandMap: Map[String, State => Parser[() => State]]): State => Parser[() => State] =
|
||||
(state: State) => token(OpOrID examples commandMap.keys.toSet) flatMap { id =>
|
||||
(commandMap get id) match { case None => failure("No command named '" + id + "'"); case Some(c) => c(state) }
|
||||
(commandMap get id) match {
|
||||
case None => failure(invalidValue("command", commandMap.keys)(id))
|
||||
case Some(c) => c(state)
|
||||
}
|
||||
}
|
||||
|
||||
def process(command: String, state: State): State =
|
||||
|
|
@ -86,17 +89,17 @@ object Command
|
|||
Parser.result(parser(state), command) match
|
||||
{
|
||||
case Right(s) => s() // apply command. command side effects happen here
|
||||
case Left((msg,pos)) =>
|
||||
val errMsg = commandError(command, msg, pos)
|
||||
case Left((msgs,pos)) =>
|
||||
val errMsg = commandError(command, msgs, pos)
|
||||
logger(state).error(errMsg)
|
||||
state.fail
|
||||
}
|
||||
}
|
||||
def commandError(command: String, msg: String, index: Int): String =
|
||||
def commandError(command: String, msgs: Seq[String], index: Int): String =
|
||||
{
|
||||
val (line, modIndex) = extractLine(command, index)
|
||||
val point = pointerSpace(command, modIndex)
|
||||
msg + "\n" + line + "\n" + point + "^"
|
||||
msgs.mkString("\n") + "\n" + line + "\n" + point + "^"
|
||||
}
|
||||
def extractLine(s: String, i: Int): (String, Int) =
|
||||
{
|
||||
|
|
@ -116,6 +119,22 @@ object Command
|
|||
s.substring(i+1)
|
||||
loop(s.length - 1)
|
||||
}
|
||||
def invalidValue(label: String, allowed: Iterable[String])(value: String): String =
|
||||
"Not a valid " + label + ": " + value + similar(value, allowed)
|
||||
def similar(value: String, allowed: Iterable[String]): String =
|
||||
{
|
||||
val suggested = suggestions(value, allowed.toSeq)
|
||||
if(suggested.isEmpty) "" else suggested.mkString(" (similar: ", ", ", ")")
|
||||
}
|
||||
def suggestions(a: String, bs: Seq[String], maxDistance: Int = 3, maxSuggestions: Int = 3): Seq[String] =
|
||||
bs.map { b => (b, distance(a, b) ) } filter withinDistance(maxDistance, a) sortBy(_._2) take(maxSuggestions) map(_._1)
|
||||
def distance(a: String, b: String): Int =
|
||||
EditDistance.levenshtein(a, b, insertCost = 1, deleteCost = 1, subCost = 2, transposeCost = 1, true)
|
||||
def withinDistance(dist: Int, a: String)(ai: (String, Int)): Boolean =
|
||||
{
|
||||
val lengthBased = ( (ai._1.length min a.length) - 1 ) max 2
|
||||
ai._2 <= (dist min lengthBased)
|
||||
}
|
||||
}
|
||||
|
||||
trait Help
|
||||
|
|
|
|||
|
|
@ -164,15 +164,15 @@ object Project extends Init[Scope] with ProjectExtra
|
|||
ref match
|
||||
{
|
||||
case ThisBuild => "<this>"
|
||||
case BuildRef(uri) => "[" + uri + "]"
|
||||
case BuildRef(uri) => "{" + uri + "}"
|
||||
}
|
||||
def display(ref: ProjectReference) =
|
||||
ref match
|
||||
{
|
||||
case ThisProject => "(<this>)<this>"
|
||||
case LocalProject(id) => "(<this>)" + id
|
||||
case RootProject(uri) => "(" + uri + ")<root>"
|
||||
case ProjectRef(uri, id) => "(" + uri + ")" + id
|
||||
case RootProject(uri) => "{" + uri + " }<root>"
|
||||
case ProjectRef(uri, id) => "{" + uri + "}" + id
|
||||
}
|
||||
|
||||
def fillTaskAxis(scoped: ScopedKey[_]): ScopedKey[_] =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package sbt.complete
|
||||
|
||||
/** @author Paul Phillips*/
|
||||
object EditDistance {
|
||||
/** Translated from the java version at
|
||||
* http://www.merriampark.com/ld.htm
|
||||
* which is declared to be public domain.
|
||||
*/
|
||||
def levenshtein(s: String, t: String, insertCost: Int, deleteCost: Int, subCost: Int, transposeCost: Int, transpositions: Boolean = false): Int = {
|
||||
val n = s.length
|
||||
val m = t.length
|
||||
if (n == 0) return m
|
||||
if (m == 0) return n
|
||||
|
||||
val d = Array.ofDim[Int](n + 1, m + 1)
|
||||
0 to n foreach (x => d(x)(0) = x)
|
||||
0 to m foreach (x => d(0)(x) = x)
|
||||
|
||||
for (i <- 1 to n ; val s_i = s(i - 1) ; j <- 1 to m) {
|
||||
val t_j = t(j - 1)
|
||||
val cost = if (s_i == t_j) 0 else 1
|
||||
|
||||
val c1 = d(i - 1)(j) + deleteCost
|
||||
val c2 = d(i)(j - 1) + insertCost
|
||||
val c3 = d(i - 1)(j - 1) + cost*subCost
|
||||
|
||||
d(i)(j) = c1 min c2 min c3
|
||||
|
||||
if (transpositions) {
|
||||
if (i > 1 && j > 1 && s(i - 1) == t(j - 2) && s(i - 2) == t(j - 1))
|
||||
d(i)(j) = d(i)(j) min (d(i - 2)(j - 2) + cost*transposeCost)
|
||||
}
|
||||
}
|
||||
|
||||
d(n)(m)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,15 +5,18 @@ package sbt.complete
|
|||
|
||||
import Parser._
|
||||
import sbt.Types.{left, right, some}
|
||||
import sbt.Collections.separate
|
||||
|
||||
sealed trait Parser[+T]
|
||||
{
|
||||
def derive(i: Char): Parser[T]
|
||||
def resultEmpty: Option[T]
|
||||
def result: Option[T] = None
|
||||
def resultEmpty: Result[T]
|
||||
def result: Option[T]
|
||||
def completions: Completions
|
||||
def valid: Boolean
|
||||
def failure: Option[Failure]
|
||||
def isTokenStart = false
|
||||
def ifValid[S](p: => Parser[S]): Parser[S]
|
||||
def valid: Boolean
|
||||
}
|
||||
sealed trait RichParser[A]
|
||||
{
|
||||
|
|
@ -39,6 +42,9 @@ sealed trait RichParser[A]
|
|||
def ??[B >: A](alt: B): Parser[B]
|
||||
def <~[B](b: Parser[B]): Parser[A]
|
||||
def ~>[B](b: Parser[B]): Parser[B]
|
||||
|
||||
/** Uses the specified message if the original Parser fails.*/
|
||||
def !!!(msg: String): Parser[A]
|
||||
|
||||
def unary_- : Parser[Unit]
|
||||
def & (o: Parser[_]): Parser[A]
|
||||
|
|
@ -51,57 +57,110 @@ sealed trait RichParser[A]
|
|||
def string(implicit ev: A <:< Seq[Char]): Parser[String]
|
||||
/** Produces a Parser that filters the original parser.
|
||||
* If 'f' is not true when applied to the output of the original parser, the Parser returned by this method fails.*/
|
||||
def filter(f: A => Boolean): Parser[A]
|
||||
def filter(f: A => Boolean, msg: String => String): Parser[A]
|
||||
|
||||
def flatMap[B](f: A => Parser[B]): Parser[B]
|
||||
}
|
||||
object Parser extends ParserMain
|
||||
{
|
||||
sealed abstract class Result[+T] {
|
||||
def isFailure: Boolean
|
||||
def isValid: Boolean
|
||||
def errors: Seq[String]
|
||||
def or[B >: T](b: => Result[B]): Result[B]
|
||||
def either[B](b: => Result[B]): Result[Either[T,B]]
|
||||
def map[B](f: T => B): Result[B]
|
||||
def flatMap[B](f: T => Result[B]): Result[B]
|
||||
def &&(b: => Result[_]): Result[T]
|
||||
def filter(f: T => Boolean, msg: => String): Result[T]
|
||||
def seq[B](b: => Result[B]): Result[(T,B)] = app(b)( (m,n) => (m,n) )
|
||||
def app[B,C](b: => Result[B])(f: (T, B) => C): Result[C]
|
||||
def toEither: Either[Seq[String], T]
|
||||
}
|
||||
final case class Value[+T](value: T) extends Result[T] {
|
||||
def isFailure = false
|
||||
def isValid: Boolean = true
|
||||
def errors = Nil
|
||||
def app[B,C](b: => Result[B])(f: (T, B) => C): Result[C] = b match {
|
||||
case fail: Failure => fail
|
||||
case Value(bv) => Value(f(value, bv))
|
||||
}
|
||||
def &&(b: => Result[_]): Result[T] = b match { case f: Failure => f; case _ => this }
|
||||
def or[B >: T](b: => Result[B]): Result[B] = this
|
||||
def either[B](b: => Result[B]): Result[Either[T,B]] = Value(Left(value))
|
||||
def map[B](f: T => B): Result[B] = Value(f(value))
|
||||
def flatMap[B](f: T => Result[B]): Result[B] = f(value)
|
||||
def filter(f: T => Boolean, msg: => String): Result[T] = if(f(value)) this else mkFailure(msg)
|
||||
def toEither = Right(value)
|
||||
}
|
||||
final class Failure(mkErrors: => Seq[String]) extends Result[Nothing] {
|
||||
lazy val errors: Seq[String] = mkErrors
|
||||
def isFailure = true
|
||||
def isValid = false
|
||||
def map[B](f: Nothing => B) = this
|
||||
def flatMap[B](f: Nothing => Result[B]) = this
|
||||
def or[B](b: => Result[B]): Result[B] = b match {
|
||||
case v: Value[B] => v
|
||||
case f: Failure => concatErrors(f)
|
||||
}
|
||||
def either[B](b: => Result[B]): Result[Either[Nothing,B]] = b match {
|
||||
case Value(v) => Value(Right(v))
|
||||
case f: Failure => concatErrors(f)
|
||||
}
|
||||
def filter(f: Nothing => Boolean, msg: => String) = this
|
||||
def app[B,C](b: => Result[B])(f: (Nothing, B) => C): Result[C] = this
|
||||
def &&(b: => Result[_]) = this
|
||||
def toEither = Left(errors)
|
||||
|
||||
private[this] def concatErrors(f: Failure) = mkFailures(errors ++ f.errors)
|
||||
}
|
||||
def mkFailures(errors: => Seq[String]): Failure = new Failure(errors.distinct)
|
||||
def mkFailure(error: => String): Failure = new Failure(error :: Nil)
|
||||
|
||||
def checkMatches(a: Parser[_], completions: Seq[String])
|
||||
{
|
||||
val bad = completions.filter( apply(a)(_).resultEmpty.isEmpty)
|
||||
val bad = completions.filter( apply(a)(_).resultEmpty.isFailure)
|
||||
if(!bad.isEmpty) error("Invalid example completions: " + bad.mkString("'", "', '", "'"))
|
||||
}
|
||||
def tuple[A,B](a: Option[A], b: Option[B]): Option[(A,B)] =
|
||||
(a,b) match { case (Some(av), Some(bv)) => Some(av, bv); case _ => None }
|
||||
|
||||
def mapParser[A,B](a: Parser[A], f: A => B): Parser[B] =
|
||||
if(a.valid) {
|
||||
a.ifValid {
|
||||
a.result match
|
||||
{
|
||||
case Some(av) => success( f(av) )
|
||||
case None => new MapParser(a, f)
|
||||
}
|
||||
}
|
||||
else Invalid
|
||||
|
||||
def bindParser[A,B](a: Parser[A], f: A => Parser[B]): Parser[B] =
|
||||
if(a.valid) {
|
||||
a.ifValid {
|
||||
a.result match
|
||||
{
|
||||
case Some(av) => f(av)
|
||||
case None => new BindParser(a, f)
|
||||
}
|
||||
}
|
||||
else Invalid
|
||||
|
||||
def filterParser[T](a: Parser[T], f: T => Boolean): Parser[T] =
|
||||
if(a.valid) {
|
||||
def filterParser[T](a: Parser[T], f: T => Boolean, seen: String, msg: String => String): Parser[T] =
|
||||
a.ifValid {
|
||||
a.result match
|
||||
{
|
||||
case Some(av) => if( f(av) ) success( av ) else Invalid
|
||||
case None => new Filter(a, f)
|
||||
case Some(av) => if( f(av) ) success( av ) else Parser.failure(msg(seen))
|
||||
case None => new Filter(a, f, seen, msg)
|
||||
}
|
||||
}
|
||||
else Invalid
|
||||
|
||||
def seqParser[A,B](a: Parser[A], b: Parser[B]): Parser[(A,B)] =
|
||||
if(a.valid && b.valid)
|
||||
a.ifValid { b.ifValid {
|
||||
(a.result, b.result) match {
|
||||
case (Some(av), Some(bv)) => success( (av, bv) )
|
||||
case (Some(av), None) => b map { bv => (av, bv) }
|
||||
case (None, Some(bv)) => a map { av => (av, bv) }
|
||||
case (None, None) => new SeqParser(a,b)
|
||||
}
|
||||
else Invalid
|
||||
}}
|
||||
|
||||
def choiceParser[A,B](a: Parser[A], b: Parser[B]): Parser[Either[A,B]] =
|
||||
if(a.valid)
|
||||
|
|
@ -112,6 +171,9 @@ object Parser extends ParserMain
|
|||
def opt[T](a: Parser[T]): Parser[Option[T]] =
|
||||
if(a.valid) new Optional(a) else success(None)
|
||||
|
||||
def onFailure[T](delegate: Parser[T], msg: String): Parser[T] =
|
||||
if(delegate.valid) new OnFailure(delegate, msg) else failure(msg)
|
||||
|
||||
def zeroOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 0, Infinite)
|
||||
def oneOrMore[T](p: Parser[T]): Parser[Seq[T]] = repeat(p, 1, Infinite)
|
||||
|
||||
|
|
@ -123,34 +185,35 @@ object Parser extends ParserMain
|
|||
assume(max >= min, "Minimum must be less than or equal to maximum (min: " + min + ", max: " + max + ")")
|
||||
|
||||
def checkRepeated(invalidButOptional: => Parser[Seq[T]]): Parser[Seq[T]] =
|
||||
if(repeated.valid)
|
||||
repeated.result match
|
||||
{
|
||||
case Some(value) => success(revAcc reverse_::: value :: Nil) // revAcc should be Nil here
|
||||
case None => if(max.isZero) success(revAcc.reverse) else new Repeat(partial, repeated, min, max, revAcc)
|
||||
}
|
||||
else if(min == 0)
|
||||
invalidButOptional
|
||||
else
|
||||
Invalid
|
||||
repeated match
|
||||
{
|
||||
case i: Invalid if min == 0 => invalidButOptional
|
||||
case i: Invalid => i
|
||||
case _ =>
|
||||
repeated.result match
|
||||
{
|
||||
case Some(value) => success(revAcc reverse_::: value :: Nil) // revAcc should be Nil here
|
||||
case None => if(max.isZero) success(revAcc.reverse) else new Repeat(partial, repeated, min, max, revAcc)
|
||||
}
|
||||
}
|
||||
|
||||
partial match
|
||||
{
|
||||
case Some(part) =>
|
||||
if(part.valid)
|
||||
part.ifValid {
|
||||
part.result match
|
||||
{
|
||||
case Some(value) => repeat(None, repeated, min, max, value :: revAcc)
|
||||
case None => checkRepeated(part.map(lv => (lv :: revAcc).reverse))
|
||||
}
|
||||
else Invalid
|
||||
}
|
||||
case None => checkRepeated(success(Nil))
|
||||
}
|
||||
}
|
||||
|
||||
def sub[T](a: Parser[T], b: Parser[_]): Parser[T] = and(a, not(b))
|
||||
|
||||
def and[T](a: Parser[T], b: Parser[_]): Parser[T] = if(a.valid && b.valid) new And(a, b) else Invalid
|
||||
def and[T](a: Parser[T], b: Parser[_]): Parser[T] = a.ifValid( b.ifValid( new And(a, b) ))
|
||||
}
|
||||
trait ParserMain
|
||||
{
|
||||
|
|
@ -169,40 +232,43 @@ trait ParserMain
|
|||
def ??[B >: A](alt: B): Parser[B] = a.? map { _ getOrElse alt }
|
||||
def <~[B](b: Parser[B]): Parser[A] = (a ~ b) map { case av ~ _ => av }
|
||||
def ~>[B](b: Parser[B]): Parser[B] = (a ~ b) map { case _ ~ bv => bv }
|
||||
def !!!(msg: String): Parser[A] = onFailure(a, msg)
|
||||
|
||||
def unary_- = not(a)
|
||||
def & (o: Parser[_]) = and(a, o)
|
||||
def - (o: Parser[_]) = sub(a, o)
|
||||
def examples(s: String*): Parser[A] = examples(s.toSet)
|
||||
def examples(s: Set[String], check: Boolean = false): Parser[A] = Parser.examples(a, s, check)
|
||||
def filter(f: A => Boolean): Parser[A] = filterParser(a, f)
|
||||
def filter(f: A => Boolean, msg: String => String): Parser[A] = filterParser(a, f, "", msg)
|
||||
def string(implicit ev: A <:< Seq[Char]): Parser[String] = map(_.mkString)
|
||||
def flatMap[B](f: A => Parser[B]) = bindParser(a, f)
|
||||
}
|
||||
implicit def literalRichParser(c: Char): RichParser[Char] = richParser(c)
|
||||
implicit def literalRichParser(s: String): RichParser[String] = richParser(s)
|
||||
|
||||
def failure[T](msg: String): Parser[T] = Invalid(msg)
|
||||
def invalid(msgs: => Seq[String]): Parser[Nothing] = Invalid(mkFailures(msgs))
|
||||
def failure(msg: => String): Parser[Nothing] = invalid(msg :: Nil)
|
||||
def success[T](value: T): Parser[T] = new ValidParser[T] {
|
||||
override def result = Some(value)
|
||||
def resultEmpty = result
|
||||
def derive(c: Char) = Invalid
|
||||
def resultEmpty = Value(value)
|
||||
def derive(c: Char) = Parser.failure("Expected end of input.")
|
||||
def completions = Completions.empty
|
||||
override def toString = "success(" + value + ")"
|
||||
}
|
||||
|
||||
implicit def range(r: collection.immutable.NumericRange[Char]): Parser[Char] =
|
||||
new CharacterClass(r contains _).examples(r.map(_.toString) : _*)
|
||||
charClass(r contains _).examples(r.map(_.toString) : _*)
|
||||
def chars(legal: String): Parser[Char] =
|
||||
{
|
||||
val set = legal.toSet
|
||||
new CharacterClass(set) examples(set.map(_.toString))
|
||||
charClass(set, "character in '" + legal + "'") examples(set.map(_.toString))
|
||||
}
|
||||
def charClass(f: Char => Boolean): Parser[Char] = new CharacterClass(f)
|
||||
def charClass(f: Char => Boolean, label: String = "<unspecified>"): Parser[Char] = new CharacterClass(f, label)
|
||||
|
||||
implicit def literal(ch: Char): Parser[Char] = new ValidParser[Char] {
|
||||
def resultEmpty = None
|
||||
def derive(c: Char) = if(c == ch) success(ch) else Invalid
|
||||
def result = None
|
||||
def resultEmpty = mkFailure( "Expected '" + ch + "'" )
|
||||
def derive(c: Char) = if(c == ch) success(ch) else new Invalid(resultEmpty)
|
||||
def completions = Completions.single(Completion.suggestStrict(ch.toString))
|
||||
override def toString = "'" + ch + "'"
|
||||
}
|
||||
|
|
@ -212,19 +278,22 @@ trait ParserMain
|
|||
}
|
||||
|
||||
// intended to be temporary pending proper error feedback
|
||||
def result[T](p: Parser[T], s: String): Either[(String,Int), T] =
|
||||
def result[T](p: Parser[T], s: String): Either[(Seq[String],Int), T] =
|
||||
{
|
||||
def loop(i: Int, a: Parser[T]): Either[(String,Int), T] =
|
||||
if(a.valid)
|
||||
def loop(i: Int, a: Parser[T]): Either[(Seq[String],Int), T] =
|
||||
a match
|
||||
{
|
||||
val ci = i+1
|
||||
if(ci >= s.length)
|
||||
a.resultEmpty.toRight(("Unexpected end of input", ci))
|
||||
else
|
||||
loop(ci, a derive s(ci) )
|
||||
case Invalid(f) => Left( (f.errors, i) )
|
||||
case _ =>
|
||||
val ci = i+1
|
||||
if(ci >= s.length)
|
||||
a.resultEmpty.toEither.left.map { msgs =>
|
||||
val nonEmpty = if(msgs.isEmpty) "Unexpected end of input" :: Nil else msgs
|
||||
(nonEmpty, ci)
|
||||
}
|
||||
else
|
||||
loop(ci, a derive s(ci) )
|
||||
}
|
||||
else
|
||||
Left(("Parse error",i))
|
||||
loop(-1, p)
|
||||
}
|
||||
|
||||
|
|
@ -250,12 +319,15 @@ trait ParserMain
|
|||
else a
|
||||
|
||||
def matched(t: Parser[_], seen: Vector[Char] = Vector.empty, partial: Boolean = false): Parser[String] =
|
||||
if(!t.valid)
|
||||
if(partial && !seen.isEmpty) success(seen.mkString) else Invalid
|
||||
else if(t.result.isEmpty)
|
||||
new MatchedString(t, seen, partial)
|
||||
else
|
||||
success(seen.mkString)
|
||||
t match
|
||||
{
|
||||
case i: Invalid => if(partial && !seen.isEmpty) success(seen.mkString) else i
|
||||
case _ =>
|
||||
if(t.result.isEmpty)
|
||||
new MatchedString(t, seen, partial)
|
||||
else
|
||||
success(seen.mkString)
|
||||
}
|
||||
|
||||
def token[T](t: Parser[T]): Parser[T] = token(t, "", true)
|
||||
def token[T](t: Parser[T], description: String): Parser[T] = token(t, description, false)
|
||||
|
|
@ -273,10 +345,12 @@ trait ParserMain
|
|||
|
||||
def not(p: Parser[_]): Parser[Unit] = new Not(p)
|
||||
|
||||
def seq[T](p: Seq[Parser[T]]): Parser[Seq[T]] =
|
||||
def seq[T](p: Seq[Parser[T]]): Parser[Seq[T]] = seq0(p, Nil)
|
||||
def seq0[T](p: Seq[Parser[T]], errors: => Seq[String]): Parser[Seq[T]] =
|
||||
{
|
||||
val valid = p.filter(_.valid)
|
||||
if(valid.isEmpty) failure("") else new ParserSeq(valid)
|
||||
val (newErrors, valid) = separate(p) { case Invalid(f) => Left(f.errors); case ok => Right(ok) }
|
||||
def combinedErrors = errors ++ newErrors.flatten
|
||||
if(valid.isEmpty) invalid(combinedErrors) else new ParserSeq(valid, combinedErrors)
|
||||
}
|
||||
|
||||
def stringLiteral(s: String, start: Int): Parser[String] =
|
||||
|
|
@ -288,27 +362,40 @@ trait ParserMain
|
|||
sealed trait ValidParser[T] extends Parser[T]
|
||||
{
|
||||
final def valid = true
|
||||
final def failure = None
|
||||
final def ifValid[S](p: => Parser[S]): Parser[S] = p
|
||||
}
|
||||
private object Invalid extends Invalid("inv")
|
||||
private sealed case class Invalid(val message: String) extends Parser[Nothing]
|
||||
private final case class Invalid(fail: Failure) extends Parser[Nothing]
|
||||
{
|
||||
def resultEmpty = None
|
||||
def failure = Some(fail)
|
||||
def result = None
|
||||
def resultEmpty = fail
|
||||
def derive(c: Char) = error("Invalid.")
|
||||
override def valid = false
|
||||
def completions = Completions.nil
|
||||
override def toString = message
|
||||
override def toString = fail.errors.mkString("; ")
|
||||
def valid = false
|
||||
def ifValid[S](p: => Parser[S]): Parser[S] = this
|
||||
}
|
||||
private final class OnFailure[A](a: Parser[A], message: String) extends ValidParser[A]
|
||||
{
|
||||
def result = a.result
|
||||
def resultEmpty = a.resultEmpty match { case f: Failure => mkFailure(message); case v: Value[A] => v }
|
||||
def derive(c: Char) = onFailure(a derive c, message)
|
||||
def completions = a.completions
|
||||
override def toString = "(" + a + " !!! \"" + message + "\" )"
|
||||
override def isTokenStart = a.isTokenStart
|
||||
}
|
||||
private final class SeqParser[A,B](a: Parser[A], b: Parser[B]) extends ValidParser[(A,B)]
|
||||
{
|
||||
def cross(ao: Option[A], bo: Option[B]): Option[(A,B)] = for(av <- ao; bv <- bo) yield (av,bv)
|
||||
lazy val resultEmpty = cross(a.resultEmpty, b.resultEmpty)
|
||||
lazy val result = tuple(a.result,b.result)
|
||||
lazy val resultEmpty = a.resultEmpty seq b.resultEmpty
|
||||
def derive(c: Char) =
|
||||
{
|
||||
val common = a.derive(c) ~ b
|
||||
a.resultEmpty match
|
||||
{
|
||||
case Some(av) => common | b.derive(c).map(br => (av,br))
|
||||
case None => common
|
||||
case Value(av) => common | b.derive(c).map(br => (av,br))
|
||||
case _: Failure => common
|
||||
}
|
||||
}
|
||||
lazy val completions = a.completions x b.completions
|
||||
|
|
@ -317,35 +404,47 @@ private final class SeqParser[A,B](a: Parser[A], b: Parser[B]) extends ValidPars
|
|||
|
||||
private final class HomParser[A](a: Parser[A], b: Parser[A]) extends ValidParser[A]
|
||||
{
|
||||
lazy val result = tuple(a.result, b.result) map (_._1)
|
||||
def derive(c: Char) = (a derive c) | (b derive c)
|
||||
lazy val resultEmpty = a.resultEmpty orElse b.resultEmpty
|
||||
lazy val resultEmpty = a.resultEmpty or b.resultEmpty
|
||||
lazy val completions = a.completions ++ b.completions
|
||||
override def toString = "(" + a + " | " + b + ")"
|
||||
}
|
||||
private final class HetParser[A,B](a: Parser[A], b: Parser[B]) extends ValidParser[Either[A,B]]
|
||||
{
|
||||
lazy val result = tuple(a.result, b.result) map { case (a,b) => Left(a) }
|
||||
def derive(c: Char) = (a derive c) || (b derive c)
|
||||
lazy val resultEmpty = a.resultEmpty.map(left.fn) orElse b.resultEmpty.map(right.fn)
|
||||
lazy val resultEmpty = a.resultEmpty either b.resultEmpty
|
||||
lazy val completions = a.completions ++ b.completions
|
||||
override def toString = "(" + a + " || " + b + ")"
|
||||
}
|
||||
private final class ParserSeq[T](a: Seq[Parser[T]]) extends ValidParser[Seq[T]]
|
||||
private final class ParserSeq[T](a: Seq[Parser[T]], errors: => Seq[String]) extends ValidParser[Seq[T]]
|
||||
{
|
||||
assert(!a.isEmpty)
|
||||
lazy val resultEmpty = { val rs = a.flatMap(_.resultEmpty); if(rs.isEmpty) None else Some(rs) }
|
||||
lazy val resultEmpty: Result[Seq[T]] =
|
||||
{
|
||||
val res = a.map(_.resultEmpty)
|
||||
val (failures, values) = separate(res)(_.toEither)
|
||||
if(failures.isEmpty) Value(values) else mkFailures(failures.flatten ++ errors)
|
||||
}
|
||||
def result = {
|
||||
val success = a.flatMap(_.result)
|
||||
if(success.length == a.length) Some(success) else None
|
||||
}
|
||||
lazy val completions = a.map(_.completions).reduceLeft(_ ++ _)
|
||||
def derive(c: Char) = seq(a.map(_ derive c))
|
||||
def derive(c: Char) = seq0(a.map(_ derive c), errors)
|
||||
override def toString = "seq(" + a + ")"
|
||||
}
|
||||
|
||||
private final class BindParser[A,B](a: Parser[A], f: A => Parser[B]) extends ValidParser[B]
|
||||
{
|
||||
lazy val resultEmpty = a.resultEmpty match { case None => None; case Some(av) => f(av).resultEmpty }
|
||||
lazy val result = a.result flatMap { av => f(av).result }
|
||||
lazy val resultEmpty = a.resultEmpty flatMap { av => f(av).resultEmpty }
|
||||
lazy val completions =
|
||||
a.completions flatMap { c =>
|
||||
apply(a)(c.append).resultEmpty match {
|
||||
case None => Completions.strict(Set.empty + c)
|
||||
case Some(av) => c x f(av).completions
|
||||
case _: Failure => Completions.strict(Set.empty + c)
|
||||
case Value(av) => c x f(av).completions
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -354,8 +453,8 @@ private final class BindParser[A,B](a: Parser[A], f: A => Parser[B]) extends Val
|
|||
val common = a derive c flatMap f
|
||||
a.resultEmpty match
|
||||
{
|
||||
case Some(av) => common | derive1(f(av), c)
|
||||
case None => common
|
||||
case Value(av) => common | derive1(f(av), c)
|
||||
case _: Failure => common
|
||||
}
|
||||
}
|
||||
override def isTokenStart = a.isTokenStart
|
||||
|
|
@ -363,17 +462,20 @@ private final class BindParser[A,B](a: Parser[A], f: A => Parser[B]) extends Val
|
|||
}
|
||||
private final class MapParser[A,B](a: Parser[A], f: A => B) extends ValidParser[B]
|
||||
{
|
||||
lazy val result = a.result map f
|
||||
lazy val resultEmpty = a.resultEmpty map f
|
||||
def derive(c: Char) = (a derive c) map f
|
||||
def completions = a.completions
|
||||
override def isTokenStart = a.isTokenStart
|
||||
override def toString = "map(" + a + ")"
|
||||
}
|
||||
private final class Filter[T](p: Parser[T], f: T => Boolean) extends ValidParser[T]
|
||||
private final class Filter[T](p: Parser[T], f: T => Boolean, seen: String, msg: String => String) extends ValidParser[T]
|
||||
{
|
||||
lazy val resultEmpty = p.resultEmpty filter f
|
||||
def derive(c: Char) = (p derive c) filter f
|
||||
lazy val completions = p.completions filterS { s => apply(p)(s).resultEmpty.filter(f).isDefined }
|
||||
def filterResult(r: Result[T]) = p.resultEmpty.filter(f, msg(seen))
|
||||
lazy val result = p.result filter f
|
||||
lazy val resultEmpty = filterResult(p.resultEmpty)
|
||||
def derive(c: Char) = filterParser(p derive c, f, seen + c, msg)
|
||||
lazy val completions = p.completions filterS { s => filterResult(apply(p)(s).resultEmpty).isValid }
|
||||
override def toString = "filter(" + p + ")"
|
||||
override def isTokenStart = p.isTokenStart
|
||||
}
|
||||
|
|
@ -382,7 +484,8 @@ private final class MatchedString(delegate: Parser[_], seenV: Vector[Char], part
|
|||
lazy val seen = seenV.mkString
|
||||
def derive(c: Char) = matched(delegate derive c, seenV :+ c, partial)
|
||||
def completions = delegate.completions
|
||||
def resultEmpty = if(delegate.resultEmpty.isDefined) Some(seen) else if(partial) Some(seen) else None
|
||||
def result = if(delegate.result.isDefined) Some(seen) else None
|
||||
def resultEmpty = delegate.resultEmpty match { case f: Failure if !partial => f; case _ => Value(seen) }
|
||||
override def isTokenStart = delegate.isTokenStart
|
||||
override def toString = "matched(" + partial + ", " + seen + ", " + delegate + ")"
|
||||
}
|
||||
|
|
@ -398,30 +501,37 @@ private final class TokenStart[T](delegate: Parser[T], seen: String, track: Bool
|
|||
else
|
||||
Completions.single(Completion.displayStrict(seen))
|
||||
|
||||
def result = delegate.result
|
||||
def resultEmpty = delegate.resultEmpty
|
||||
override def isTokenStart = true
|
||||
override def toString = "token('" + seen + "', " + track + ", " + delegate + ")"
|
||||
}
|
||||
private final class And[T](a: Parser[T], b: Parser[_]) extends ValidParser[T]
|
||||
{
|
||||
lazy val result = tuple(a.result,b.result) map { _._1 }
|
||||
def derive(c: Char) = (a derive c) & (b derive c)
|
||||
lazy val completions = a.completions.filterS(s => apply(b)(s).resultEmpty.isDefined )
|
||||
lazy val resultEmpty = if(b.resultEmpty.isDefined) a.resultEmpty else None
|
||||
lazy val completions = a.completions.filterS(s => apply(b)(s).resultEmpty.isValid )
|
||||
lazy val resultEmpty = a.resultEmpty && b.resultEmpty
|
||||
}
|
||||
|
||||
private final class Not(delegate: Parser[_]) extends ValidParser[Unit]
|
||||
{
|
||||
def derive(c: Char) = if(delegate.valid) not(delegate derive c) else this
|
||||
def completions = Completions.empty
|
||||
lazy val resultEmpty = if(delegate.resultEmpty.isDefined) None else Some(())
|
||||
def result = None
|
||||
lazy val resultEmpty = delegate.resultEmpty match {
|
||||
case f: Failure => Value(())
|
||||
case v: Value[_] => mkFailure("Excluded.")
|
||||
}
|
||||
}
|
||||
private final class Examples[T](delegate: Parser[T], fixed: Set[String]) extends ValidParser[T]
|
||||
{
|
||||
def derive(c: Char) = examples(delegate derive c, fixed.collect { case x if x.length > 0 && x(0) == c => x substring 1 })
|
||||
def result = delegate.result
|
||||
lazy val resultEmpty = delegate.resultEmpty
|
||||
lazy val completions =
|
||||
if(fixed.isEmpty)
|
||||
if(resultEmpty.isEmpty) Completions.nil else Completions.empty
|
||||
if(resultEmpty.isValid) Completions.nil else Completions.empty
|
||||
else
|
||||
Completions(fixed map(f => Completion.suggestion(f)) )
|
||||
override def toString = "examples(" + delegate + ", " + fixed.take(2) + ")"
|
||||
|
|
@ -429,21 +539,25 @@ private final class Examples[T](delegate: Parser[T], fixed: Set[String]) extends
|
|||
private final class StringLiteral(str: String, start: Int) extends ValidParser[String]
|
||||
{
|
||||
assert(0 <= start && start < str.length)
|
||||
def resultEmpty = None
|
||||
def derive(c: Char) = if(str.charAt(start) == c) stringLiteral(str, start+1) else Invalid
|
||||
def failMsg = "Expected '" + str + "'"
|
||||
def resultEmpty = mkFailure(failMsg)
|
||||
def result = None
|
||||
def derive(c: Char) = if(str.charAt(start) == c) stringLiteral(str, start+1) else new Invalid(resultEmpty)
|
||||
lazy val completions = Completions.single(Completion.suggestion(str.substring(start)))
|
||||
override def toString = '"' + str + '"'
|
||||
}
|
||||
private final class CharacterClass(f: Char => Boolean) extends ValidParser[Char]
|
||||
private final class CharacterClass(f: Char => Boolean, label: String) extends ValidParser[Char]
|
||||
{
|
||||
def resultEmpty = None
|
||||
def derive(c: Char) = if( f(c) ) success(c) else Invalid
|
||||
def result = None
|
||||
def resultEmpty = mkFailure("Expected " + label)
|
||||
def derive(c: Char) = if( f(c) ) success(c) else Invalid(resultEmpty)
|
||||
def completions = Completions.empty
|
||||
override def toString = "class()"
|
||||
override def toString = "class(" + label + ")"
|
||||
}
|
||||
private final class Optional[T](delegate: Parser[T]) extends ValidParser[Option[T]]
|
||||
{
|
||||
def resultEmpty = Some(None)
|
||||
def result = delegate.result map some.fn
|
||||
def resultEmpty = Value(None)
|
||||
def derive(c: Char) = (delegate derive c).map(some.fn)
|
||||
lazy val completions = Completion.empty +: delegate.completions
|
||||
override def toString = delegate.toString + "?"
|
||||
|
|
@ -460,8 +574,8 @@ private final class Repeat[T](partial: Option[Parser[T]], repeated: Parser[T], m
|
|||
val partD = repeat(Some(part derive c), repeated, min, max, accumulatedReverse)
|
||||
part.resultEmpty match
|
||||
{
|
||||
case Some(pv) => partD | repeatDerive(c, pv :: accumulatedReverse)
|
||||
case None => partD
|
||||
case Value(pv) => partD | repeatDerive(c, pv :: accumulatedReverse)
|
||||
case _: Failure => partD
|
||||
}
|
||||
case None => repeatDerive(c, accumulatedReverse)
|
||||
}
|
||||
|
|
@ -481,21 +595,21 @@ private final class Repeat[T](partial: Option[Parser[T]], repeated: Parser[T], m
|
|||
case None => fin
|
||||
}
|
||||
}
|
||||
lazy val resultEmpty: Option[Seq[T]] =
|
||||
def result = None
|
||||
lazy val resultEmpty: Result[Seq[T]] =
|
||||
{
|
||||
val partialAccumulatedOption =
|
||||
partial match
|
||||
{
|
||||
case None => Some(accumulatedReverse)
|
||||
case None => Value(accumulatedReverse)
|
||||
case Some(partialPattern) => partialPattern.resultEmpty.map(_ :: accumulatedReverse)
|
||||
}
|
||||
for(partialAccumulated <- partialAccumulatedOption; repeatEmpty <- repeatedParseEmpty) yield
|
||||
partialAccumulated reverse_::: repeatEmpty
|
||||
(partialAccumulatedOption app repeatedParseEmpty)(_ reverse_::: _)
|
||||
}
|
||||
private def repeatedParseEmpty: Option[List[T]] =
|
||||
private def repeatedParseEmpty: Result[List[T]] =
|
||||
{
|
||||
if(min == 0)
|
||||
Some(Nil)
|
||||
Value(Nil)
|
||||
else
|
||||
// forced determinism
|
||||
for(value <- repeated.resultEmpty) yield
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ trait Parsers
|
|||
lazy val any: Parser[Char] = charClass(_ => true)
|
||||
|
||||
lazy val DigitSet = Set("0","1","2","3","4","5","6","7","8","9")
|
||||
lazy val Digit = charClass(_.isDigit) examples DigitSet
|
||||
lazy val Letter = charClass(_.isLetter)
|
||||
lazy val Digit = charClass(_.isDigit, "digit") examples DigitSet
|
||||
lazy val Letter = charClass(_.isLetter, "letter")
|
||||
def IDStart = Letter
|
||||
lazy val IDChar = charClass(isIDChar)
|
||||
lazy val IDChar = charClass(isIDChar, "ID character")
|
||||
lazy val ID = IDStart ~ IDChar.* map { case x ~ xs => (x +: xs).mkString }
|
||||
lazy val OpChar = charClass(isOpChar)
|
||||
lazy val OpChar = charClass(isOpChar, "symbol")
|
||||
lazy val Op = OpChar.+.string
|
||||
lazy val OpOrID = ID | Op
|
||||
|
||||
|
|
@ -28,12 +28,15 @@ trait Parsers
|
|||
def isIDChar(c: Char) = c.isLetterOrDigit || c == '-' || c == '_'
|
||||
def isDelimiter(c: Char) = c match { case '`' | '\'' | '\"' | /*';' | */',' | '.' => true ; case _ => false }
|
||||
|
||||
lazy val NotSpaceClass = charClass(!_.isWhitespace)
|
||||
lazy val SpaceClass = charClass(_.isWhitespace)
|
||||
lazy val NotSpaceClass = charClass(!_.isWhitespace, "non-whitespace character")
|
||||
lazy val SpaceClass = charClass(_.isWhitespace, "whitespace character")
|
||||
lazy val NotSpace = NotSpaceClass.+.string
|
||||
lazy val Space = SpaceClass.+.examples(" ")
|
||||
lazy val OptSpace = SpaceClass.*.examples(" ")
|
||||
lazy val URIClass = charClass(x => !x.isWhitespace && ')' != x).+.string
|
||||
lazy val URIClass = URIChar.+.string !!! "Invalid URI"
|
||||
|
||||
lazy val URIChar = charClass(alphanum) | chars("_-!.~'()*,;:$&+=?/[]@%")
|
||||
def alphanum(c: Char) = ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')
|
||||
|
||||
// TODO: implement
|
||||
def fileParser(base: File): Parser[File] = token(mapOrFail(NotSpace)(s => new File(s.mkString)), "<file>")
|
||||
|
|
@ -63,6 +66,6 @@ object Parsers extends Parsers
|
|||
object DefaultParsers extends Parsers with ParserMain
|
||||
{
|
||||
def matches(p: Parser[_], s: String): Boolean =
|
||||
apply(p)(s).resultEmpty.isDefined
|
||||
apply(p)(s).resultEmpty.isValid
|
||||
def validID(s: String): Boolean = matches(ID, s)
|
||||
}
|
||||
Loading…
Reference in New Issue