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

org.ensime.sexp.SexpParser.scala Maven / Gradle / Ivy

// Copyright: 2010 - 2018 https://github.com/ensime/ensime-server/graphs
// License: http://www.gnu.org/licenses/lgpl-3.0.en.html
package org.ensime.sexp

//import org.parboiled2._
import fastparse.all._

/**
 * Parse Emacs Lisp into an `Sexp`. Other lisp variants may
 * require tweaking, e.g. Scheme's nil, infinity, NaN, etc.
 */
object SexpParser {

  def parse(desc: String): Sexp =
    SexpP.parse(desc) match {
      case Parsed.Success(d, _) => d
      case f: Parsed.Failure =>
        throw new Exception("Failed to parse sexp: " + f.msg)
    }

  // https://www.gnu.org/software/emacs/manual/html_node/elisp/Basic-Char-Syntax.html
  // https://www.gnu.org/software/emacs/manual/html_node/elisp/Syntax-for-Strings.html
  // Not supported: https://www.gnu.org/software/emacs/manual/html_node/elisp/Non_002dASCII-in-Strings.html
  private[sexp] val specialChars = Map[String, String](
    "\"" -> "\"",
    "a"  -> "\u0007",
    "b"  -> "\b",
    "t"  -> "\t",
    "n"  -> "\n",
    "v"  -> "\u000b",
    "f"  -> "\f",
    "r"  -> "\r",
    "e"  -> "\u001b",
    "s"  -> " ",
    "d"  -> "\u007f",
    "\\" -> "\\"
  )

  val SexpQuote = SexpSymbol("quote")

  val PrintableRange = '\u0021' to '\u007e'
  val Symbols        = "+-*/_~!@$%^&=:<>{}"
  val SymbolStartChar =
    Seq[Seq[Char]]('0' to '9', 'a' to 'z', 'A' to 'Z', Symbols)

  val NormalCharPredicate = CharPred(
    c => PrintableRange.contains(c) && "\"\\".forall(_ != c)
  )
  val WhiteSpacePredicate = CharIn(" \n\r\t\f")
  val NotNewLinePredicate = CharPred(
    c => PrintableRange.contains(c) && c != '\n'
  )
  val SymbolStartCharPredicate = CharIn(SymbolStartChar: _*)
  val SymbolBodyCharPredicate  = CharIn(SymbolStartChar :+ ".".toSeq: _*)
  val PlusMinusPredicate       = CharIn("+-")
  val ExpPredicate             = CharIn("eE")
  val QuoteSlashBackSlash      = CharIn("\"\\/")
  val NCCharPredicate          = CharPred(c => "\"\\".forall(_ != c))

  private val SexpP: Parser[Sexp] =
    P(SexpAtomP | SexpListP | SexpEmptyList | SexpConsP | SexpQuotedP)

  private val SexpConsP: Parser[SexpCons] =
    P(LeftBrace ~ SexpP ~ Whitespace ~ "." ~ Whitespace ~ SexpP ~ RightBrace)
      .map(SexpCons.tupled)

  private val SexpListP: Parser[Sexp] =
    P(LeftBrace ~ SexpP ~ (Whitespace ~ SexpP).rep ~ RightBrace).map {
      case (head, tail) => SexpList(head :: tail.toList)
    }

  private val SexpAtomP: Parser[SexpAtom] =
    P(SexpCharP | SexpStringP | SexpNaNP | SexpNumberP | SexpSymbolP)

  private val SexpCharP: Parser[SexpChar] =
    P("?" ~ NormalChar).map(SexpChar)

  val SexpStringP: Parser[SexpString] =
    P("\"" ~ Characters ~ "\"").map(SexpString)

  val Characters: Parser[String] = P(
    (NormalCharS | EscapedCharS).rep
      .map(chars => chars.mkString)
  )

  val NormalCharS: P[String] = P(NCCharPredicate.!)

  val EscapedCharS: P[String] =
    P(
      "\\" ~
        (QuoteSlashBackSlash.!
          | CharIn("\"").map(_ => "\"")
          | CharIn("b").map(_ => "\b")
          | CharIn("s").map(_ => " ")
          | CharIn("f").map(_ => "\f")
          | CharIn("n").map(_ => "\n")
          | CharIn("r").map(_ => "\r")
          | CharIn("t").map(_ => "\t")
          | CharIn(" \n")
            .map(_ => "")                  // special emacs magic for comments \ are removed
          | CharIn("a").map(_ => "\u0007") // bell
          | CharIn("v").map(_ => "\u000b") // vertical tab
          | CharIn("e").map(_ => "\u001b") // escape
          | CharIn("d").map(_ => "\u007f")) // DEL
    )

  val SexpNumberP = P((Integer ~ Frac.? ~ Exp.?).!)
    .map(num => SexpNumber(BigDecimal(num)))

  val Integer = P("-".? ~ ((CharIn('1' to '9') ~ Digits) | CharIn('0' to '9')))

  val Digits = P(CharIn('0' to '9').rep(1))

  val Frac = P("." ~ Digits)

  val Exp = P(ExpPredicate ~ PlusMinusPredicate.? ~ Digits)

  private val SexpNaNP: Parser[SexpAtom] =
    P(
      StringIn("-1.0e+INF").map(_ => SexpNegInf) |
        StringIn("1.0e+INF").map(_ => SexpPosInf) |
        ("-".? ~ "0.0e+NaN").map(_ => SexpNaN)
    )

  private val SexpQuotedP: Parser[Sexp] =
    P("\'" ~ SexpP).map(SexpCons(SexpQuote, _))

  private val SexpSymbolP: Parser[SexpAtom] =
    // ? allowed at the end of symbol names
    P((SymbolStartCharPredicate.rep(1) ~ SymbolBodyCharPredicate.rep ~ "?".?).!).map {
      case "nil" => SexpNil
      case sym   => SexpSymbol(sym)
    }

  private val SexpEmptyList: Parser[SexpNil.type] =
    P(LeftBrace ~ RightBrace)
      .map(_ => SexpNil)

  private val NormalChar: Parser[Char] =
    P(NormalCharPredicate.!)
      .map(_(0))

  private val Whitespace: P0 =
    P((Comment | WhiteSpacePredicate).rep)

  private val Comment: P0 =
    P(";" ~ NotNewLinePredicate.rep ~ ("\n" | End))

  private val LeftBrace: P0 =
    P(Whitespace ~ "(" ~ Whitespace)

  private val RightBrace: P0 =
    P(Whitespace ~ ")" ~ Whitespace)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy