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

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

// Copyright: 2010 - 2016 https://github.com/ensime/ensime-server/graphs
// Licence: http://www.gnu.org/licenses/gpl-3.0.en.html
package org.ensime.sexp

import org.parboiled2._

import scala.util.{ Failure, Success }

/**
 * 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 = {
    val parser = new SexpParser(desc)
    parser.SexpP.run() match {
      case Success(d) =>
        d
      case Failure(error: ParseError) =>
        val msg = parser.formatError(error, new ErrorFormatter(showTraces = true))
        throw new Exception("Failed to parse sexp: " + msg)
      case Failure(other) =>
        throw new Exception("Failed to parse sexp: ", other)
    }
  }

  // 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 SymbolsPredicate = CharPredicate("+-*/_~!@$%^&=:<>{}")
  val NormalCharPredicate = CharPredicate.Printable -- "\"\\"
  val WhiteSpacePredicate = CharPredicate(" \n\r\t\f")
  val NotNewLinePredicate = CharPredicate.Printable -- '\n'
  val SymbolStartCharPredicate = CharPredicate.AlphaNum ++ SymbolsPredicate
  val SymbolBodyCharPredicate = SymbolStartCharPredicate ++ "."
  val PlusMinusPredicate = CharPredicate("+-")
  val ExpPredicate = CharPredicate("eE")

  val QuoteBackslash = CharPredicate("\"\\")
  val QuoteSlashBackSlash = QuoteBackslash ++ "/"
  val NCCharPredicate = CharPredicate.All -- "\"\\"

}

/**
 * Parse Emacs Lisp into an `Sexp`. Other lisp variants may
 * require tweaking, e.g. Scheme's nil, infinity, NaN, etc.
 */
class SexpParser(val input: ParserInput) extends Parser with StringBuilding {

  import SexpParser._
  private def SexpP: Rule1[Sexp] = rule {
    SexpAtomP | SexpListP | SexpEmptyList | SexpConsP | SexpQuotedP
  }

  private def SexpConsP: Rule1[SexpCons] = rule {
    LeftBrace ~ SexpP ~ Whitespace ~ '.' ~ Whitespace ~ SexpP ~ RightBrace ~> {
      (x: Sexp, y: Sexp) => SexpCons(x, y)
    }
  }

  private def SexpListP: Rule1[Sexp] = rule {
    LeftBrace ~ SexpP ~ zeroOrMore(Whitespace ~ SexpP) ~ RightBrace ~> {
      (head: Sexp, tail: Seq[Sexp]) => { SexpList(head :: tail.toList) }
    }
  }

  private def SexpAtomP: Rule1[SexpAtom] = rule {
    SexpCharP | SexpStringP | SexpNaNP | SexpNumberP | SexpSymbolP
  }

  private def SexpCharP: Rule1[SexpChar] = rule {
    '?' ~ NormalChar ~> { SexpChar }
  }

  def SexpStringP = rule { '"' ~ clearSB() ~ CharactersSB ~ '"' ~ push(SexpString(sb.toString)) }

  def CharactersSB = rule { zeroOrMore(NormalCharSB | '\\' ~ EscapedCharSB) }

  def NormalCharSB = rule { NCCharPredicate ~ appendSB() }

  def EscapedCharSB = rule(
    QuoteSlashBackSlash ~ appendSB()
      | '\"' ~ appendSB('\"')
      | 'b' ~ appendSB('\b')
      | 's' ~ appendSB(' ')
      | 'f' ~ appendSB('\f')
      | 'n' ~ appendSB('\n')
      | 'r' ~ appendSB('\r')
      | 't' ~ appendSB('\t')
      | ' ' ~ appendSB("") // special emacs magic for comments \ are removed
      | '\n' ~ appendSB("")
      | 'a' ~ appendSB('\u0007') // bell
      | 'v' ~ appendSB('\u000b') // vertical tab
      | 'e' ~ appendSB('\u001b') // escape
      | 'd' ~ appendSB('\u007f') // DEL
  )

  def SexpNumberP = rule {
    capture(Integer ~ optional(Frac) ~ optional(Exp)) ~> { s: String => SexpNumber(BigDecimal(s)) }
  }

  import CharPredicate.{ Digit, Digit19 }

  def Integer = rule {
    optional('-') ~ (Digit19 ~ Digits | Digit)
  }

  def Digits = rule {
    oneOrMore(Digit)
  }

  def Frac = rule {
    '.' ~ Digits
  }

  def Exp = rule {
    ExpPredicate ~ optional(PlusMinusPredicate) ~ Digits
  }

  private def SexpNaNP: Rule1[SexpAtom] = rule {
    "-1.0e+INF" ~ push(SexpNegInf) |
      "1.0e+INF" ~ push(SexpPosInf) |
      optional('-') ~ "0.0e+NaN" ~ push(SexpNaN)
  }

  private def SexpQuotedP: Rule1[Sexp] = rule {
    '\'' ~ SexpP ~> { v: Sexp => SexpCons(SexpQuote, v) }
  }

  private def SexpSymbolP: Rule1[SexpAtom] = rule {
    // ? allowed at the end of symbol names
    capture(oneOrMore(SymbolStartCharPredicate) ~ zeroOrMore(SymbolBodyCharPredicate) ~ optional('?')) ~> { sym: String =>
      if (sym == "nil") SexpNil
      else SexpSymbol(sym)
    }
  }

  private def SexpEmptyList: Rule1[SexpNil.type] = rule {
    LeftBrace ~ RightBrace ~ push(SexpNil)
  }

  private def NormalChar: Rule1[Char] = rule {
    NormalCharPredicate ~ push(lastChar)
  }

  private def Whitespace: Rule0 = rule {
    zeroOrMore(Comment | WhiteSpacePredicate)
  }

  private def Comment: Rule0 = rule {
    ';' ~ zeroOrMore(NotNewLinePredicate) ~ ("\n" | EOI)
  }

  private def LeftBrace: Rule0 = rule {
    Whitespace ~ '(' ~ Whitespace
  }

  private def RightBrace: Rule0 = rule {
    Whitespace ~ ')' ~ Whitespace
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy