All Downloads are FREE. Search and download functionalities are using the official Maven repository.

yamusca.parser.Parsers.scala Maven / Gradle / Ivy

package yamusca.parser

trait Parsers {
  val nextChar: Parser[Char] = { in =>
    in.charAt(0) match {
      case Some(c) => Right((in.dropLeft(1), c))
      case None => Left((in, "Input exhausted"))
    }
  }

  def nextChar(p: Char => Boolean): Parser[Char] = { in =>
    in.charAt(0) match {
      case Some(c) if p(c) => Right((in.dropLeft(1), c))
      case Some(c) => Left((in, "Character not expected"))
      case None => Left((in, "Input exhausted"))
    }
  }

  def consume(s: String): Parser[String] = { in =>
    if (in.takeLeft(s.length).current == s) Right(in.dropLeft(s.length) -> s)
    else Left(in -> s"Expected '$s'")
  }

  def consume(c: Char): Parser[Char] = { in =>
    in.charAt(0) match {
      case Some(`c`) => Right((in.dropLeft(1), c))
      case _ => Left((in, s"Char is not $c"))
    }
  }

  def peek[A](p: Parser[A]): Parser[A] = { in =>
    val widen = in.copy(end = in.raw.length)
    p(widen) match {
      case Right((_, a)) => Right((in, a))
      case Left((_, err)) => Left((in, err))
    }
  }

  val newLine: Parser[String] = (consume('\r').opt ~ consume('\n')).map {
    case (r, n) => if (r.isDefined) "\r\n" else "\n"
  }

  def consumeUntil(s: String): Parser[String] = { in =>
    in.splitAtNext(s) match {
      case Some((left, right)) => Right(right -> left.current)
      case None => Left(in -> s"Expected string not found: $s")
    }
  }

  def consumeWhile(p: Char => Boolean): Parser[Unit] = { in =>
    @annotation.tailrec
    def go(index: Int): Int =
      if (index >= in.end || !p(in.raw.charAt(index))) index
      else go(index +1)

    val idx = go(in.pos)
    Right((in.copy(pos = idx), ()))
  }

  val ignoreWs: Parser[Unit] = consumeWhile(c => c == ' ' || c == '\t')

  val atEnd: Parser[Unit] = { in =>
    if (in.exhausted) Right((in, ()))
    else Left(in -> "input is not exhausted")
  }

  def nonEmpty(p: Parser[String]): Parser[String] =
    p.emap(s => if (s.isEmpty) Left("Empty string") else Right(s))

  def repeat[A](p: Parser[A]): Parser[Seq[A]] = { in =>
    @annotation.tailrec
    def go(in: ParseInput, result: Vector[A]): ParseResult[Vector[A]] =
      if (in.exhausted) Right(in -> result)
      else p(in) match {
        case Right((next, a)) =>
          go(next, result :+ a)
        case Left((next, err)) =>
          Right(next -> result)
      }

    go(in, Vector.empty)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy