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

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

package io.kaitai.struct.languages

import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.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.RubyTranslator
import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, Utils}

class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig)
  extends LanguageCompiler(typeProvider, config)
    with ObjectOrientedLanguage
    with SingleOutputFile
    with UniversalFooter
    with UniversalDoc
    with UpperCamelCaseClasses
    with AllocateIOLocalVar
    with EveryReadIsExpression
    with FixedContentsUsingArrayByteLiteral
    with NoNeedForFullClassPath {

  import RubyCompiler._

  val translator = new RubyTranslator(typeProvider)

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

  override def outFileName(topClassName: String): String = s"$topClassName.rb"
  override def indent: String = "  "

  override def outImports(topClass: ClassSpec) =
    importList.toList.map((x) => s"require '$x'").mkString("\n") + "\n"

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

    importList.add("kaitai/struct/struct")

    out.puts

    // API compatibility check
    out.puts(
      "unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('" +
      KSVersion.minimalRuntime +
      "')"
    )
    out.inc
    out.puts(
      "raise \"Incompatible Kaitai Struct Ruby API: " +
      KSVersion.minimalRuntime +
      " or later is required, but you have #{Kaitai::Struct::VERSION}\""
    )
    out.dec
    out.puts("end")
    out.puts
  }

  override def classHeader(name: String): Unit = {
    out.puts(s"class ${type2class(name)} < $kstructName")
    out.inc
    if (config.readStoresPos)
      out.puts("attr_reader :_debug")
  }

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

    val paramsList = Utils.join(params.map((p) => paramName(p.id)), ", ", ", ", "")

    out.puts(s"def initialize(_io, _parent = nil, _root = self$endianSuffix$paramsList)")
    out.inc
    out.puts("super(_io, _parent, _root)")

    if (isHybrid) {
      out.puts("@_is_le = _is_le")
    }

    // Store parameters passed to us
    params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id)))

    if (config.readStoresPos) {
      out.puts("@_debug = {}")
    }
  }

  override def runRead(name: List[String]): Unit = {
    out.puts("_read")
  }

  override def runReadCalc(): Unit = {
    out.puts
    out.puts(s"if @_is_le == true")
    out.inc
    out.puts("_read_le")
    out.dec
    out.puts("elsif @_is_le == false")
    out.inc
    out.puts("_read_be")
    out.dec
    out.puts("else")
    out.inc
    out.puts(s"raise ${ksErrorName(UndecidedEndiannessError)}.new(" + "\"" + typeProvider.nowClass.path.mkString("/", "/", "") + "\")")
    out.dec
    out.puts("end")
  }

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

  override def readFooter() = {
    // This is required for debug mode to be able to do stuff like:
    //
    //     obj = Obj.new(...)._read
    //
    // i.e. drop-in replacement of non-debug mode invocation:
    //
    //     obj = Obj.new(...)
    out.puts("self")

    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 added in Kaitai::Struct::Struct
      case _ =>
        out.puts(s"attr_reader :${publicMemberName(attrName)}")
    }
  }

  override def universalDoc(doc: DocSpec): Unit = {
    out.puts
    out.puts("##")

    doc.summary.foreach(summary => out.putsLines("# ", summary))

    doc.ref.foreach {
      case TextRef(text) =>
        out.putsLines("# ", s"@see '' $text", "  ")
      case UrlRef(url, text) =>
        out.putsLines("# ", s"@see $url $text", "  ")
    }
  }

  override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = {
    out.puts("if @_is_le")
    out.inc
    leProc()
    out.dec
    out.puts("else")
    out.inc
    beProc()
    out.dec
    out.puts("end")
  }

  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, rep: RepeatSpec): Unit = {
    val srcExpr = getRawIdExpr(varSrc, rep)

    val expr = proc match {
      case ProcessXor(xorValue) =>
        val procName = translator.detectType(xorValue) match {
          case _: IntType => "process_xor_one"
          case _: BytesType => "process_xor_many"
        }
        s"$kstreamName::$procName($srcExpr, ${expression(xorValue)})"
      case ProcessZlib =>
        importList.add("zlib")
        s"Zlib::Inflate.inflate($srcExpr)"
      case ProcessRotate(isLeft, rotValue) =>
        val expr = if (isLeft) {
          expression(rotValue)
        } else {
          s"8 - (${expression(rotValue)})"
        }
        s"$kstreamName::process_rotate_left($srcExpr, $expr, 1)"
      case ProcessCustom(name, args) =>
        val procClass = name.map((x) => type2class(x)).mkString("::")
        out.puts(s"_process = $procClass.new(${args.map(expression).mkString(", ")})")
        s"_process.decode($srcExpr)"
    }
    handleAssignment(varDest, expr, rep, false)
  }

  override def allocateIO(id: Identifier, rep: RepeatSpec): String = {
    val memberName = privateMemberName(id)
    val ioName = s"_io_${idToStr(id)}"

    val args = getRawIdExpr(id, rep)

    out.puts(s"$ioName = $kstreamName.new($args)")
    ioName
  }

  def getRawIdExpr(varName: Identifier, rep: RepeatSpec): String = {
    val memberName = privateMemberName(varName)
    rep match {
      case NoRepeat => memberName
      case RepeatExpr(_) => s"$memberName[i]"
      case _ => s"$memberName.last"
    }
  }

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

  override def pushPos(io: String): Unit =
    out.puts(s"_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 attrDebugStart(attrId: Identifier, attrType: DataType, ios: Option[String], rep: RepeatSpec): Unit = {
    ios.foreach { (io) =>
      val name = attrId match {
        case _: RawIdentifier | _: SpecialIdentifier => return
        case _ => idToStr(attrId)
      }
      rep match {
        case NoRepeat =>
          out.puts(s"(@_debug['$name'] ||= {})[:start] = $io.pos")
        case _: RepeatExpr =>
          out.puts(s"(@_debug['$name'][:arr] ||= [])[i] = {:start => $io.pos}")
        case RepeatEos | _: RepeatUntil =>
          out.puts(s"(@_debug['$name'][:arr] ||= [])[${privateMemberName(attrId)}.size] = {:start => $io.pos}")
      }
    }
  }

  override def attrDebugEnd(attrId: Identifier, attrType: DataType, io: String, rep: RepeatSpec): Unit = {
    val name = attrId match {
      case _: RawIdentifier | _: SpecialIdentifier => return
      case _ => idToStr(attrId)
    }
    rep match {
      case NoRepeat =>
        out.puts(s"(@_debug['$name'] ||= {})[:end] = $io.pos")
      case _: RepeatExpr =>
        out.puts(s"@_debug['$name'][:arr][i][:end] = $io.pos")
      case RepeatEos | _: RepeatUntil =>
        out.puts(s"@_debug['$name'][:arr][${privateMemberName(attrId)}.size - 1][:end] = $io.pos")
    }
  }

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

  override def condRepeatCommonInit(id: Identifier, dataType: DataType, needRaw: NeedRaw): Unit = {
    if (needRaw.level >= 1)
      out.puts(s"${privateMemberName(RawIdentifier(id))} = []")
    if (needRaw.level >= 2)
      out.puts(s"${privateMemberName(RawIdentifier(RawIdentifier(id)))} = []")
    out.puts(s"${privateMemberName(id)} = []")
  }

  override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = {
    out.puts("i = 0")
    out.puts(s"while not $io.eof?")
    out.inc
  }
  override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit =
    out.puts(s"${privateMemberName(id)} << $expr")

  override def condRepeatEosFooter: Unit = {
    out.puts("i += 1")
    super.condRepeatEosFooter
  }

  override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: expr): Unit = {
    out.puts(s"(${expression(repeatExpr)}).times { |i|")
    out.inc
  }
  override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit =
    handleAssignmentRepeatEos(id, expr)

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

  override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = {
    out.puts("i = 0")
    out.puts("begin")
    out.inc
  }

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

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

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

  override def handleAssignmentTempVar(dataType: DataType, id: String, expr: String): Unit =
    out.puts(s"$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, $include, $consume, $eosError)"
      case BitsType1(bitEndian) =>
        s"$io.read_bits_int_${bitEndian.toSuffix}(1) != 0"
      case BitsType(width: Int, bitEndian) =>
        s"$io.read_bits_int_${bitEndian.toSuffix}($width)"
      case t: UserType =>
        val addParams = Utils.join(t.args.map((a) => translator.translate(a)), ", ", ", ", "")
        val addArgs = if (t.isOpaque) {
          ""
        } else {
          val parent = t.forcedParent match {
            case Some(USER_TYPE_NO_PARENT) => "nil"
            case Some(fp) => translator.translate(fp)
            case None => "self"
          }
          val addEndian = t.classSpec.get.meta.endian match {
            case Some(InheritedEndian) => ", @_is_le"
            case _ => ""
          }
          s", $parent, @_root$addEndian"
        }
        s"${types2class(t.name)}.new($io$addArgs$addParams)"
    }
  }

  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, $include)"
      case None => expr1
    }
    expr2
  }

  override def userTypeDebugRead(id: String, dataType: DataType, assignType: DataType): Unit =
    out.puts(s"$id._read")

  override def switchStart(id: Identifier, on: Ast.expr): Unit =
    out.puts(s"case ${expression(on)}")

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

  override def switchCaseEnd(): Unit =
    out.dec

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

  override def switchEnd(): Unit =
    out.puts("end")

  override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = {
    out.puts(s"def ${instName.name}")
    out.inc
  }

  override def instanceCheckCacheAndReturn(instName: InstanceIdentifier, dataType: DataType): Unit = {
    out.puts(s"return ${privateMemberName(instName)} unless ${privateMemberName(instName)}.nil?")
  }

  override def instanceReturn(instName: InstanceIdentifier, attrType: DataType): Unit = {
    out.puts(privateMemberName(instName))
  }

  override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit = {
    val enumConst = value2Const(enumName)

    out.puts
    out.puts(s"$enumConst = {")
    out.inc
    enumColl.foreach { case (id, label) =>
      out.puts(s"${translator.doIntLiteral(id)} => ${enumValue(enumName, label)},")
    }
    out.dec
    out.puts("}")

    // Generate inverse hash
    out.puts(s"${inverseEnumName(enumConst)} = $enumConst.invert")
  }

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

  def value2Const(s: String) = Utils.upperUnderscoreCase(s)

  override def debugClassSequence(seq: List[AttrSpec]) = {
    val seqStr = seq.map((attr) => "\"" + idToStr(attr.id) + "\"").mkString(", ")
    out.puts(s"SEQ_FIELDS = [$seqStr]")
  }

  override def classToString(toStringExpr: Ast.expr): Unit = {
    out.puts
    out.puts("def inspect")
    out.inc
    out.puts(translator.translate(toStringExpr))
    out.dec
    out.puts("end")
  }

  override def idToStr(id: Identifier): String = RubyCompiler.idToStr(id)

  override def publicMemberName(id: Identifier): String = RubyCompiler.publicMemberName(id)

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

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

  override def ksErrorName(err: KSError): String = RubyCompiler.ksErrorName(err)

  override def attrValidateExpr(
    attrId: Identifier,
    attrType: DataType,
    checkExpr: Ast.expr,
    err: KSError,
    errArgs: List[Ast.expr]
  ): Unit = {
    val errArgsStr = errArgs.map(translator.translate).mkString(", ")
    out.puts(s"raise ${ksErrorName(err)}.new($errArgsStr) if not ${translator.translate(checkExpr)}")
  }

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

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

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

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

  override def kstreamName: String = "Kaitai::Struct::Stream"
  override def kstructName: String = "Kaitai::Struct::Struct"
  override def ksErrorName(err: KSError): String = err match {
    case EndOfStreamError => "EOFError"
    case _ => s"Kaitai::Struct::${err.name}"
  }

  def inverseEnumName(enumName: String) = s"I__$enumName"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy