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

io.kaitai.struct.languages.PerlCompiler.scala Maven / Gradle / Ivy

package io.kaitai.struct.languages

import io.kaitai.struct.datatype.{DataType, FixedEndian, InheritedEndian}
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.exprlang.Ast.expr
import io.kaitai.struct.format._
import io.kaitai.struct.languages.components._
import io.kaitai.struct.translators.{PerlTranslator, TypeProvider}
import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig}

class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
  extends LanguageCompiler(typeProvider, config)
    with SingleOutputFile
    with UniversalFooter
    with UpperCamelCaseClasses
    with AllocateIOLocalVar
    with FixedContentsUsingArrayByteLiteral
    with EveryReadIsExpression {

  import PerlCompiler._

  override val translator = new PerlTranslator(typeProvider, importList)

  override def innerClasses = false

  override def universalFooter: Unit = {
    out.dec
    out.puts("}")
  }

  override def indent: String = "    "
  override def outFileName(topClassName: String): String = s"${type2class(topClassName)}.pm"

  override def outImports(topClass: ClassSpec) =
    importList.toList.map((x) => s"use $x;").mkString("", "\n", "\n")

  override def fileHeader(topClassName: String): Unit = {
    outHeader.puts(s"# $headerComment")
    outHeader.puts

    importList.add("strict")
    importList.add("warnings")
    importList.add(s"$packageName ${KSVersion.minimalRuntime.toPerlVersion}")
  }

  override def fileFooter(topClassName: String): Unit = {
    out.puts
    out.puts("1;")
  }

  override def opaqueClassDeclaration(classSpec: ClassSpec): Unit =
    out.puts(s"use ${type2class(classSpec.name.head)};")

  override def classHeader(name: List[String]): Unit = {
    out.puts
    out.puts("########################################################################")
    out.puts(s"package ${types2class(name)};")
    out.puts
    out.puts(s"our @ISA = '$kstructName';")
    out.puts
    out.puts("sub from_file {")
    out.inc
    out.puts("my ($class, $filename) = @_;")
    out.puts("my $fd;")
    out.puts
    out.puts("open($fd, '<', $filename) or return undef;")
    out.puts("binmode($fd);")
    out.puts(s"return new($$class, $kstreamName->new($$fd));")
    universalFooter
  }

  override def classFooter(name: List[String]): Unit = {}

  override def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit = {
    val endianSuffix = if (isHybrid) ", $_is_le" else ""

    out.puts
    out.puts("sub new {")
    out.inc
    out.puts("my ($class, $_io, $_parent, $_root" + endianSuffix + ") = @_;")
    out.puts(s"my $$self = $kstructName->new($$_io);")
    out.puts
    out.puts("bless $self, $class;")
    handleAssignmentSimple(ParentIdentifier, "$_parent")
    handleAssignmentSimple(RootIdentifier, "$_root || $self;")

    if (isHybrid)
      handleAssignmentSimple(EndianIdentifier, "$_is_le")

    out.puts
  }

  override def classConstructorFooter(): Unit = {
    out.puts
    out.puts("return $self;")
    universalFooter
  }

  override def runRead(): Unit =
    out.puts("$self->_read();")

  override def runReadCalc(): Unit = {
    val isLe = privateMemberName(EndianIdentifier)

    out.puts(s"if (!(defined $isLe)) {")
    out.inc
    out.puts("die \"Unable to decide on endianness\";")
    out.dec
    out.puts(s"} elsif ($isLe) {")
    out.inc
    out.puts("$self->_read_le();")
    out.dec
    out.puts("} else {")
    out.inc
    out.puts("$self->_read_be();")
    out.dec
    out.puts("}")
  }

  override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean): Unit = {
    val suffix = endian match {
      case Some(e) => s"_${e.toSuffix}"
      case None => ""
    }
    out.puts
    out.puts(s"sub _read$suffix {")
    out.inc
    out.puts("my ($self) = @_;")
    out.puts
  }

  override def readFooter(): Unit = universalFooter

  override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {}

  override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {
    attrName match {
      case RootIdentifier | ParentIdentifier =>
        // ignore, they are already defined in KaitaiStruct class
      case _ =>
        out.puts
        out.puts(s"sub ${publicMemberName(attrName)} {")
        out.inc

        out.puts("my ($self) = @_;")
        out.puts(s"return ${privateMemberName(attrName)};")

        universalFooter
    }
  }

  override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
    out.puts(s"if (${privateMemberName(EndianIdentifier)}) {")
    out.inc
    leProc()
    out.dec
    out.puts("} else {")
    out.inc
    beProc()
    out.dec
    out.puts("}")
  }

  override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit = {
    out.puts(s"${privateMemberName(attrName)} = $normalIO->ensure_fixed_contents($contents);")
  }

  override def attrProcess(proc: ProcessExpr, varSrc: Identifier, varDest: Identifier): Unit = {
    val srcName = privateMemberName(varSrc)
    val destName = privateMemberName(varDest)

    out.puts(proc match {
      case ProcessXor(xorValue) =>
        val procName = translator.detectType(xorValue) match {
          case _: IntType => "process_xor_one"
          case _: BytesType => "process_xor_many"
        }
        s"$destName = $kstreamName::$procName($srcName, ${expression(xorValue)});"
      case ProcessZlib =>
        importList.add("Compress::Zlib")
        s"$destName = Compress::Zlib::uncompress($srcName);"
      case ProcessRotate(isLeft, rotValue) =>
        val expr = if (isLeft) {
          expression(rotValue)
        } else {
          s"8 - (${expression(rotValue)})"
        }
        s"$destName = $kstreamName::process_rotate_left($srcName, $expr, 1);"
    })
  }

  override def allocateIO(id: Identifier, rep: RepeatSpec): String = {
    val memberName = privateMemberName(id)

    val args = rep match {
      case RepeatEos => s"$memberName[-1]"
      case RepeatExpr(_) => s"$memberName[$$i]"
      case RepeatUntil(_) => translator.doName(Identifier.ITERATOR2)
      case NoRepeat => s"$memberName"
    }

    val ioName = s"$$io_${idToStr(id)}"

    out.puts(s"my $ioName = $kstreamName->new($args);")
    ioName
  }

  override def useIO(ioEx: expr): String = {
    out.puts(s"my $$io = ${expression(ioEx)};")
    "$io"
  }

  override def pushPos(io: String): Unit =
    out.puts(s"my $$_pos = $io->pos();")

  override def seek(io: String, pos: Ast.expr): Unit =
    out.puts(s"$io->seek(${expression(pos)});")

  override def popPos(io: String): Unit =
    out.puts(s"$io->seek($$_pos);")

  override def alignToByte(io: String): Unit =
    out.puts(s"$io->align_to_byte();")

  override def condIfHeader(expr: Ast.expr): Unit = {
    out.puts(s"if (${expression(expr)}) {")
    out.inc
  }

  override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean): Unit = {
    if (needRaw)
      out.puts(s"${privateMemberName(RawIdentifier(id))} = ();")
    out.puts(s"${privateMemberName(id)} = ();")
    out.puts(s"while (!$io->is_eof()) {")
    out.inc
  }

  override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit =
    out.puts(s"push @{${privateMemberName(id)}}, $expr;")

  override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: expr): Unit = {
    if (needRaw)
      out.puts(s"${privateMemberName(RawIdentifier(id))} = ();")
    out.puts(s"${privateMemberName(id)} = ();")
    val nVar = s"$$n_${idToStr(id)}"
    out.puts(s"my $nVar = ${expression(repeatExpr)};")
    out.puts(s"for (my $$i = 0; $$i < $nVar; $$i++) {")
    out.inc
  }

  override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit =
    out.puts(s"${privateMemberName(id)}[$$i] = $expr;")

  override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
    if (needRaw)
      out.puts(s"${privateMemberName(RawIdentifier(id))} = ();")
    out.puts(s"${privateMemberName(id)} = ();")
    out.puts("do {")
    out.inc
  }

  override def handleAssignmentRepeatUntil(id: Identifier, expr: String, isRaw: Boolean): Unit = {
    val (decl, tmpName) = if (isRaw) {
      ("my ", translator.doName(Identifier.ITERATOR2))
    } else {
      ("", translator.doName(Identifier.ITERATOR))
    }
    out.puts(s"$decl$tmpName = $expr;")
    out.puts(s"push @{${privateMemberName(id)}}, $tmpName;")
  }

  override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = {
    typeProvider._currentIteratorType = Some(dataType)
    out.dec
    out.puts(s"} until (${expression(untilExpr)});")
  }

  override def handleAssignmentSimple(id: Identifier, expr: String): Unit =
    out.puts(s"${privateMemberName(id)} = $expr;")

  override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = {
    dataType match {
      case t: ReadableType =>
        s"$io->read_${t.apiCall(defEndian)}()"
      case blt: BytesLimitType =>
        s"$io->read_bytes(${expression(blt.size)})"
      case _: BytesEosType =>
        s"$io->read_bytes_full()"
      case BytesTerminatedType(terminator, include, consume, eosError, _) =>
        s"$io->read_bytes_term($terminator, ${boolLiteral(include)}, ${boolLiteral(consume)}, ${boolLiteral(eosError)})"
      case BitsType1 =>
        s"$io->read_bits_int(1)"
      case BitsType(width: Int) =>
        s"$io->read_bits_int($width)"
      case t: UserType =>
        val addArgs = if (t.isOpaque) {
          ""
        } else {
          val parent = t.forcedParent match {
            case Some(fp) => translator.translate(fp)
            case None => "$self"
          }
          val addEndian = t.classSpec.get.meta.endian match {
            case Some(InheritedEndian) => s", ${privateMemberName(EndianIdentifier)}"
            case _ => ""
          }
          s", $parent, ${privateMemberName(RootIdentifier)}$addEndian"
        }
        s"${types2class(t.classSpec.get.name)}->new($io$addArgs)"
    }
  }

  override def bytesPadTermExpr(expr0: String, padRight: Option[Int], terminator: Option[Int], include: Boolean) = {
    val expr1 = padRight match {
      case Some(padByte) => s"$kstreamName::bytes_strip_right($expr0, $padByte)"
      case None => expr0
    }
    val expr2 = terminator match {
      case Some(term) => s"$kstreamName::bytes_terminate($expr1, $term, ${boolLiteral(include)})"
      case None => expr1
    }
    expr2
  }

  override def switchStart(id: Identifier, on: Ast.expr): Unit = {
    typeProvider._currentSwitchType = Some(translator.detectType(on))
    out.puts(s"my $$_on = ${expression(on)};")
  }

  override def switchCaseFirstStart(condition: Ast.expr): Unit = {
    out.puts(s"if (${expression(onComparisonExpr(condition))}) {")
    out.inc
  }

  override def switchCaseStart(condition: Ast.expr): Unit = {
    out.puts(s"elsif (${expression(onComparisonExpr(condition))}) {")
    out.inc
  }

  override def switchCaseEnd(): Unit = universalFooter

  override def switchElseStart(): Unit = {
    out.puts(s"else {")
    out.inc
  }

  override def switchEnd(): Unit = {}

  /**
    * Generates comparison expression by a given condition expression, comparing
    * it to special local variable "_on".
    * @param condition condition to check for equality with "_on"
    * @return comparison expression of boolean type
    */
  def onComparisonExpr(condition: Ast.expr) =
    Ast.expr.Compare(Ast.expr.Name(Ast.identifier("_on")), Ast.cmpop.Eq, condition)

  override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
    out.puts
    out.puts(s"sub ${instName.name} {")
    out.inc
    out.puts("my ($self) = @_;")
  }

  override def instanceCheckCacheAndReturn(instName: InstanceIdentifier): Unit = {
    out.puts(s"return ${privateMemberName(instName)} if (${privateMemberName(instName)});")
  }

  override def instanceReturn(instName: InstanceIdentifier): Unit = {
    out.puts(s"return ${privateMemberName(instName)};")
  }

  override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = {
    out.puts

    enumColl.foreach { case (id, label) =>
      out.puts(s"our ${enumValue(enumName, label.name)} = $id;")
    }
  }

  def enumValue(enumName: String, enumLabel: String) = translator.doEnumByLabel(List(enumName), enumLabel)

  override def idToStr(id: Identifier): String = {
    id match {
      case NamedIdentifier(name) => name
      case NumberedIdentifier(idx) => s"_${NumberedIdentifier.TEMPLATE}$idx"
      case si: SpecialIdentifier => si.name
      case RawIdentifier(inner) => s"_raw_${idToStr(inner)}"
      case InstanceIdentifier(name) => name
    }
  }

  override def privateMemberName(id: Identifier): String = s"$$self->{${idToStr(id)}}"

  override def publicMemberName(id: Identifier): String = idToStr(id)

  override def localTemporaryName(id: Identifier): String = s"$$_t_${idToStr(id)}"

  def boolLiteral(b: Boolean): String = translator.doBoolLiteral(b)

  def types2class(t: List[String]) = t.map(type2class).mkString("::")
}

object PerlCompiler extends LanguageCompilerStatic
  with UpperCamelCaseClasses
  with StreamStructNames {
  override def getCompiler(
    tp: ClassTypeProvider,
    config: RuntimeConfig
  ): LanguageCompiler = new PerlCompiler(tp, config)

  def packageName: String = "IO::KaitaiStruct"
  override def kstreamName: String = s"$packageName::Stream"
  override def kstructName: String = s"$packageName::Struct"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy