package wacc

import parsley.Parsley
import parsley.character
import parsley.token.{Basic, Lexer}
import parsley.token.descriptions.*
import parsley.token.errors._

/** ErrorConfig for producing more informative error messages
  */
val errConfig = new ErrorConfig {
  override def labelSymbol = Map(
    "!=" -> Label("binary operator"),
    "%" -> Label("binary operator"),
    "&&" -> Label("binary operator"),
    "*" -> Label("binary operator"),
    "/" -> Label("binary operator"),
    "<" -> Label("binary operator"),
    "<=" -> Label("binary operator"),
    "==" -> Label("binary operator"),
    ">" -> Label("binary operator"),
    ">=" -> Label("binary operator"),
    "||" -> Label("binary operator"),
    "!" -> Label("unary operator"),
    "len" -> Label("unary operator"),
    "ord" -> Label("unary operator"),
    "chr" -> Label("unary operator"),
    "bool" -> Label("valid type"),
    "char" -> Label("valid type"),
    "int" -> Label("valid type"),
    "pair" -> Label("valid type"),
    "string" -> Label("valid type"),
    "fst" -> Label("pair extraction"),
    "snd" -> Label("pair extraction"),
    "false" -> Label("boolean literal"),
    "true" -> Label("boolean literal"),
    "=" -> Label("assignment"),
    "[" -> Label("array index")
  )
}
object lexer {
  val escapedChars: Map[String, Int] = Map(
    "0" -> '\u0000',
    "b" -> '\b',
    "t" -> '\t',
    "n" -> '\n',
    "f" -> '\f',
    "r" -> '\r',
    "\\" -> '\\',
    "'" -> '\'',
    "\"" -> '\"'
  )

  /** Language description for the WACC lexer
    */
  private val desc = LexicalDesc.plain.copy(
    nameDesc = NameDesc.plain.copy(
      identifierStart = Basic(c => c.isLetter || c == '_'),
      identifierLetter = Basic(c => c.isLetterOrDigit || c == '_')
    ),
    symbolDesc = SymbolDesc.plain.copy(
      hardKeywords = Set(
        "begin", "end", "is", "skip", "if", "then", "else", "fi", "while", "do", "done", "read",
        "free", "return", "exit", "print", "println", "true", "false", "int", "bool", "char",
        "string", "pair", "newpair", "fst", "snd", "call", "chr", "ord", "len", "null"
      ),
      hardOperators = Set(
        "+", "-", "*", "/", "%", ">", "<", ">=", "<=", "==", "!=", "&&", "||", "!"
      )
    ),
    spaceDesc = SpaceDesc.plain.copy(
      lineCommentStart = "#"
    ),
    textDesc = TextDesc.plain.copy(
      graphicCharacter = Basic(c => c >= ' ' && c != '\\' && c != '\'' && c != '"'),
      escapeSequences = EscapeDesc.plain.copy(
        literals =
          escapedChars.filter { (s, chr) => chr.toChar.toString == s }.map(_._2.toChar).toSet,
        mapping = escapedChars.filter { (s, chr) => chr.toChar.toString != s }
      )
    ),
    numericDesc = NumericDesc.plain.copy(
      decimalExponentDesc = ExponentDesc.NoExponents
    )
  )

  /** Token definitions for the WACC lexer
    */
  private val lexer = Lexer(desc, errConfig)
  val ident = lexer.lexeme.names.identifier
  val integer = lexer.lexeme.integer.decimal32[Int]
  val negateCheck = lexer.nonlexeme.symbol("-") ~> character.digit
  val charLit = lexer.lexeme.character.ascii
  val stringLit = lexer.lexeme.string.ascii
  val implicits = lexer.lexeme.symbol.implicits

  /** Tokens for producing lexer-backed error messages
    */
  val errTokens = Seq(
    lexer.nonlexeme.names.identifier.map(v => s"identifier $v"),
    lexer.nonlexeme.integer.decimal32[Int].map(n => s"integer $n"),
    lexer.nonlexeme.character.ascii.map(c => s"character literal \'$c\'"),
    lexer.nonlexeme.string.ascii.map(s => s"string literal \"$s\""),
    lexer.nonlexeme.symbol("[").as("array literal"),
    character.whitespace.map(_ => "")
  ) ++ desc.symbolDesc.hardKeywords.map { k =>
    lexer.nonlexeme.symbol(k).as(s"keyword $k")
  }
  def fully[A](p: Parsley[A]): Parsley[A] = lexer.fully(p)
}
