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

io.prophecy.abinitio.dml.Parser.scala Maven / Gradle / Ivy

There is a newer version: 6.3.0-3.3.0
Show newest version
package io.prophecy.abinitio.dml

import io.prophecy.abinitio.dml.pSchemaParser.condition
import io.prophecy.libs.{
  FFAST,
  FFCompoundSchemaRow,
  FFConditionalSchemaRow,
  FFDataFormat,
  FFDateFormat,
  FFDateTimeFormat,
  FFDefaultVal,
  FFDoubleDefaultVal,
  FFIncludeFileRow,
  FFIntDefaultVal,
  FFNoDefaultVal,
  FFNullDefaultVal,
  FFNumberArrayFormat,
  FFNumberFormat,
  FFRecordType,
  FFSchemaRecord,
  FFSchemaRow,
  FFSimpleSchemaList,
  FFSimpleSchemaRow,
  FFStringArrayFormat,
  FFStringDefaultVal,
  FFStringFormat,
  FFStructArrayType,
  FFStructType,
  FFTypeName,
  FFTypeNameWithProperties,
  FFUnionType,
  FFUnknownFormat,
  FFVoidFormat
}
import org.apache.commons.lang.StringEscapeUtils
import org.slf4j.LoggerFactory

import scala.collection.immutable.Map
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.Source
import scala.util.{Left,                              Right}
import scala.util.parsing.combinator.{PackratParsers, Parsers}
import scala.util.parsing.input.{NoPosition,          Position, Reader}

object pSchemaCompiler {
  private val LOGGER = LoggerFactory.getLogger(this.getClass)
  var debug          = false

  def apply(
    code:           String,
    merge:          Boolean = true,
    keepConditions: Boolean = false
  ): Either[pSchemaCompilationError, FFAST] = {
    if (debug) LOGGER.info(s"Parsing Schema: ${code}")
    val x = for {
      tokens ← pSchemaLexer(code).right
      ast ← pSchemaParser(tokens, merge, keepConditions).right
    } yield ast
    if (pSchemaCompiler.debug) LOGGER.debug(s"Parsed Schema: ${x}")
    x
  }

  def parseDML(field: String, addHeader: Boolean = true, merge: Boolean = true): FFAST = {
    val dml         = if (addHeader) s"record\n$field;\nend;" else field
    val newDML      = StringEscapeUtils.unescapeXml(dml.replace(" ", " "))
    val compiledAST = apply(newDML,                                  merge)
    compiledAST match {
      case Right(schemaRecord) ⇒
        schemaRecord
      case Left(errorDetails) ⇒
        // logger.error(s"Error parsing \nERR::SCHEMA: $coreSchema\nERR::ERROR: $errorDetails")
        throw new Exception(s"Error parsing \nERR::SCHEMA: $dml\nERR::ERROR: $errorDetails")
    }
  }
}

object pSchemaParser extends Parsers with PackratParsers {
  import org.slf4j.LoggerFactory
  import pSchemaCompiler.parseDML
  private val LOGGER = LoggerFactory.getLogger(classOf[SchemaTokenReader])
  var debug          = false
  var debugTokens    = false
  val DEFAULT_PREC   = 38

  override type Elem = SchemaToken

  class SchemaTokenReader(tokens: Seq[SchemaToken]) extends Reader[SchemaToken] {
    if (debugTokens) LOGGER.info("TOKENS: " + tokens.take(10).toString())
    override def first: SchemaToken         = tokens.head
    override def atEnd: Boolean             = tokens.isEmpty
    override def pos:   Position            = tokens.headOption.map(_.pos).getOrElse(NoPosition)
    override def rest:  Reader[SchemaToken] = new SchemaTokenReader(tokens.tail)
  }

  /**
    * Method to replace unknown record types with struct type mapping provided in passed map.
    *
    * @param row
    * @param recordNameToRecordMap
    * @return
    */
  private def replaceUnknownTypesIfPossible(
    oldRow:                FFSchemaRow,
    recordNameToRecordMap: Map[String, FFSchemaRecord],
    keepConditions:        Boolean
  ): FFSchemaRow = {
    if (recordNameToRecordMap.isEmpty) oldRow
    else {
      val row =
        if (oldRow.isInstanceOf[FFConditionalSchemaRow]) oldRow.asInstanceOf[FFConditionalSchemaRow].schemaRow
        else oldRow
      val resultRow = row match {
        case x @ FFSimpleSchemaRow(name, FFUnknownFormat(FFTypeName(unknownType, None), arraySizeInfo), value) ⇒
          if (recordNameToRecordMap.contains(unknownType)) {
            val replacedByRows = recordNameToRecordMap.get(unknownType).get.rows
            if (
              replacedByRows.size == 1 && replacedByRows.head
                .isInstanceOf[FFSimpleSchemaRow] && replacedByRows.head.asInstanceOf[FFSimpleSchemaRow].name.isEmpty
            ) {
              FFSimpleSchemaRow(name, replacedByRows.head.asInstanceOf[FFSimpleSchemaRow].format, value)
            } else {
              FFCompoundSchemaRow(
                if (arraySizeInfo.isDefined) FFStructArrayType(name, arraySizeInfo) else FFStructType(name),
                replacedByRows
              )
            }
          } else x
        case x @ FFSimpleSchemaRow(name, format, value) ⇒ x
        case FFCompoundSchemaRow(compound, rows) ⇒
          FFCompoundSchemaRow(compound,
                              rows.map(replaceUnknownTypesIfPossible(_, recordNameToRecordMap, keepConditions))
          )
        case x @ FFSimpleSchemaList(rows) ⇒ x
      }
      if (keepConditions && oldRow.isInstanceOf[FFConditionalSchemaRow])
        oldRow.asInstanceOf[FFConditionalSchemaRow].copy(schemaRow = resultRow)
      else resultRow
    }
  }

  def read_file(filename: String): String = {
    if (filename == null) null
    else {
      try {
        val bufferedSource = Source.fromFile(filename)
        val content        = bufferedSource.getLines().mkString("\n")
        bufferedSource.close
        content
      } catch {
        case _: Throwable ⇒ ""
      }
    }
  }

  private def replaceFFConditionalRowsIfNeeded(row: FFSchemaRow, keepConditions: Boolean): FFSchemaRow = {
    row match {
      case x @ FFConditionalSchemaRow(condition, schemaRow) ⇒
        if (keepConditions) x else x.schemaRow
      case x @ FFCompoundSchemaRow(compound, rows) ⇒
        x.copy(rows = rows.map(row ⇒ replaceFFConditionalRowsIfNeeded(row, keepConditions)))
      case x ⇒ x
    }
  }

  private def replaceFFConditionalRowsIfNeeded(input: FFSchemaRecord, keepConditions: Boolean): FFAST = {
    val newRows = input.rows.map(replaceFFConditionalRowsIfNeeded(_, keepConditions))
    input.copy(rows = generateElseConditions(newRows))
  }

  private def generateElseCondition(conditions: Seq[String]): String = {
    conditions match {
      case Nil          ⇒ throw new Exception("Cant generate else condition when no if condition provided")
      case head :: Nil  ⇒ s"!($head)"
      case head :: tail ⇒ s"!($head) && " + generateElseCondition(tail)
    }
  }

  private def generateElseConditions(rows: Seq[FFSchemaRow]): Seq[FFSchemaRow] = {
    val conditionQueue = ListBuffer[FFConditionalSchemaRow]()
    rows.map {
      case row @ FFConditionalSchemaRow("else", schemaRow) ⇒
        val elseCondition = generateElseCondition(conditionQueue.map(_.condition).toList)
        val newRow        = row.copy(condition = elseCondition)
        conditionQueue += newRow
        newRow
      case row @ FFConditionalSchemaRow(condition, schemaRow) ⇒
        conditionQueue += row
        row
      case row @ FFCompoundSchemaRow(compound, rows) ⇒
        conditionQueue.clear()
        row.copy(rows = generateElseConditions(rows))
      case row ⇒
        conditionQueue.clear()
        row
    }
  }

  /**
    * Method to take care of merging of multiple named schema record definition like in below
    *                    type tmp_record_type  = record
    *                    string("") dimension ;
    *                    integer(8)    dxf_sk =   0;
    *                    end;
    *
    *                    metadata type = tmp_record_type;
    *
    * tmp_record_type in last record definition is replaced with tmp_record_type definition in first record definition to
    * return final schema Record as below
    *                    record
    *                    string("") dimension ;
    *                    integer(8)    dxf_sk =   0;
    *                    end;
    *
    * Also This method replaces unknown struct types with their definitions like in below example
    *                    type hash_key_combine_type  =
    *                    record
    *                    unsigned integer(8)  dxf_hk_part1 =  0 ;
    *                    end ;
    *
    *                    record
    *                    decimal("") fsk_hk;
    *                    hash_key_combine_type fsk_hk2;
    *                    end;
    *
    * hash_key_combine_type in second record definition is replaced with its definition in first record definition to
    * give final record definition as
    *                    record
    *                    decimal("") fsk_hk;
    *                    record
    *                    unsigned integer(8)  dxf_hk_part1 =  0 ;
    *                    end fsk_hk2;
    *                    end;
    *
    * @param records
    * @return
    */
  private def mergeRecordDefinitions(
    records:        FFSchemaRecords,
    merge:          Boolean = true,
    keepConditions: Boolean
  ): FFAST = {
    val recordNameToRecordMap = mutable.Map[String, FFSchemaRecord]()
    val includedDefinitions = records.recordDefinitions.flatMap {
      case FFSchemaRecordDefinition(FFSchemaRecord("include", List(FFIncludeFileRow(filePath))), None) ⇒
        val content = read_file(filePath)
        if (content.isEmpty) Nil
        else
          parseDML(content, false, false).asInstanceOf[FFSchemaRecords].recordDefinitions
      case x @ _ ⇒ x :: Nil
    }

    val mergedRecords = includedDefinitions.map { recordDefinition ⇒
      val rows = recordDefinition.schemaRecord.rows.filter {
        case FFSimpleSchemaRow(name, format, value) if name.endsWith("()") ⇒ false
        case _ ⇒ true
      }
      val modifiedDefinition = if (rows.nonEmpty) {
        val modifiedRows =
          rows.map(replaceUnknownTypesIfPossible(_, recordNameToRecordMap.toMap, keepConditions))
        val newSchemaRecord =
          if (
            rows.length == 1 && rows.head.isInstanceOf[FFSimpleSchemaRow] && rows.head
              .asInstanceOf[FFSimpleSchemaRow]
              .format
              .isInstanceOf[FFUnknownFormat] && rows.head.asInstanceOf[FFSimpleSchemaRow].name.isEmpty
          )
            recordDefinition.schemaRecord.copy(
              rows = modifiedRows,
              recordType = recordNameToRecordMap
                .getOrElse(rows.head
                             .asInstanceOf[FFSimpleSchemaRow]
                             .format
                             .asInstanceOf[FFUnknownFormat]
                             .name
                             .name,
                           recordDefinition.schemaRecord
                )
                .recordType
            )
          else recordDefinition.schemaRecord.copy(rows = modifiedRows)
        recordDefinition.copy(schemaRecord = newSchemaRecord)
      } else {
        recordDefinition
      }
      modifiedDefinition.name match {
        case None ⇒
          modifiedDefinition.schemaRecord
        case Some(name) ⇒
          val newSchemaRecord =
            if (modifiedDefinition.schemaRecord.rows.isEmpty && recordNameToRecordMap.contains(name)) {
              recordNameToRecordMap.getOrElse(name, modifiedDefinition.schemaRecord)
            } else modifiedDefinition.schemaRecord
          recordNameToRecordMap.put(name, newSchemaRecord)
          newSchemaRecord
      }
    }
    val result = replaceFFConditionalRowsIfNeeded(mergedRecords.last, keepConditions)
    result
  }

  def apply(
    tokens:         Seq[SchemaToken],
    merge:          Boolean = true,
    keepConditions: Boolean = false
  ): Either[pSchemaParserError, FFAST] = {
    val reader = new PackratReader(new SchemaTokenReader(tokens))
    schemaRecords(reader) match {
      case NoSuccess(msg,  next) ⇒ Left(pSchemaParserError(pLocation(next.pos.line, next.pos.column), msg))
      case Success(result, _) ⇒
        val curResult = if (merge) mergeRecordDefinitions(result, merge, keepConditions) else result
        val finalResult = curResult match {
          case FFSchemaRecord(recordType, rows)
              if rows.length == 1 && rows.head.isInstanceOf[FFCompoundSchemaRow] && rows.head
                .asInstanceOf[FFCompoundSchemaRow]
                .compound
                .name
                .get
                .isEmpty ⇒
            FFSchemaRecord(recordType, rows.head.asInstanceOf[FFCompoundSchemaRow].rows)
          case _ ⇒ curResult
        }
        Right(finalResult)
    }
  }

  def dbg[T](p: ⇒ Parser[T])(name: String): Parser[T] = {
    if (!debug) p
    // uncomment a parser to see all it's application attempts (success + failure)
    name match {
      // Row types
      case "multiRow"  ⇒ p
      case "simpleRow" ⇒ p // log(p)(name)
//      case "simpleRow1"               ⇒ p // log(p)(name)
      case "noAssignmentSimpleRow"    ⇒ p // log(p)(name)
      case "structRecord"             ⇒ p // log(p)(name)
      case "structRecordWithoutEnd"   ⇒ p
      case "unionRecord1"             ⇒ p // log(p)(name)
      case "unionRecord2"             ⇒ p // log(p)(name)
      case "structArrayRecord"        ⇒ p // log(p)(name)
      case "structArrayRecord2"       ⇒ p
      case "structArrayRecord2"       ⇒ p
      case "functionCallRow"          ⇒ p
      case "discardedFunctionCallRow" ⇒ p
      case "ifConditionRow"           ⇒ p
      case "ifExprRow2"               ⇒ p
      case "ifExprRow3"               ⇒ p
      case "ifElseExprRow3"           ⇒ p
      case "ifElseExprRow4"           ⇒ p
      case "ifElseExprRow2"           ⇒ p

      // Declarations
      case "oneLiteral"                              ⇒ p // log(p)(name)
      case "oneLiteralWithDateTimeFormat"            ⇒ p // log(p)(name)
      case "oneLiteralWithInnerArrayType"            ⇒ p
      case "oneLiteralWithArrayType2"                ⇒ p
      case "oneLiteralWithArrayType"                 ⇒ p
      case "twoLiteral"                              ⇒ p // log(p)(name)
      case "twoLiteralWithZeroFilling"               ⇒ p // log(p)(name)
      case "twoLiteralWithZoned"                     ⇒ p
      case "oneLiteralMaxLength"                     ⇒ p // log(p)(name)
      case "oneLiteralPrecisionAndMaxLength"         ⇒ p // log(p)(name)
      case "oneLiteralWithCentury"                   ⇒ p // log(p)(name)
      case "twoLiteralMaxLength1"                    ⇒ p // log(p)(name)
      case "oneLiteralWithConstantArrayType"         ⇒ p
      case "decimalWithSingleLiteralAndSignReserved" ⇒ p
      case "decimalWithDoubleLiteralAndSignReserved" ⇒ p
      case "decimalWithNumbersAndSignReserved"       ⇒ p // log(p)(name)
      case "decimalWithNumbersAndZeroFill"           ⇒ p
      case "twoLiteralMaxLengthWithSignReserved"     ⇒ p // log(p)(name)
      case "singleLiteralMaxLengthWithSignReserved"  ⇒ p
      case "twoLiteralMaxLength2"                    ⇒ p // log(p)(name)
      case "twoLiteralMaxLengthAndSignReserved"      ⇒ p
      case "decimalWithDot"                          ⇒ p // log(p)(name)
      case "decimalWithGenericProperty"              ⇒ p
      case "decimalWithDotWithSignReserved"          ⇒ p // log(p)(name)
      case "noLiteral"                               ⇒ p // log(p)(name)
      case "structArrayUnBoundedRecord"              ⇒ p
    }
  }

  case class FFSchemaRecords(recordDefinitions: Seq[FFSchemaRecordDefinition]) extends FFAST
  case class FFSchemaRows(rows: Seq[FFSchemaRow]) extends FFAST
  case class FFSchemaRecordDefinition(schemaRecord: FFSchemaRecord, name: Option[String] = None) extends FFAST

  lazy val schemaRecords: Parser[FFSchemaRecords] = positioned {
    rep1(schemaRecord) ^^ {
      case records ⇒
        FFSchemaRecords(records)
    }
  }

  lazy val schemaRecord: Parser[FFSchemaRecordDefinition] = positioned {
    val includeRow = identifier ~ literal ~ SEMICOLON() ^^ {
      case IDENTIFIER("include") ~ STRING_LITERAL(content) ~ _ ⇒
        FFSchemaRecordDefinition(FFSchemaRecord("include", FFIncludeFileRow(content) :: Nil))
    }

    val x0 = VOID() ~ OPAREN() ~ literal ~ CPAREN() ^^ {
      case _ ~ _ ~ literal ~ _ ⇒
        literal match {
          case INT_LITERAL(value) ⇒
            FFSchemaRecordDefinition(
              FFSchemaRecord("void",
                             FFSimpleSchemaRow("",
                                               FFVoidFormat(FFTypeName("ByteType", delimiter = None), Some(value)),
                                               FFNoDefaultVal()
                             ) :: Nil
              )
            )
          case x ⇒ throw new Exception(s"Bad literal $x")
        }
    }

    val x01 = STRING() ~ OPAREN() ~ literal ~ CPAREN() ~ SEMICOLON() ^^ {
      case _ ~ _ ~ literal ~ _ ~ _ ⇒
        literal match {
          case STRING_LITERAL(value) ⇒
            FFSchemaRecordDefinition(
              FFSchemaRecord(
                "record",
                FFSimpleSchemaRow("",
                                  FFStringFormat(FFTypeName("StringType", delimiter = Some(value)), None, None),
                                  FFNoDefaultVal()
                ) :: Nil
              )
            )
          case x ⇒ throw new Exception(s"Bad literal $x")
        }
    }

    val x001 = RECORD() ~ END() ~ SEMICOLON() ^^ {
      case _ ~ _ ~ _ ⇒
        FFSchemaRecordDefinition(
          FFSchemaRecord("UnknownType", Nil)
        )
    }
    val x1 = recordType ~ rep(record) ~ END() ^^ {
      case FFRecordType(rType) ~ statements ~ _ ⇒
        FFSchemaRecordDefinition(
          FFSchemaRecord(rType,
                         statements.flatMap(_.rows).flatMap { x ⇒
                           x match {
                             case list: FFSimpleSchemaList ⇒
                               list.rows.map { y ⇒
                                 y.asInstanceOf[FFSchemaRow]
                               }
                             case _ ⇒ List(x)
                           }
                         }
          )
        )
    }
    val x2 = recordType ~ rep(record) ~ END() ~ SEMICOLON() ^^ {
      case FFRecordType(rType) ~ statements ~ _ ~ _ ⇒
        FFSchemaRecordDefinition(
          FFSchemaRecord(rType,
                         statements.flatMap(_.rows).flatMap { x ⇒
                           x match {
                             case list: FFSimpleSchemaList ⇒
                               list.rows.map { y ⇒
                                 y.asInstanceOf[FFSchemaRow]
                               }
                             case _ ⇒ List(x)
                           }
                         }
          )
        )
    }

    val x8 = recordType ~ rep(record) ~ SEMICOLON() ^^ {
      case FFRecordType(rType) ~ statements ~ _ ⇒
        FFSchemaRecordDefinition(
          FFSchemaRecord(rType,
                         statements.flatMap(_.rows).flatMap { x ⇒
                           x match {
                             case list: FFSimpleSchemaList ⇒
                               list.rows.map { y ⇒
                                 y.asInstanceOf[FFSchemaRow]
                               }
                             case _ ⇒ List(x)
                           }
                         }
          )
        )
    }

    val x6 = recordType ~ rep1(
      record
    ) ~ IF() ~ OPAREN() ~ identifier ~ EQUALS() ~ EQUALS() ~ literal ~ CPAREN() ~ BEGIN() ~ rep1(
      record
    ) ~ END() ~ opt(
      ELSE() ~ IF() ~ OPAREN() ~ identifier ~ EQUALS() ~ EQUALS() ~ literal ~ CPAREN() ~ BEGIN() ~ rep1(
        record
      ) ~ END()
    ) ~ opt(ELSE() ~ BEGIN() ~ rep1(record) ~ END()) ~ rep(record) ~ END() ~ SEMICOLON() ^^ {
      case FFRecordType(
            rType
          ) ~ statements1 ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ statements2 ~ _ ~ elseIfOpt ~ elseOpt ~ remainingStmts ~ _ ~ _ ⇒
        val statements3     = if (elseIfOpt.isDefined) elseIfOpt.get._1._2 else Nil
        val statements4     = if (elseOpt.isDefined) elseOpt.get._1._2 else Nil
        val finalStatements = statements1 ++ statements2 ++ statements3 ++ statements4 ++ remainingStmts
        FFSchemaRecordDefinition(
          FFSchemaRecord(rType,
                         finalStatements.flatMap(_.rows).flatMap { x ⇒
                           x match {
                             case list: FFSimpleSchemaList ⇒
                               list.rows.map { y ⇒
                                 y.asInstanceOf[FFSchemaRow]
                               }
                             case _ ⇒ List(x)
                           }
                         }
          )
        )
    }

    val x7 = recordType ~ rep1(
      record
    ) ~ IF() ~ OPAREN() ~ identifier ~ EQUALS() ~ EQUALS() ~ literal ~ CPAREN() ~ BEGIN() ~ rep1(
      record
    ) ~ END() ~ IF() ~ OPAREN() ~ identifier ~ EQUALS() ~ EQUALS() ~ literal ~ CPAREN() ~ BEGIN() ~ rep1(
      record
    ) ~ END() ~ BEGIN() ~ rep1(record) ~ END() ~ SEMICOLON() ^^ {
      case FFRecordType(
            rType
          ) ~ statements1 ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ statements2 ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ _ ~ statements3 ~ _ ~ _ ~ statements4 ~ _ ~ _ ⇒
        val finalStatements = statements1 ++ statements2 ++ statements3 ++ statements4
        FFSchemaRecordDefinition(
          FFSchemaRecord(rType,
                         finalStatements.flatMap(_.rows).flatMap { x ⇒
                           x match {
                             case list: FFSimpleSchemaList ⇒
                               list.rows.map { y ⇒
                                 y.asInstanceOf[FFSchemaRow]
                               }
                             case _ ⇒ List(x)
                           }
                         }
          )
        )
    }

    val x3 = TYPE() ~ identifier ~ EQUALS() ~ recordType ~ rep(record) ~ END() ~ SEMICOLON() ^^ {
      case _ ~ IDENTIFIER(name) ~ _ ~ FFRecordType(rType) ~ statements ~ _ ~ _ ⇒
        FFSchemaRecordDefinition(
          FFSchemaRecord(rType,
                         statements.flatMap(_.rows).flatMap { x ⇒
                           x match {
                             case list: FFSimpleSchemaList ⇒
                               list.rows.map { y ⇒
                                 y.asInstanceOf[FFSchemaRow]
                               }
                             case _ ⇒ List(x)
                           }
                         }
          ),
          Some(name)
        )
    }

    val x5 = TYPE() ~ identifier ~ EQUALS() ~ recordType ~ rep(
      record
    ) ~ END() ~ OBRACK() ~ theFormat ~ CBRACK() ~ SEMICOLON() ^^ {
      case _ ~ IDENTIFIER(name) ~ _ ~ FFRecordType(rType) ~ statements ~ _ ~ _ ~ dataType ~ _ ~ _ ⇒
        FFSchemaRecordDefinition(
          FFSchemaRecord(rType,
                         statements.flatMap(_.rows).flatMap { x ⇒
                           x match {
                             case list: FFSimpleSchemaList ⇒
                               list.rows.map { y ⇒
                                 y.asInstanceOf[FFSchemaRow]
                               }
                             case _ ⇒ List(x)
                           }
                         }
          ),
          Some(name)
        )
    }

    val x4 = METADATA() ~ TYPE() ~ EQUALS() ~ identifier ~ SEMICOLON() ^^ {
      case _ ~ _ ~ _ ~ IDENTIFIER(name) ~ _ ⇒
        FFSchemaRecordDefinition(
          FFSchemaRecord("record",
                         FFSimpleSchemaRow("", FFUnknownFormat(FFTypeName(name, None), None), FFNoDefaultVal()) :: Nil
          ),
          Some(name)
        )
    }
    val x41 =
      TYPE() ~ identifier ~ EQUALS() ~ identifier ~ OBRACK() ~ datatypeWithTypeInfo ~ OPAREN() ~ literal ~ CPAREN() ~ CBRACK() ~ SEMICOLON() ^^ {
        case _ ~ IDENTIFIER(name1) ~ _ ~ IDENTIFIER(name) ~ _ ~ arrayType ~ _ ~ literal ~ _ ~ _ ~ _ ⇒
          FFSchemaRecordDefinition(
            FFSchemaRecord("record",
                           FFSimpleSchemaRow("",
                                             FFUnknownFormat(FFTypeName(name, None), Some(arrayType.name)),
                                             FFNoDefaultVal()
                           ) :: Nil
            ),
            Some(name1)
          )
      }

    val x10 = TYPE() ~ identifier ~ EQUALS() ~ theFormat ~ SEMICOLON() ^^ {
      case _ ~ IDENTIFIER(typeName) ~ _ ~ dataType ~ _ ⇒
        FFSchemaRecordDefinition(FFSchemaRecord("", FFSimpleSchemaRow("", dataType, FFNoDefaultVal()) :: Nil),
                                 Some(typeName)
        )
    }

    val x9 = METADATA() ~ TYPE() ~ EQUALS() ~ recordType ~ rep(record) ~ END() ~ SEMICOLON() ^^ {
      case _ ~ _ ~ _ ~ FFRecordType(rType) ~ statements ~ _ ~ _ ⇒
        FFSchemaRecordDefinition(
          FFSchemaRecord(rType,
                         statements.flatMap(_.rows).flatMap { x ⇒
                           x match {
                             case list: FFSimpleSchemaList ⇒
                               list.rows.map { y ⇒
                                 y.asInstanceOf[FFSchemaRow]
                               }
                             case _ ⇒ List(x)
                           }
                         }
          )
        )
    }

    includeRow | x6 | x7 | x2 | x8 | x1 | x3 | x9 | x4 | x41 | x5 | x0 | x01 | x10 | x001
  }

  lazy val record: Parser[FFSchemaRows] = positioned {

    val simpleRow = theFormat ~ identifier ~ assignment ^^ {
      case dataType ~ IDENTIFIER(name) ~ assignment1 ⇒
        FFSchemaRows(FFSimpleSchemaRow(name, dataType, assignment1) :: Nil)
    }

    val ifExprRow2 =
      IF() ~ repN(2, OPAREN()) ~ (nestedTernaryCondition | ternaryCondition | condition) ~ repN(2,
                                                                                                CPAREN()
      ) ~ record ^^ {
        case _ ~ _ ~ condition ~ _ ~ row ⇒
          row.copy(rows = row.rows.map(row ⇒ FFConditionalSchemaRow(condition.startType, row)))
      }
    val ifExprRow3 = IF() ~ OPAREN() ~ (nestedTernaryCondition | ternaryCondition | condition) ~ CPAREN() ~ record ^^ {
      case _ ~ _ ~ condition ~ _ ~ row ⇒
        row.copy(rows = row.rows.map(row ⇒ FFConditionalSchemaRow(condition.startType, row)))
    }
    val ifElseExprRow2 =
      ELSE() ~ IF() ~ repN(2, OPAREN()) ~ (nestedTernaryCondition | ternaryCondition | condition) ~ repN(2,
                                                                                                         CPAREN()
      ) ~ record ^^ {
        case _ ~ _ ~ _ ~ condition ~ _ ~ row ⇒
          row.copy(rows = row.rows.map(row ⇒ FFConditionalSchemaRow(condition.startType, row)))
      }
    val ifElseExprRow3 =
      ELSE() ~ IF() ~ OPAREN() ~ (nestedTernaryCondition | ternaryCondition | condition) ~ CPAREN() ~ record ^^ {
        case _ ~ _ ~ _ ~ condition ~ _ ~ row ⇒
          row.copy(rows = row.rows.map(row ⇒ FFConditionalSchemaRow(condition.startType, row)))
      }
    val ifElseExprRow4 =
      ELSE() ~ record ^^ {
        case _ ~ row ⇒
          row.copy(rows = row.rows.map(row ⇒ FFConditionalSchemaRow("else", row)))
      }
    val discardedFunctionCallRow =
      theFormat ~ identifier ~ OPAREN() ~ theFormat ~ identifier ~ CPAREN() ~ assignment ^^ {
        case colDataType ~ IDENTIFIER(colName) ~ _ ~ datatype ~ IDENTIFIER(argName) ~ _ ~ assignment ⇒
          FFSchemaRows(FFSimpleSchemaRow(colName + "()", colDataType, assignment) :: Nil)
      }

    val functionCallRow = theFormat ~ identifier ~ OPAREN() ~ CPAREN() ~ assignment ^^ {
      case datatype ~ IDENTIFIER(name) ~ _ ~ _ ~ assignment ⇒
        FFSchemaRows(FFSimpleSchemaRow(name + "()", datatype, assignment) :: Nil)
    }

    val multiRow = theFormat ~ rep(identifier ~ assignment ~ COMMA()) ~ identifier ~ assignment ^^ {
      case dataType ~ inputs1 ~ IDENTIFIER(lastName) ~ lastAssignment ⇒
        val rows =
          inputs1.map(x ⇒ FFSimpleSchemaRow(x._1._1.str, dataType, x._1._2)) :+ FFSimpleSchemaRow(lastName,
                                                                                                  dataType,
                                                                                                  lastAssignment
          )
        FFSchemaRows(rows)
    }

    val noAssignmentSimpleRow = theFormat ~ rep(identifier ~ COMMA()) ~ rep(identifier) ~ SEMICOLON() ^^ {
      case dataType ~ inputs1 ~ lastInput ~ _ ⇒
        FFSchemaRows(FFSimpleSchemaList((inputs1.map(_._1) ++ lastInput).map {
          case IDENTIFIER(name) ⇒ FFSimpleSchemaRow(name, dataType, FFNoDefaultVal())
        }) :: Nil)
    }

    val structRecord = recordType ~ rep(record) ~ END() ~ identifier ~ SEMICOLON() ^^ {
      case _ ~ (records: Seq[FFSchemaRows]) ~ _ ~ IDENTIFIER(name) ~ _ ⇒
        FFSchemaRows(FFCompoundSchemaRow(FFStructType(name), records.flatMap(_.rows)) :: Nil)
    }

    val structRecordWithoutEnd = recordType ~ rep(record) ~ SEMICOLON() ^^ {
      case _ ~ (records: Seq[FFSchemaRows]) ~ _ ⇒
        FFSchemaRows(FFCompoundSchemaRow(FFStructType(""), records.flatMap(_.rows)) :: Nil)
    }

    val structArrayRecord =
      recordType ~ rep(record) ~ END() ~ OBRACK() ~ opt(literal) ~ CBRACK() ~ identifier ~ SEMICOLON() ^^ {
        case _ ~ (records: Seq[FFSchemaRows]) ~ _ ~ _ ~ optLiteral ~ _ ~ IDENTIFIER(name) ~ _ ⇒
          FFSchemaRows(
            FFCompoundSchemaRow(FFStructArrayType(name, getArraySizeInfo(optLiteral)), records.flatMap(_.rows)) :: Nil
          )
      }

    val structArrayRecord2 =
      recordType ~ rep(record) ~ END() ~ identifier ~ OBRACK() ~ identifier ~ CBRACK() ~ SEMICOLON() ^^ {
        case _ ~ (records: Seq[FFSchemaRows]) ~ _ ~ IDENTIFIER(name) ~ _ ~ IDENTIFIER(arraySize) ~ _ ~ _ ⇒
          FFSchemaRows(
            FFCompoundSchemaRow(FFStructArrayType(name, Some(arraySize)), records.flatMap(_.rows)) :: Nil
          )
      }

    val structArrayUnBoundedRecord =
      recordType ~ rep(record) ~ END() ~ OBRACK() ~ theFormat ~ CBRACK() ~ identifier ~ assignment ^^ {
        case _ ~ (records: Seq[FFSchemaRows]) ~ _ ~ _ ~ literal1 ~ _ ~ IDENTIFIER(name) ~ _ ⇒
          FFSchemaRows(
            FFCompoundSchemaRow(FFStructArrayType(name, Some(literal1.toString)), records.flatMap(_.rows)) :: Nil
          )
      }

    val unionRecord1 = unionRecordType ~ rep(record) ~ END() ~ identifier ~ SEMICOLON() ^^ {
      case _ ~ (records: Seq[FFSchemaRows]) ~ _ ~ IDENTIFIER(name) ~ _ ⇒
        FFSchemaRows(FFCompoundSchemaRow(FFUnionType(Some(name)), records.flatMap(_.rows)) :: Nil)
    }

    val unionRecord2 = unionRecordType ~ rep(record) ~ END() ~ SEMICOLON() ^^ {
      case _ ~ (records: Seq[FFSchemaRows]) ~ _ ~ _ ⇒
        FFSchemaRows(FFCompoundSchemaRow(FFUnionType(None), records.flatMap(_.rows)) :: Nil)
    }

//    dbg(simpleRow)(name = "simpleRow") |
    dbg(functionCallRow)(name = "functionCallRow") |
      dbg(discardedFunctionCallRow)(name = "discardedFunctionCallRow") |
      dbg(multiRow)(name = "multiRow") |
      dbg(noAssignmentSimpleRow)(name = "noAssignmentSimpleRow") |
      dbg(structRecord)(name = "structRecord") |
      dbg(structRecordWithoutEnd)(name = "structRecordWithoutEnd") |
      dbg(unionRecord1)(name = "unionRecord1") |
      dbg(unionRecord2)(name = "unionRecord2") |
      dbg(structArrayRecord2)(name = "structArrayRecord2") |
      dbg(structArrayRecord)(name = "structArrayRecord") |
      dbg(structArrayUnBoundedRecord)(name = "structArrayUnBoundedRecord") |
      dbg(ifExprRow2)(name = "ifExprRow2") |
      dbg(ifExprRow3)(name = "ifExprRow3") |
      dbg(ifElseExprRow2)(name = "ifElseExprRow2") |
      dbg(ifElseExprRow3)(name = "ifElseExprRow3") |
      dbg(ifElseExprRow4)(name = "ifElseExprRow4")
  }

  lazy val condition: Parser[FFRecordType] = positioned {
    identifier ~ rep(DOT() ~ identifier) ~ EQUALS() ~ EQUALS() ~ literal ^^ {
      case IDENTIFIER(var1) ~ restIdentifiers ~ _ ~ _ ~ STRING_LITERAL(str) ⇒
        FFRecordType(s"""$var1${if (restIdentifiers.nonEmpty) ("." + restIdentifiers.map(_._2.str).mkString("."))
        else ""} == "$str"""")
    }
  }

  lazy val ternaryCondition: Parser[FFRecordType] = positioned {
    OPAREN() ~ condition ~ CPAREN() ~ QUESTION_MARK() ~ literal ~ COLON() ~ OPAREN() ~ condition ~ CPAREN() ^^ {
      case _ ~ condition ~ _ ~ _ ~ STRING_LITERAL(str) ~ _ ~ _ ~ condition2 ~ _ ⇒
        FFRecordType(s"""(${condition.startType}) ? "$str" : (${condition2.startType})""")
      case _ ~ condition ~ _ ~ _ ~ INT_LITERAL(num) ~ _ ~ _ ~ condition2 ~ _ ⇒
        FFRecordType(s"""(${condition.startType}) ? $num : (${condition2.startType})""")
    }
  }

  lazy val nestedTernaryCondition: Parser[FFRecordType] = positioned {
    OPAREN() ~ condition ~ CPAREN() ~ QUESTION_MARK() ~ literal ~ COLON() ~ OPAREN() ~ ternaryCondition ~ CPAREN() ^^ {
      case _ ~ condition ~ _ ~ _ ~ STRING_LITERAL(str) ~ _ ~ _ ~ condition2 ~ _ ⇒
        FFRecordType(s"""(${condition.startType}) ? "$str" : (${condition2.startType})""")
      case _ ~ condition ~ _ ~ _ ~ INT_LITERAL(num) ~ _ ~ _ ~ condition2 ~ _ ⇒
        FFRecordType(s"""(${condition.startType}) ? "$num" : (${condition2.startType})""")
    }
  }

  lazy val recordType: Parser[FFRecordType] = positioned {
    val x1 = RECORD() ^^ { _ ⇒
      FFRecordType("record")
    }

    val ifExpr =
      IF() ~ OPAREN() ~ OPAREN() ~ identifier ~ EQUALS() ~ EQUALS() ~ literal ~ CPAREN() ~ CPAREN() ~ RECORD() ^^ {
        case _ ~ _ ~ _ ~ IDENTIFIER(name) ~ _ ~ _ ~ literal ~ _ ~ _ ~ _ ⇒ FFRecordType("record")
      }

    val ifWithSingleParentExpr =
      IF() ~ OPAREN() ~ identifier ~ EQUALS() ~ EQUALS() ~ literal ~ CPAREN() ~ RECORD() ^^ {
        case _ ~ _ ~ IDENTIFIER(name) ~ _ ~ _ ~ literal ~ _ ~ _ ⇒ FFRecordType("record")
      }

    val elseIfExpr =
      ELSE() ~ IF() ~ OPAREN() ~ identifier ~ EQUALS() ~ EQUALS() ~ literal ~ CPAREN() ~ RECORD() ^^ {
        case _ ~ _ ~ _ ~ IDENTIFIER(name) ~ _ ~ _ ~ literal ~ _ ~ _ ⇒ FFRecordType("record")
      }

    val x2 = ASCII() ~ RECORD() ^^ { case _ ~ _ ⇒ FFRecordType("ascii") }
    val x3 = UTF8() ~ RECORD() ^^ { case _ ~ _ ⇒ FFRecordType("utf8") }
    val x4 = EBCDIC() ~ RECORD() ^^ { case _ ~ _ ⇒ FFRecordType("ebcdic") }
    val x5 = ISO_LATIN() ~ RECORD() ^^ { case _ ~ _ ⇒ FFRecordType("iso_latin") }

    x1 | x2 | x3 | x4 | x5 | ifWithSingleParentExpr | elseIfExpr | ifExpr
  }

  lazy val unionRecordType: Parser[FFRecordType] = positioned {
    val x1 = UNION() ^^ { case _ ⇒ FFRecordType("union") }
    val x2 = ASCII() ~ UNION() ^^ { case _ ~ _ ⇒ FFRecordType("ascii") }
    val x3 = UTF8() ~ UNION() ^^ { case _ ~ _ ⇒ FFRecordType("utf8") }
    val x4 = EBCDIC() ~ UNION() ^^ { case _ ~ _ ⇒ FFRecordType("ebcdic") }
    val x5 = ISO_LATIN() ~ UNION() ^^ { case _ ~ _ ⇒ FFRecordType("iso_latin") }

    x1 | x2 | x3 | x4
  }

  /**
    * Changes the numerical type name to an integer if the scale is not present.
    *
    * @param inTyp number format type to check
    * @return The same number format, but with an integer type if the scale is not present, otherwise return unchanged
    */
  def zpd(inTyp: FFNumberFormat): FFNumberFormat = {
    inTyp match {
      //      case FFNumberFormat(FFTypeName(typeName, delimiter), precision, scale, miscProperties) ⇒
      //        if (typeName == "DecimalType" && scale.isDefined && scale.get == 0 && precision.isDefined && precision.get < 10)
      //          FFNumberFormat(FFTypeName("IntegerType", delimiter), precision, scale, miscProperties)
      //        else inTyp
      case _ ⇒ inTyp
    }
  }

  def processStringProperties(_props: Map[String, Any]): Option[Map[String, String]] = {
    val props = _props.filterNot(p ⇒ p._1 == "packed" || p._1 == "signReserved")
    if (props.isEmpty) None
    else {
      Some(props.map(entry ⇒ (entry._1, entry._2.toString)))
    }
  }

  def processDateProperties(props: Map[String, Any]): Map[String, Any] =
    props.filterNot(p ⇒ p._1 == "packed" || p._1 == "signReserved")

  private def getArraySizeInfo(literal1: Option[LITERAL]): Option[String] = {
    literal1 match {
      case Some(STRING_LITERAL(str)) ⇒ Some(str)
      case Some(INT_LITERAL(num))    ⇒ Some(num.toString)
      case Some(DOUBLE_LITERAL(dub)) ⇒ Some(dub.asInstanceOf[Int].toString)
      case _                         ⇒ None
    }
  }

  lazy val theFormat: Parser[FFDataFormat] = positioned {
    // string("|")
    // string(1)
    // decimal(",")
    // decimal("record (")
    // decimal(5)
    // date( "YYYYMMDD" )
    val oneLiteral =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ opt(COMMA() ~ CHARSET() ~ EQUALS() ~ literal) ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ optCharSet ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "StringType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFStringFormat(FFTypeName(typeName, delimiter = Some(value)),
                                 precision = None,
                                 props = processStringProperties(ptypeName.miscProperties)
                  )
                case INT_LITERAL(value) ⇒
                  FFStringFormat(FFTypeName(typeName, delimiter = None),
                                 precision = Some(value),
                                 props = processStringProperties(ptypeName.miscProperties)
                  )
                case INT() ⇒
                  FFStringFormat(FFTypeName(typeName, delimiter = None),
                                 precision = None,
                                 props = processStringProperties(ptypeName.miscProperties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "DecimalType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  zpd(
                    FFNumberFormat(FFTypeName(typeName, delimiter = Some(value)),
                                   precision = None,
                                   scale = None,
                                   miscProperties = properties
                    )
                  )
                case INT_LITERAL(value) ⇒
                  zpd(
                    FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                   precision = Some(Math.min(DEFAULT_PREC, value)),
                                   scale = None,
                                   miscProperties = properties
                    )
                  )
                case DOUBLE_LITERAL(value) ⇒
                  val fractionString = String.valueOf(value)
                  val fraction       = fractionString.substring(fractionString.indexOf('.') + 1).toInt
                  val precision      = Some(Math.min(DEFAULT_PREC, Math.floor(value).toInt - 1))
                  val scale          = Some(fraction)
                  zpd(
                    FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                   precision = precision,
                                   scale = scale,
                                   miscProperties = properties ++ Map("decimal_point" → "Period")
                    )
                  )
                case x ⇒ FFUnknownFormat(FFTypeName(typeName, delimiter = None), None)
              }
            case "DateType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFDateFormat(
                    FFTypeName(typeName, delimiter = None),
                    format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value)),
                    processDateProperties(ptypeName.miscProperties)
                  )
                case INT_LITERAL(value) ⇒
                  FFDateFormat(FFTypeName(typeName, delimiter = None),
                               format = None,
                               processDateProperties(ptypeName.miscProperties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "DateTimeType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFDateTimeFormat(
                    FFTypeName(typeName, delimiter = None),
                    format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value)),
                    processDateProperties(ptypeName.miscProperties)
                  )
                case INT_LITERAL(value) ⇒
                  FFDateTimeFormat(FFTypeName(typeName, delimiter = None),
                                   format = None,
                                   processDateProperties(ptypeName.miscProperties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "TimestampType" ⇒
              // @TODO this requires adding the timestamp format later on
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFStringFormat(FFTypeName(typeName, delimiter = None),
                                 precision = None,
                                 props = processStringProperties(ptypeName.miscProperties)
                  )
                case INT_LITERAL(value) ⇒
                  FFStringFormat(FFTypeName(typeName, delimiter = None),
                                 precision = None,
                                 props = processStringProperties(ptypeName.miscProperties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "TimestampType" ⇒
              // @TODO this requires adding the timestamp format later on
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFStringFormat(FFTypeName(typeName, delimiter = None),
                                 precision = None,
                                 props = processStringProperties(ptypeName.miscProperties)
                  )
                case INT_LITERAL(value) ⇒
                  FFStringFormat(FFTypeName(typeName, delimiter = None),
                                 precision = None,
                                 props = processStringProperties(ptypeName.miscProperties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "BinaryType" ⇒
              literal1 match {
                case INT_LITERAL(value) ⇒ FFVoidFormat(FFTypeName("ByteType", delimiter = None), Some(value))
                case x                  ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "IntegerType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFNumberFormat(FFTypeName(typeName, delimiter = Some(value)),
                                 precision = None,
                                 scale = None,
                                 miscProperties = properties
                  )
                case INT_LITERAL(value) ⇒
                  if (value > 4)
                    FFNumberFormat(FFTypeName("LongType", delimiter = None),
                                   precision = Some(Math.min(value, DEFAULT_PREC)),
                                   scale = None,
                                   miscProperties = properties
                    )
                  else
                    FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                   precision = Some(Math.min(value, DEFAULT_PREC)),
                                   scale = None,
                                   miscProperties = properties
                    )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "DoubleType" ⇒
              literal1 match {
                case INT_LITERAL(value) ⇒
                  zpd(
                    FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                   precision = Some(Math.min(value, DEFAULT_PREC)),
                                   scale = None,
                                   miscProperties = properties
                    )
                  )
                case x @ _ ⇒ throw new Exception(s"$x not supported")
              }
            case x ⇒
              throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // integer(4)[integer(4)]
    val oneLiteralWithArrayType =
      datatypeWithTypeInfo ~ opt(
        OPAREN() ~ literal ~ CPAREN()
      ) ~ OBRACK() ~ datatypeWithTypeInfo ~ OPAREN() ~ literal ~ CPAREN() ~ CBRACK() ^^ {
        case ptypeName ~ literal1Opt ~ _ ~ FFTypeNameWithProperties(typeName2, delim2, _) ~ _ ~ literal2 ~ _ ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val literal1 = literal1Opt.map(_._1._2)
          val typeName = ptypeName.name
          (typeName2, literal2) match {
            case ("IntegerType", INT_LITERAL(value)) ⇒
              typeName match {
                case "StringType" ⇒
                  literal1 match {
                    case Some(STRING_LITERAL(value)) ⇒
                      FFStringArrayFormat(FFTypeName(typeName, delimiter = Some(value)), precision = None, Some(value))
                    case Some(INT_LITERAL(value)) ⇒
                      FFStringArrayFormat(FFTypeName(typeName, delimiter = None),
                                          precision = Some(value),
                                          Some(value.toString)
                      )
                    case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
                  }
                case "DecimalType" ⇒
                  val (format, arraySizeInfo) = literal1 match {
                    case Some(STRING_LITERAL(value)) ⇒
                      (zpd(
                         FFNumberFormat(FFTypeName(typeName, delimiter = Some(value)),
                                        precision = None,
                                        scale = None,
                                        miscProperties = properties
                         )
                       ),
                       Some(value)
                      )
                    case Some(INT_LITERAL(value)) ⇒
                      (zpd(
                         FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                        precision = Some(Math.min(value, DEFAULT_PREC)),
                                        scale = None,
                                        miscProperties = properties
                         )
                       ),
                       Some(value.toString)
                      )
                    case Some(DOUBLE_LITERAL(value)) ⇒
                      val fractionString = String.valueOf(value)
                      val fraction       = fractionString.substring(fractionString.indexOf('.') + 1).toInt
                      val precision      = Some(Math.min(DEFAULT_PREC, Math.floor(value).toInt - 1))
                      val scale          = Some(fraction)
                      (zpd(
                         FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                        precision = precision,
                                        scale = scale,
                                        miscProperties = properties ++ Map("decimal_point" → "Period")
                         )
                       ),
                       Some(precision.get.toString)
                      )
                    case x ⇒
                      (FFUnknownFormat(FFTypeName(typeName, delimiter = None), getArraySizeInfo(Some(literal2))),
                       getArraySizeInfo(Some(literal2))
                      )
                  }
                  if (format.isInstanceOf[FFNumberFormat]) {
                    val numberFormat = format.asInstanceOf[FFNumberFormat]
                    FFNumberArrayFormat(numberFormat.name,
                                        numberFormat.precision,
                                        numberFormat.scale,
                                        arraySizeInfo,
                                        numberFormat.miscProperties
                    )
                  } else {
                    format
                  }
                case "DateType" ⇒
                  literal1 match {
                    case Some(STRING_LITERAL(value)) ⇒
                      FFDateFormat(
                        FFTypeName(typeName, delimiter = None),
                        format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value)),
                        miscProperties = processDateProperties(ptypeName.miscProperties)
                      )
                    case Some(INT_LITERAL(value)) ⇒
                      FFDateFormat(FFTypeName(typeName, delimiter = None),
                                   format = None,
                                   miscProperties = processDateProperties(ptypeName.miscProperties)
                      )
                    case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
                  }
                case "DateTimeType" ⇒
                  literal1 match {
                    case Some(STRING_LITERAL(value)) ⇒
                      FFDateTimeFormat(
                        FFTypeName(typeName, delimiter = None),
                        format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value)),
                        processDateProperties(ptypeName.miscProperties)
                      )
                    case Some(INT_LITERAL(value)) ⇒
                      FFDateTimeFormat(FFTypeName(typeName, delimiter = None),
                                       format = None,
                                       processDateProperties(ptypeName.miscProperties)
                      )
                    case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
                  }
                case "IntegerType" ⇒
                  literal1 match {
                    case Some(STRING_LITERAL(value)) ⇒
                      FFNumberArrayFormat(FFTypeName(typeName, delimiter = Some(value)),
                                          precision = None,
                                          scale = None,
                                          arraySizeInfo = Some(value),
                                          properties
                      )
                    case Some(INT_LITERAL(value)) ⇒
                      FFNumberArrayFormat(FFTypeName(typeName, delimiter = None),
                                          precision = Some(value),
                                          scale = None,
                                          arraySizeInfo = Some(value.toString),
                                          miscProperties = properties
                      )
                    case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
                  }
                case x ⇒ FFUnknownFormat(FFTypeName(typeName, None), getArraySizeInfo(literal1))
//                  throw new Exception(s"Don't know how to parse type $typeName with literal $x")
              }
            case (_: String, _) ⇒
              FFUnknownFormat(FFTypeName(ptypeName.name, ptypeName.delimiter), getArraySizeInfo(literal1))
            case x ⇒ throw new Exception(s"Bad literal $x for $typeName2")
          }

      }

    // utf8 string(big endian integer(4)) value;
    val oneLiteralWithInnerArrayType =
      datatypeWithTypeInfo ~
        OPAREN() ~ datatypeWithTypeInfo ~ opt(OPAREN() ~ literal ~ CPAREN()) ~ opt(
          COMMA() ~ CHARSET() ~ EQUALS() ~ literal
        ) ~ CPAREN() ~ opt(
          OBRACK() ~ datatypeWithTypeInfo ~ opt(OPAREN() ~ literal ~ CPAREN()) ~ CBRACK()
        ) ^^ {
          case ptypeName ~ _ ~ ptype2 ~ _ ~ charSetOpt ~ _ ~ optArrayPart ⇒
            val optArrayLiteral = optArrayPart.flatMap(_._1._2)
            var arraySizeInfo = optArrayLiteral.map(entry ⇒
              entry._1._2 match {
                case STRING_LITERAL(str) ⇒ str
                case INT_LITERAL(num)    ⇒ num.toString
                case DOUBLE_LITERAL(dub) ⇒ dub.asInstanceOf[Int].toString
                case _                   ⇒ ""
              }
            )
            arraySizeInfo = if (arraySizeInfo.getOrElse("") != "") Some(arraySizeInfo.get) else None
            val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                            None,
                                            None
            ).miscProperties ++ ptypeName.miscProperties ++ (if (charSetOpt.isDefined)
                                                               Map(
                                                                 "charset" → charSetOpt.get._2
                                                                   .asInstanceOf[STRING_LITERAL]
                                                                   .str
                                                               )
                                                             else Map())
            val typeName = ptypeName.name
            typeName match {
              case "StringType" ⇒
                if (optArrayPart.isDefined)
                  FFStringArrayFormat(FFTypeName(typeName, delimiter = None),
                                      precision = None,
                                      arraySizeInfo = arraySizeInfo
                  )
                else
                  FFStringFormat(
                    FFTypeName(typeName, delimiter = None),
                    None,
                    props = processStringProperties(properties)
                  )
              case "BinaryType" ⇒
                FFVoidFormat(FFTypeName(typeName, None), None)
              case "DecimalType" ⇒
                val numberFormat = zpd(
                  FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                 precision = None,
                                 scale = None,
                                 miscProperties = properties
                  )
                )
                if (optArrayPart.isDefined)
                  FFNumberArrayFormat(numberFormat.name,
                                      numberFormat.precision,
                                      numberFormat.scale,
                                      arraySizeInfo,
                                      numberFormat.miscProperties
                  )
                else numberFormat
              case "DateType" ⇒
                FFDateFormat(FFTypeName(typeName, delimiter = None),
                             format = None,
                             miscProperties = processDateProperties(properties)
                )
              case "DateTimeType" ⇒
                FFDateTimeFormat(FFTypeName(typeName, delimiter = None),
                                 format = None,
                                 processDateProperties(properties)
                )
              case "IntegerType" | "LongType" ⇒
                if (optArrayPart.isDefined)
                  FFNumberArrayFormat(FFTypeName(typeName, delimiter = None),
                                      precision = None,
                                      scale = None,
                                      arraySizeInfo = arraySizeInfo
                  )
                else
                  FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                 precision = None,
                                 scale = None,
                                 miscProperties = properties
                  )
              case x: String if x.length > 0 ⇒
                FFUnknownFormat(FFTypeName(x, None), arraySizeInfo)
            }
        }

    // utf8 string(big endian integer(4)) value;
    val oneLiteralWithArrayType2 =
      datatypeWithTypeInfo ~ OBRACK() ~ datatypeWithTypeInfo ~ CBRACK() ^^ {
        case ptypeName ~ _ ~ arrayType ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName      = ptypeName.name
          val arrayTypeInfo = Some(arrayType.name)
          typeName match {
            case "StringType" ⇒
              FFStringArrayFormat(FFTypeName(typeName, delimiter = None),
                                  precision = None,
                                  arraySizeInfo = arrayTypeInfo
              )
            case "BinaryType" ⇒
              FFVoidFormat(FFTypeName(typeName, None), None)
            case "DecimalType" ⇒
              val numberFormat = zpd(
                FFNumberFormat(FFTypeName(typeName, delimiter = None),
                               precision = None,
                               scale = None,
                               miscProperties = properties
                )
              )
              FFNumberArrayFormat(numberFormat.name,
                                  numberFormat.precision,
                                  numberFormat.scale,
                                  arrayTypeInfo,
                                  numberFormat.miscProperties
              )
            case "DateType" ⇒
              FFDateFormat(FFTypeName(typeName, delimiter = None),
                           format = None,
                           miscProperties = processDateProperties(properties)
              )
            case "DateTimeType" ⇒
              FFDateTimeFormat(FFTypeName(typeName, delimiter = None), format = None, processDateProperties(properties))
            case "IntegerType" | "LongType" ⇒
              FFNumberArrayFormat(FFTypeName(typeName, delimiter = None),
                                  precision = None,
                                  scale = None,
                                  arrayTypeInfo,
                                  miscProperties = properties
              )
            case x: String if x.length > 0 ⇒
              FFUnknownFormat(FFTypeName(x, None), arrayTypeInfo)
          }
      }

    // string(2)[256] hex2
    val oneLiteralWithConstantArrayType =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ CPAREN() ~ OBRACK() ~ literal ~ CBRACK() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ _ ~ literal2 ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val arrayTypeInfo = getArraySizeInfo(Some(literal2))
          val typeName      = ptypeName.name
          typeName match {
            case "StringType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFStringArrayFormat(FFTypeName(typeName, delimiter = Some(value)), precision = None, arrayTypeInfo)
                case INT_LITERAL(value) ⇒
                  FFStringArrayFormat(FFTypeName(typeName, delimiter = None), precision = Some(value), arrayTypeInfo)
                case INT() ⇒
                  FFStringArrayFormat(FFTypeName(typeName, delimiter = None), precision = None, arrayTypeInfo)
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "DecimalType" ⇒
              val format = literal1 match {
                case STRING_LITERAL(value) ⇒
                  zpd(
                    FFNumberFormat(FFTypeName(typeName, delimiter = Some(value)),
                                   precision = None,
                                   scale = None,
                                   miscProperties = properties
                    )
                  )
                case INT_LITERAL(value) ⇒
                  zpd(
                    FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                   precision = Some(Math.min(value, DEFAULT_PREC)),
                                   scale = None,
                                   miscProperties = properties
                    )
                  )
                case DOUBLE_LITERAL(value) ⇒
                  val fractionString = String.valueOf(value)
                  val fraction       = fractionString.substring(fractionString.indexOf('.') + 1).toInt
                  val precision      = Some(Math.min(DEFAULT_PREC, Math.floor(value).toInt - 1))
                  val scale          = Some(fraction)
                  zpd(
                    FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                   precision = precision,
                                   scale = scale,
                                   miscProperties = properties ++ Map("decimal_point" → "Period")
                    )
                  )
                case x ⇒
                  FFUnknownFormat(FFTypeName(typeName, delimiter = None), arrayTypeInfo)
              }
              if (format.isInstanceOf[FFNumberFormat]) {
                val numberFormat = format.asInstanceOf[FFNumberFormat]
                FFNumberArrayFormat(numberFormat.name,
                                    numberFormat.precision,
                                    numberFormat.scale,
                                    arrayTypeInfo,
                                    numberFormat.miscProperties
                )
              } else {
                format
              }
            case "DateType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFDateFormat(
                    FFTypeName(typeName, delimiter = None),
                    format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value)),
                    miscProperties = processDateProperties(ptypeName.miscProperties)
                  )
                case INT_LITERAL(value) ⇒
                  FFDateFormat(FFTypeName(typeName, delimiter = None),
                               format = None,
                               miscProperties = processDateProperties(ptypeName.miscProperties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "DateTimeType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFDateTimeFormat(
                    FFTypeName(typeName, delimiter = None),
                    format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value)),
                    processDateProperties(ptypeName.miscProperties)
                  )
                case INT_LITERAL(value) ⇒
                  FFDateTimeFormat(FFTypeName(typeName, delimiter = None),
                                   format = None,
                                   processDateProperties(ptypeName.miscProperties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "IntegerType" ⇒
              literal1 match {
                case STRING_LITERAL(value) ⇒
                  FFNumberArrayFormat(FFTypeName(typeName, delimiter = Some(value)),
                                      precision = None,
                                      scale = None,
                                      arrayTypeInfo,
                                      miscProperties = properties
                  )
                case INT_LITERAL(value) ⇒
                  FFNumberArrayFormat(FFTypeName(typeName, delimiter = None),
                                      precision = Some(value),
                                      scale = None,
                                      arrayTypeInfo,
                                      miscProperties = properties
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }

      }

    val noLiteral = datatypeWithTypeInfo ^^ {
      case FFTypeNameWithProperties(typeName, delim, properties) ⇒
        typeName match {
          case "StringType" ⇒
            FFStringFormat(FFTypeName(typeName, delimiter = None),
                           precision = None,
                           props = processStringProperties(properties)
            )
          case "DoubleType"  ⇒ FFNumberFormat(FFTypeName(typeName, None), None,    None, properties)
          case "IntegerType" ⇒ FFNumberFormat(FFTypeName(typeName, None), Some(4), None, properties)
          case "LongType"    ⇒ FFNumberFormat(FFTypeName(typeName, None), Some(8), None, properties)
          case x ⇒
            FFUnknownFormat(FFTypeName(typeName, None), None)
        }
    }

    // date ( "YYYYMMDD" ) ('|')
    // datetime ( "yyyyMMDD" ) ('|')
    val oneLiteralWithDateTimeFormat =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ CPAREN() ~ OPAREN() ~ literal ~ CPAREN() ^^ {
        case FFTypeNameWithProperties(typeName, delim, properties) ~ _ ~ literal1 ~ _ ~ _ ~ literal2 ~ _ ⇒
          typeName match {
            case "StringType" | "DecimalType" ⇒
              throw new Exception(
                s"String or Decimal Type can't have two string literals $literal1 and $literal2 for $typeName"
              )
            case "DateType" ⇒
              (literal1, literal2) match {
                case (STRING_LITERAL(value1), STRING_LITERAL(value2)) ⇒
                  FFDateFormat(
                    FFTypeName(typeName, delimiter = Some(value2)),
                    format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value1)),
                    processDateProperties(properties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case "DateTimeType" ⇒
              (literal1, literal2) match {
                case (STRING_LITERAL(value1), STRING_LITERAL(value2)) ⇒
                  FFDateTimeFormat(
                    FFTypeName(typeName, delimiter = Some(value2)),
                    format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value1)),
                    processDateProperties(properties)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal("\x01", 0)
    // decimal(",", 0)
    // string(",", 5)
    val twoLiteral =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ literal ~ rep(
        COMMA() ~ identifier ~ opt(EQUALS() ~ literal)
      ) ~ CPAREN() ~ opt(
        OBRACK() ~ literal ~ CBRACK()
      ) ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literal2 ~ miscProperties ~ _ ~ optArraySize ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties ++ miscProperties.map(entry ⇒
            if (entry._2.isDefined && entry._2.get._2.isInstanceOf[STRING_LITERAL])
              (entry._1._2.str,    entry._2.get._2.asInstanceOf[STRING_LITERAL].str)
            else (entry._1._2.str, true)
          )
          val typeName      = ptypeName.name
          val arraySizeInfo = if (optArraySize.isDefined) getArraySizeInfo(Some(optArraySize.get._1._2)) else None
          typeName match {
            case "StringType" ⇒
              (literal1, literal2) match {
                case (STRING_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  if (arraySizeInfo.isEmpty)
                    FFStringFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(value2),
                      props = processStringProperties(properties)
                    )
                  else
                    FFStringArrayFormat(FFTypeName(typeName, delimiter = Some(value1)),
                                        precision = Some(value2),
                                        arraySizeInfo
                    )
                case (x, y) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y")
              }
            case "DecimalType" ⇒
              (literal1, literal2) match {
                case (STRING_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  if (arraySizeInfo.isEmpty)
                    zpd(
                      FFNumberFormat(FFTypeName(typeName, delimiter = Some(value1)),
                                     precision = Some(DEFAULT_PREC),
                                     scale = Some(value2),
                                     miscProperties = properties ++ Map("decimal_point" → "Comma")
                      )
                    )
                  else
                    FFNumberArrayFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(DEFAULT_PREC),
                      scale = Some(value2),
                      arraySizeInfo = arraySizeInfo,
                      miscProperties = properties ++ Map("decimal_point" → "Comma")
                    )
                case (INT_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  if (arraySizeInfo.isEmpty)
                    zpd(
                      FFNumberFormat(
                        FFTypeName(typeName, delimiter = None),
                        precision = Some(Math.min(value1, DEFAULT_PREC)),
                        scale = Some(value2),
                        miscProperties = properties ++ Map("decimal_point" → "Comma")
                      )
                    )
                  else
                    FFNumberArrayFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = Some(value1),
                      scale = Some(value2),
                      arraySizeInfo = arraySizeInfo,
                      miscProperties = properties ++ Map("decimal_point" → "Comma")
                    )
                case (x, y) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    val twoLiteralWithZeroFilling = datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ ZEROFILL() ~ CPAREN() ^^ {
      case ptypeName ~ _ ~ literal1 ~ _ ~ _ ~ _ ⇒
        val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                        None,
                                        None
        ).miscProperties ++ ptypeName.miscProperties ++ Map("zerofill" → true)
        val typeName = ptypeName.name
        typeName match {
          case "StringType" ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $literal1")
          case "DecimalType" ⇒
            literal1 match {
              case STRING_LITERAL(value1) ⇒
                zpd(
                  FFNumberFormat(FFTypeName(typeName, delimiter = Some(value1)),
                                 precision = Some(0),
                                 scale = None,
                                 miscProperties = properties
                  )
                )
              case INT_LITERAL(value1) ⇒
                zpd(
                  FFNumberFormat(FFTypeName(typeName, delimiter = None),
                                 precision = Some(Math.min(value1, DEFAULT_PREC)),
                                 scale = None,
                                 miscProperties = properties
                  )
                )
              case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x")
            }
          case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
        }
    }

    val twoLiteralWithZoned =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ opt(literal ~ COMMA()) ~ ZONED() ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literal2Opt ~ _ ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          val value2: Option[Int] =
            if (literal2Opt.isDefined && literal2Opt.get._1.isInstanceOf[INT_LITERAL])
              Some(literal2Opt.get._1.asInstanceOf[INT_LITERAL].num)
            else None
          typeName match {
            case "StringType" ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $literal1")
            case "DecimalType" ⇒
              literal1 match {
                case INT_LITERAL(value1) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = Some(Math.min(value1, DEFAULT_PREC)),
                      scale = value2,
                      miscProperties = properties ++ Map("decimal_point" → "Comma")
                    )
                  )
                case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // string("\x01", maximum_length=1)
    // string(",", maximum_length=1)
    val oneLiteralMaxLength =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ opt(
        CHARSET() ~ EQUALS() ~ literal ~ COMMA()
      ) ~ MAX_LEN() ~ EQUALS() ~ literal ~ CPAREN() ~ opt(OBRACK() ~ literal ~ CBRACK()) ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ charSetOpt ~ _ ~ _ ~ literal2 ~ _ ~ arrayTypeOpt ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName      = ptypeName.name
          val arraySizeInfo = if (arrayTypeOpt.isDefined) getArraySizeInfo(Some(arrayTypeOpt.get._1._2)) else None
          typeName match {
            case "StringType" ⇒
              (literal1, literal2) match {
                case (STRING_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  if (arraySizeInfo.isDefined)
                    FFStringArrayFormat(FFTypeName(typeName, delimiter = Some(value1)),
                                        precision = Some(value2),
                                        arraySizeInfo = arraySizeInfo
                    )
                  else
                    FFStringFormat(FFTypeName(typeName, delimiter = Some(value1)),
                                   precision = Some(value2),
                                   props = processStringProperties(ptypeName.miscProperties)
                    )
                case (x, y) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y")
              }
            case "DecimalType" ⇒
              (literal1, literal2) match {
                case (STRING_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  if (arrayTypeOpt.isDefined)
                    FFNumberArrayFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(Math.min(DEFAULT_PREC, value2)),
                      scale = None,
                      arraySizeInfo = arraySizeInfo,
                      miscProperties = properties
                    )
                  else
                    zpd(
                      FFNumberFormat(FFTypeName(typeName, delimiter = Some(value1)),
                                     precision = Some(Math.min(DEFAULT_PREC, value2)),
                                     scale = None,
                                     miscProperties = properties
                      )
                    )
                case (x, y) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }

      }

    // string("\x01", maximum_length=1)
    // string(",", maximum_length=1)
    // only accept if this is string type, as fixed width and maxlength
    // can only really occur together in string
    val oneLiteralPrecisionAndMaxLength =
      datatypeWithTypeInfo ~ OPAREN() ~ datatypeWithTypeInfo ~ opt(OPAREN() ~ literal ~ CPAREN()) ~ COMMA() ~ opt(
        CHARSET() ~ EQUALS() ~ literal <~ COMMA()
      ) ~ MAX_LEN() ~ EQUALS() ~ literal ~ CPAREN() ^^ {
        case colType ~ _ ~ precisionType ~ packType ~ _ ~ _ ~ _ ~ _ ~ literal2 ~ _
            if colType.name == "StringType" && precisionType.name == "IntegerType" ⇒
          // get the pack type data
          val packLen = packType match {
            case Some(value) ⇒ Some(value._1._2.asInstanceOf[INT_LITERAL].num)
            case _           ⇒ None
          }
          var properties: Map[String, Any] = colType.miscProperties ++ precisionType.miscProperties
          literal2 match {
            case INT_LITERAL(num) ⇒
              if (packLen.isDefined) {
                properties = properties ++ Map("pckLen" → packLen.get.toString)
                if (precisionType.miscProperties.contains("endian")) {
                  properties = properties ++ Map("endian" → precisionType.miscProperties("endian").toString)
                }
                FFStringFormat(
                  FFTypeName(colType.name, delimiter = None),
                  precision = Some(num),
                  props = processStringProperties(properties)
                )
              } else
                FFStringFormat(
                  FFTypeName(colType.name, delimiter = None),
                  precision = Some(num),
                  props = processStringProperties(properties)
                )
            case _ ⇒ throw new Exception(s"Max length should be integer")
          }
      }

    // date("YYMMDD", century="2000")
    val oneLiteralWithCentury =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ CENTURY() ~ EQUALS() ~ literal ~ CPAREN() ^^ {
        case FFTypeNameWithProperties(typeName, delim, properties) ~ _ ~ literal1 ~ _ ~ _ ~ _ ~ literal2 ~ _ ⇒
          typeName match {
            case "DateType" ⇒
              (literal1, literal2) match {
                case (STRING_LITERAL(value1), STRING_LITERAL(value2)) ⇒
                  FFDateFormat(FFTypeName(typeName, None),
                               format = Some(AbInitioToSparkFunctionMapping.getSparkDateTimeFormat(value1)),
                               properties ++ Map("century" → value2)
                  )
                case x ⇒ throw new Exception(s"Bad literal $x for $typeName")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal(",", 2, maximum_length=9 )
    val twoLiteralMaxLength1 =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ literal ~ COMMA() ~ MAX_LEN() ~ EQUALS() ~ literal ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literal2 ~ _ ~ _ ~ _ ~ literal3 ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal2, literal3) match {
                case (STRING_LITERAL(value1), INT_LITERAL(value2), INT_LITERAL(value3)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(Math.min(DEFAULT_PREC, value3)),
                      scale = Some(value2),
                      miscProperties = properties ++ Map("decimal_point" → "Comma")
                    )
                  )
                case (x, y, z) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y, $z")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal(",", 2, maximum_length=9 , sign_reserved)
    val twoLiteralMaxLengthWithSignReserved =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ literal ~ COMMA() ~ MAX_LEN() ~ EQUALS() ~ literal ~ COMMA() ~ SIGN_RESERVED() ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literal2 ~ _ ~ _ ~ _ ~ literal3 ~ _ ~ _ ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal2, literal3) match {
                case (STRING_LITERAL(value1), INT_LITERAL(value2), INT_LITERAL(value3)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(Math.min(DEFAULT_PREC, value3)),
                      scale = Some(value2),
                      properties ++ Map[String, Any]("signReserved" → true, "decimal_point" → "Comma")
                    )
                  )
                case (x, y, z) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y, $z")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    val singleLiteralMaxLengthWithSignReserved =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ MAX_LEN() ~ EQUALS() ~ literal ~ COMMA() ~ SIGN_RESERVED() ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ _ ~ _ ~ literal3 ~ _ ~ _ ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal3) match {
                case (STRING_LITERAL(value1), INT_LITERAL(value3)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(Math.min(DEFAULT_PREC, value3)),
                      scale = None,
                      properties ++ Map[String, Any]("signReserved" → true, "decimal_point" → "Comma")
                    )
                  )
                case (x, z) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $z")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal("\x01".2, maximum_length=9 )
    val twoLiteralMaxLength2 =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ literal ~ COMMA() ~ MAX_LEN() ~ EQUALS() ~ literal ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ literal2 ~ _ ~ _ ~ _ ~ literal3 ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal2, literal3) match {
                case (STRING_LITERAL(value1), DOUBLE_LITERAL(value2), INT_LITERAL(value3)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(Math.min(DEFAULT_PREC, value3 - 1)),
                      scale = Some((value2 * 10).toInt),
                      miscProperties = properties ++ Map("decimal_point" → "Period")
                    )
                  )
                case (x, y, z) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y, $z")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal("\x01", 4, maximum_length=7, signReserved)
    val twoLiteralMaxLengthAndSignReserved =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ literal ~ COMMA() ~ MAX_LEN() ~ EQUALS() ~ literal ~ COMMA() ~ SIGN_RESERVED() ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literal2 ~ _ ~ _ ~ _ ~ literal3 ~ _ ~ _ ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal2, literal3) match {
                case (STRING_LITERAL(value1), INT_LITERAL(value2), INT_LITERAL(value3)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(Math.min(DEFAULT_PREC, value3)),
                      scale = Some(value2),
                      miscProperties = properties ++ Map[String, Any]("signReserved" → true, "decimal_point" → "Comma")
                    )
                  )
                case (x, y, z) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y, $z")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal(",", 2, maximum_length=9 )
    val decimalWithNumbersAndSignReserved =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ literal ~ COMMA() ~ SIGN_RESERVED() ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literal2 ~ _ ~ _ ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal2) match {
                case (INT_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = Some(Math.min(value1, DEFAULT_PREC)),
                      scale = Some(value2),
                      properties ++ Map[String, Any]("signReserved" → true, "decimal_point" → "Comma")
                    )
                  )

                case (STRING_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(Math.min(value2, DEFAULT_PREC)),
                      scale = None,
                      miscProperties = properties ++ Map[String, Any]("signReserved" → true)
                    )
                  )
                case (x, y) ⇒
                  throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    val decimalWithNumbersAndZeroFill =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ literal ~ COMMA() ~ ZEROFILL() ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literal2 ~ _ ~ _ ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties ++ Map("zerofill" → true)
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal2) match {
                case (INT_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = Some(Math.min(value1, DEFAULT_PREC)),
                      scale = Some(value2),
                      properties ++ Map[String, Any]("zerofill" → true, "decimal_point" → "Comma")
                    )
                  )
                case (x, y) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    val decimalWithGenericProperty =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ opt(literal ~ COMMA()) ~ opt(
        ZEROFILL() ~ COMMA()
      ) ~ rep(identifier ~ COMMA()) ~ identifier ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literalOpt ~ zeroFillOpt ~ multiProperties ~ IDENTIFIER(propName) ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties ++ multiProperties.map(x ⇒ (x._1.str, true)).toMap
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              val value2: Option[Int] =
                if (literalOpt.isDefined && literalOpt.get._1.isInstanceOf[INT_LITERAL])
                  Some(literalOpt.get._1.asInstanceOf[INT_LITERAL].num)
                else None
              literal1 match {
                case INT_LITERAL(value1) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = Some(Math.min(value1, DEFAULT_PREC)),
                      scale = value2,
                      properties ++ Map[String, Any](propName → true, "decimal_point" → "Comma") ++ (if (
                                                                                                       zeroFillOpt.isDefined
                                                                                                     )
                                                                                                       Map(
                                                                                                         "zerofill" → true
                                                                                                       )
                                                                                                     else Map())
                    )
                  )
                case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal(10, 2, unsigned)
    val decimalWithDoubleLiteralAndSignReserved =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ literal ~ COMMA() ~ (SIGN_RESERVED() | UNSIGNED()) ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ literal2 ~ _ ~ property ~ _ ⇒
          val newProperty = if (property == SIGN_RESERVED()) "signReserved" else "unsigned"
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal2) match {

                case (STRING_LITERAL(value), INT_LITERAL(num)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = Some(value)),
                      precision = Some(DEFAULT_PREC),
                      scale = Some(num),
                      miscProperties = properties ++ Map[String, Any](newProperty → true, "decimal_point" → "Comma")
                    )
                  )
                case (INT_LITERAL(value1), INT_LITERAL(value2)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = Some(value1),
                      scale = Some(value2),
                      miscProperties = properties ++ Map[String, Any](newProperty → true, "decimal_point" → "Comma")
                    )
                  )
                case (DOUBLE_LITERAL(value), _) ⇒
                  val fractionString = String.valueOf(value)
                  val fraction       = fractionString.substring(fractionString.indexOf('.') + 1).toInt
                  val scale          = Some(fraction)
                  val precision      = Some(Math.min(Math.floor(value).toInt - 1, DEFAULT_PREC))
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = precision,
                      scale = scale,
                      miscProperties = properties ++ Map[String, Any](newProperty → true, "decimal_point" → "Period")
                    )
                  )
                case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal(",", 2, maximum_length=9 )
    val decimalWithSingleLiteralAndSignReserved =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ COMMA() ~ (SIGN_RESERVED() | UNSIGNED()) ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ _ ~ property ~ _ ⇒
          val newProperty = if (property == SIGN_RESERVED()) "signReserved" else "unsigned"
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              literal1 match {

                case STRING_LITERAL(value) ⇒
                  zpd(
                    FFNumberFormat(FFTypeName(typeName, delimiter = Some(value)),
                                   precision = None,
                                   scale = None,
                                   miscProperties = properties ++ Map[String, Any](newProperty → true)
                    )
                  )
                case INT_LITERAL(value) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = Some(Math.min(value, DEFAULT_PREC)),
                      scale = None,
                      miscProperties = properties ++ Map[String, Any](newProperty → true)
                    )
                  )
                case DOUBLE_LITERAL(value) ⇒
                  val fractionString = String.valueOf(value)
                  val fraction       = fractionString.substring(fractionString.indexOf('.') + 1).toInt
                  val scale          = Some(fraction)
                  val precision      = Some(Math.min(Math.floor(value).toInt - 1, DEFAULT_PREC))
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = None),
                      precision = precision,
                      scale = scale,
                      miscProperties = properties ++ Map[String, Any](newProperty → true, "decimal_point" → "Period")
                    )
                  )
                case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal("\x01".2, maximum_length=9, sign_reserved )
    val decimalWithDotWithSignReserved =
      datatypeWithTypeInfo ~ OPAREN() ~ literal ~ literal ~ COMMA() ~ MAX_LEN() ~ EQUALS() ~ literal ~ COMMA() ~ SIGN_RESERVED() ~ CPAREN() ^^ {
        case ptypeName ~ _ ~ literal1 ~ literal2 ~ _ ~ _ ~ _ ~ literal3 ~ _ ~ _ ~ _ ⇒
          val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                          None,
                                          None
          ).miscProperties ++ ptypeName.miscProperties
          val typeName = ptypeName.name
          typeName match {
            case "DecimalType" ⇒
              (literal1, literal2, literal3) match {
                case (STRING_LITERAL(value1), DOUBLE_LITERAL(value2), INT_LITERAL(value3)) ⇒
                  zpd(
                    FFNumberFormat(
                      FFTypeName(typeName, delimiter = Some(value1)),
                      precision = Some(Math.min(DEFAULT_PREC, value3 - 1)),
                      scale = Some((value2 * 10).toInt),
                      properties ++ Map[String, Any]("signReserved" → true, "decimal_point" → "Period")
                    )
                  )
                case (x, y, z) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y, $z")
              }
            case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
          }
      }

    // decimal("\x01".2)
    val decimalWithDot = datatypeWithTypeInfo ~ OPAREN() ~ literal ~ literal ~ CPAREN() ^^ {
      case ptypeName ~ _ ~ literal1 ~ literal2 ~ _ ⇒
        val properties = FFNumberFormat(FFTypeName(ptypeName.name, ptypeName.delimiter),
                                        None,
                                        None
        ).miscProperties ++ ptypeName.miscProperties
        val typeName = ptypeName.name
        typeName match {
          case "DecimalType" ⇒
            (literal1, literal2) match {
              case (STRING_LITERAL(value1), DOUBLE_LITERAL(value2)) ⇒
                zpd(
                  FFNumberFormat(
                    FFTypeName(typeName, delimiter = Some(value1)),
                    precision = Some(DEFAULT_PREC - 1),
                    scale = Some((value2 * 10).toInt),
                    miscProperties = properties ++ Map("decimal_point" → "Period")
                  )
                )
              case (x, y) ⇒ throw new Exception(s"Don't know how to parse type $typeName with literals $x, $y")
            }
          case x ⇒ throw new Exception(s"Don't know how to parse type $typeName with literal $x")
        }
    }

    dbg(oneLiteralWithDateTimeFormat)(name = "oneLiteralWithDateTimeFormat") |
      dbg(oneLiteralWithArrayType)(name = "oneLiteralWithArrayType") |
      dbg(oneLiteralWithConstantArrayType)(name = "oneLiteralWithConstantArrayType") |
      dbg(twoLiteralWithZeroFilling)(name = "twoLiteralWithZeroFilling") |
      dbg(twoLiteralWithZoned)(name = "twoLiteralWithZoned") |
      dbg(oneLiteral)(name = "oneLiteral") |
      dbg(twoLiteral)(name = "twoLiteral") |
      dbg(oneLiteralWithArrayType2)(name = "oneLiteralWithArrayType2") |
      dbg(oneLiteralWithInnerArrayType)(name = "oneLiteralWithInnerArrayType") |
      dbg(oneLiteralMaxLength)(name = "oneLiteralMaxLength") |
      dbg(oneLiteralPrecisionAndMaxLength)(name = "oneLiteralPrecisionAndMaxLength") |
      dbg(oneLiteralWithCentury)(name = "oneLiteralWithCentury") |
      dbg(decimalWithDoubleLiteralAndSignReserved)(name = "decimalWithDoubleLiteralAndSignReserved") |
      dbg(decimalWithSingleLiteralAndSignReserved)(name = "decimalWithSingleLiteralAndSignReserved") |
      dbg(decimalWithNumbersAndSignReserved)(name = "decimalWithNumbersAndSignReserved") |
      dbg(twoLiteralMaxLengthWithSignReserved)(name = "twoLiteralMaxLengthWithSignReserved") |
      dbg(singleLiteralMaxLengthWithSignReserved)(name = "singleLiteralMaxLengthWithSignReserved") |
      dbg(twoLiteralMaxLength1)(name = "twoLiteralMaxLength1") |
      dbg(decimalWithDotWithSignReserved)(name = "decimalWithDotWithSignReserved") |
      dbg(twoLiteralMaxLengthAndSignReserved)(name = "twoLiteralMaxLengthAndSignReserved") |
      dbg(twoLiteralMaxLength2)(name = "twoLiteralMaxLength2") |
      dbg(decimalWithDot)(name = "decimalWithDot") |
      dbg(decimalWithNumbersAndZeroFill)(name = "decimalWithNumbersAndZeroFill") |
      dbg(decimalWithGenericProperty)(name = "decimalWithGenericProperty") |
      dbg(noLiteral)(name = "noLiteral")
  }

  lazy val assignment: PackratParser[FFDefaultVal] = positioned {
    val x1 = SEMICOLON() ^^ { _ ⇒
      FFNoDefaultVal()
    }
    val x2 = EQUALS() ~ NULL() ~ SEMICOLON() ^^ { _ ⇒
      FFNullDefaultVal()
    }
    val x3 = EQUALS() ~ literal ~ opt(SEMICOLON()) ^^ {
      case _ ~ STRING_LITERAL(value) ~ _ ⇒ FFStringDefaultVal(value)
      case _ ~ INT_LITERAL(value) ~ _    ⇒ FFIntDefaultVal(value)
      case _ ~ DOUBLE_LITERAL(value) ~ _ ⇒ FFDoubleDefaultVal(value)
    }
    val x31 = EQUALS() ~ OPAREN() ~ literal ~ CPAREN() ~ opt(SEMICOLON()) ^^ {
      case _ ~ _ ~ STRING_LITERAL(value) ~ _ ~ _ ⇒ FFStringDefaultVal(value)
      case _ ~ _ ~ INT_LITERAL(value) ~ _ ~ _    ⇒ FFIntDefaultVal(value)
      case _ ~ _ ~ DOUBLE_LITERAL(value) ~ _ ~ _ ⇒ FFDoubleDefaultVal(value)
    }

    val x4 = EQUALS() ~ NULL() ~ OPAREN() ~ literal ~ CPAREN() ~ opt(SEMICOLON()) ^^ { _ ⇒
      FFNullDefaultVal()
    }
    val x41 = EQUALS() ~ (OBRACK() | OBRACE()) ~ identifier ~ (CBRACK() | CBRACE()) ~ SEMICOLON() ^^ { _ ⇒
      FFNoDefaultVal()
    }
    val x5 = EQUALS() ~ OPAREN() ~ literal ~ CPAREN() ~ opt(SEMICOLON()) ^^ {
      case _ ~ STRING_LITERAL(value) ~ _ ⇒ FFStringDefaultVal(value)
      case _ ~ INT_LITERAL(value) ~ _    ⇒ FFIntDefaultVal(value)
      case _ ~ DOUBLE_LITERAL(value) ~ _ ⇒ FFDoubleDefaultVal(value)
    }
    val x6 = EQUALS() ~ identifier ~ OPAREN() ~ opt(identifier ~ COMMA() ~ literal) ~ CPAREN() ~ SEMICOLON() ^^ { _ ⇒
      FFNullDefaultVal()
    }
    val x61 = EQUALS() ~ NOT_OP1() ~ identifier ~ OPAREN() ~ identifier ~ CPAREN() ~ SEMICOLON() ^^ { _ ⇒
      FFNullDefaultVal()
    }
    val x7 =
      EQUALS() ~ OPAREN() ~ identifier ~ OPAREN() ~ identifier ~ CPAREN() ~ GREATER_THAN() ~ literal ~ CPAREN() ~ SEMICOLON() ^^ {
        _ ⇒
          FFNullDefaultVal()
      }
    val x8 =
      EQUALS() ~ NULL() ~ OPAREN() ~ identifier ~ OPAREN() ~ theFormat ~ COMMA() ~ literal ~ CPAREN() ~ CPAREN() ~ SEMICOLON() ^^ {
        _ ⇒
          FFNullDefaultVal()
      }
    val x9 = EQUALS() ~ NULL() ~ OPAREN() ~ OBRACK() ~ VECTOR() ~ CBRACK() ~ CPAREN() ~ opt(SEMICOLON()) ^^ { _ ⇒
      FFNullDefaultVal()
    }
    x2 | x31 | x3 | x4 | x5 | x61 | x6 | x7 | x1 | x8 | x41 | x9
  }
//(reinterpret_as(packed decimal(8),"    "))
  lazy val datatypeWithTypeInfo: PackratParser[FFTypeNameWithProperties] = positioned {

    val x1 = datatype ^^ { case ptypeName ⇒ ptypeName }

    val x2 = ASCII() ~ datatype ^^ { case _ ~ ptypeName ⇒ ptypeName }

    val x3 = UTF8() ~ datatype ^^ { case _ ~ ptypeName ⇒ ptypeName }

    x1 | x2 | x3
  }

  lazy val datatype: PackratParser[FFTypeNameWithProperties] = positioned {
    val x111 = EBCDIC() ~ STRING() ^^ {
      case _ ~ _ ⇒ FFTypeNameWithProperties("StringType", None, Map("ebcdic" → true))
    }
    val x112 = UNICODE() ~ STRING() ^^ { case _ ~ _ ⇒ FFTypeNameWithProperties("StringType", None) }
    val x1 = STRING() ^^ { _ ⇒
      FFTypeNameWithProperties("StringType", None)
    }
    val x12 = VARCHAR() ^^ { _ ⇒
      FFTypeNameWithProperties("StringType", None)
    }

    val x21 = EBCDIC() ~ INTEGER() ^^ {
      case _ ~ _ ⇒
        FFTypeNameWithProperties("IntegerType", None, Map("ebcdic" → true))
    }
    val x22 = BIGENDIAN() ~ (INTEGER() | REAL() | DECIMAL()) ^^ {
      case _ ~ INTEGER() ⇒
        FFTypeNameWithProperties("IntegerType", None, miscProperties = Map("packed" → false, "endian" → "big"))
      case _ ~ REAL() ⇒
        FFTypeNameWithProperties("DoubleType", None, miscProperties = Map("packed" → false, "endian" → "big"))
      case _ ~ DECIMAL() ⇒
        FFTypeNameWithProperties("DecimalType", None, miscProperties = Map("packed" → false, "endian" → "big"))
    }
    val x23 = (UNSIGNED() | SIGNED()) ~ (LITTLE_ENDIAN() | BIGENDIAN()) ~ INTEGER() ^^ {
      case SIGNED() ~ LITTLE_ENDIAN() ~ _ ⇒
        FFTypeNameWithProperties("IntegerType", None, miscProperties = Map("packed" → false, "endian" → "little"))
      case UNSIGNED() ~ LITTLE_ENDIAN() ~ _ ⇒
        FFTypeNameWithProperties("IntegerType",
                                 None,
                                 miscProperties = Map("packed" → false, "endian" → "little", "unsigned" → true)
        )
      case SIGNED() ~ BIGENDIAN() ~ _ ⇒
        FFTypeNameWithProperties("IntegerType", None, miscProperties = Map("packed" → false, "endian" → "big"))
      case UNSIGNED() ~ BIGENDIAN() ~ _ ⇒
        FFTypeNameWithProperties("IntegerType",
                                 None,
                                 miscProperties = Map("packed" → false, "endian" → "big", "unsigned" → true)
        )
    }
    val x26 = LITTLE_ENDIAN() ~ INTEGER() ^^ {
      case _ ~ _ ⇒
        FFTypeNameWithProperties("IntegerType", None, miscProperties = Map("packed" → false, "endian" → "little"))
    }
    val x2 = (INTEGER() | INT() | LONG()) ^^ {
      case INTEGER() | INT() ⇒ FFTypeNameWithProperties("IntegerType", None)
      case LONG() ⇒
        FFTypeNameWithProperties("LongType", None, miscProperties = Map("packed" → false))
    }
    val x25 = UNSIGNED() ~ (INTEGER() | INT()) ^^ { _ ⇒
      FFTypeNameWithProperties(name = "IntegerType", None, miscProperties = Map("unsigned" → true))
    }
    val x24 = BIGINT() ^^ { _ ⇒
      FFTypeNameWithProperties("IntegerType", None)
    }

    val x31 = PACKED() ~ DECIMAL() ^^ {
      case _ ~ _ ⇒ FFTypeNameWithProperties("DecimalType", None, Map("packed" → true))
    }
    val x32 = EBCDIC() ~ DECIMAL() ^^ {
      case _ ~ _ ⇒ FFTypeNameWithProperties("DecimalType", None, Map("ebcdic" → true))
    }
    val x3 = DECIMAL() ^^ { _ ⇒
      FFTypeNameWithProperties("DecimalType", None)
    }
    val x33 = DOUBLE() ^^ { _ ⇒
      FFTypeNameWithProperties("DoubleType", None)
    }

    val x41 = EBCDIC() ~ DATE() ^^ { case _ ~ _ ⇒ FFTypeNameWithProperties("DateType", None) }
    val x4 = DATE() ^^ { _ ⇒
      FFTypeNameWithProperties("DateType", None)
    }

    val x51 = EBCDIC() ~ DATETIME() ^^ { case _ ~ _ ⇒ FFTypeNameWithProperties("DateTimeType", None) }
    val x5 = DATETIME() ^^ { _ ⇒
      FFTypeNameWithProperties("DateTimeType", None)
    }

    val x61 = EBCDIC() ~ TIMESTAMP() ^^ { case _ ~ _ ⇒ FFTypeNameWithProperties("TimestampType", None) }
    val x6 = TIMESTAMP() ^^ { _ ⇒
      FFTypeNameWithProperties("TimestampType", None)
    }

    val x7 = VOID() ^^ { _ ⇒
      FFTypeNameWithProperties("BinaryType", None)
    }
    val x10 = identifier ^^ {
      case IDENTIFIER(typeName) ⇒ FFTypeNameWithProperties(typeName, None)
    }

    x111 | x112 | x31 | x1 | x12 | x32 | x21 | x22 | x23 | x25 | x26 | x2 | x24 | x3 | x33 | x41 | x4 | x51 | x5 | x61 | x6 | x7 | x10
  }

  private def identifier: Parser[IDENTIFIER] = positioned {
    accept("identifier", { case id @ IDENTIFIER(name) ⇒ id })
  }

  private def literal: Parser[LITERAL] = positioned {
    accept("string literal",
           {
             case lit @ INT_LITERAL(name)    ⇒ lit
             case lit @ DOUBLE_LITERAL(name) ⇒ lit
             case lit @ STRING_LITERAL(name) ⇒ lit
             case lit @ INT()                ⇒ lit
           }
    )
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy