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

io.kaitai.struct.translators.BaseTranslator.scala Maven / Gradle / Ivy

package io.kaitai.struct.translators

import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.format.{ClassSpec, Identifier}
import io.kaitai.struct.precompile.TypeMismatchError

/**
  * BaseTranslator is a common semi-abstract implementation of a translator
  * API (i.e. [[AbstractTranslator]]), which fits target languages that
  * follow "every KS expression is translatable into expression" paradigm.
  * Main [[AbstractTranslator.translate]] method is implemented as a huge
  * case matching, which usually just calls relevant abstract methods for
  * every particular piece of KS expression, i.e. literals, operations,
  * method calls, etc.
  *
  * Given that there are many of these abstract methods, to make it more
  * maintainable, they are grouped into several abstract traits:
  * [[CommonLiterals]], [[CommonOps]], [[CommonMethods]],
  * [[CommonArraysAndCast]].
  *
  * This translator implementation also handles user-defined types and
  * fields properly - it uses given [[TypeProvider]] to resolve these.
  *
  * @param provider TypeProvider that will answer queries on user types
  */
abstract class BaseTranslator(val provider: TypeProvider)
  extends TypeDetector(provider)
  with AbstractTranslator
  with CommonLiterals
  with CommonOps
  with CommonArraysAndCast[String]
  with CommonMethods[String]
  with ByteArraysAsTrueArrays[String] {

  /**
    * Translates KS expression into an expression in some target language.
    * Note that this implementation may throw errors subclassed off the
    * [[precompile.ExpressionError]] when encountering
    * some sort of logical error in expression (i.e. invalid usage of
    * operator, type mismatch, etc). Typically, one's supposed to catch
    * and rethrow it, wrapped in [[problems.ErrorInInput]]
    * to assist error reporting in KSC.
    *
    * @param v KS expression to translate
    * @return expression in target language as string
    */
  def translate(v: Ast.expr): String = {
    v match {
      case Ast.expr.IntNum(n) =>
        doIntLiteral(n)
      case Ast.expr.FloatNum(n) =>
        doFloatLiteral(n)
      case Ast.expr.Str(s) =>
        doStringLiteral(s)
      case Ast.expr.Bool(n) =>
        doBoolLiteral(n)
      case Ast.expr.EnumById(enumType, id, inType) =>
        val enumSpec = provider.resolveEnum(inType, enumType.name)
        doEnumById(enumSpec.name, translate(id))
      case Ast.expr.EnumByLabel(enumType, label, inType) =>
        val enumSpec = provider.resolveEnum(inType, enumType.name)
        doEnumByLabel(enumSpec.name, label.name)
      case Ast.expr.Name(name: Ast.identifier) =>
        if (name.name == Identifier.SIZEOF) {
          byteSizeOfClassSpec(provider.nowClass)
        } else {
          doLocalName(name.name)
        }
      case Ast.expr.InternalName(id: Identifier) =>
        doInternalName(id)
      case Ast.expr.UnaryOp(op: Ast.unaryop, inner: Ast.expr) =>
        val opStr = unaryOp(op)
        (op, inner) match {
          /** required by trait [[MinSignedIntegers]] - see also test cases in [[TranslatorSpec]] */
          case (Ast.unaryop.Minus, Ast.expr.IntNum(n)) => translate(Ast.expr.IntNum(-n))
          case (_, Ast.expr.IntNum(_) | Ast.expr.FloatNum(_)) =>
            s"$opStr${translate(inner)}"
          case _ =>
            s"$opStr(${translate(inner)})"
        }
      case Ast.expr.Compare(left: Ast.expr, op: Ast.cmpop, right: Ast.expr) =>
        (detectType(left), detectType(right)) match {
          case (_: NumericType, _: NumericType) =>
            doNumericCompareOp(left, op, right)
          case (_: BooleanType, _: BooleanType) =>
            op match {
              case Ast.cmpop.Eq | Ast.cmpop.NotEq =>
                // FIXME: probably for some languages we'll need non-numeric comparison
                doNumericCompareOp(left, op, right)
              case _ =>
                throw new TypeMismatchError(s"can't compare booleans using $op operator")
            }
          case (_: StrType, _: StrType) =>
            doStrCompareOp(left, op, right)
          case (_: BytesType, _: BytesType) =>
            doBytesCompareOp(left, op, right)
          case (et1: EnumType, et2: EnumType) =>
            val et1Spec = et1.enumSpec.get
            val et2Spec = et2.enumSpec.get
            if (et1Spec != et2Spec) {
              throw new TypeMismatchError(s"can't compare enums type ${et1Spec.nameAsStr} and ${et2Spec.nameAsStr}")
            } else {
              doEnumCompareOp(left, op, right)
            }
          case (ltype, rtype) =>
            throw new TypeMismatchError(s"can't compare $ltype and $rtype")
        }
      case Ast.expr.BinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) =>
        (detectType(left), detectType(right), op) match {
          case (_: NumericType, _: NumericType, _) =>
            numericBinOp(left, op, right)
          case (_: StrType, _: StrType, Ast.operator.Add) =>
            strConcat(left, right)
          case (ltype, rtype, _) =>
            throw new TypeMismatchError(s"can't do $ltype $op $rtype")
        }
      case Ast.expr.BoolOp(op: Ast.boolop, values: Seq[Ast.expr]) =>
        doBooleanOp(op, values)
      case Ast.expr.IfExp(condition, ifTrue, ifFalse) =>
        doIfExp(condition, ifTrue, ifFalse)
      case Ast.expr.Subscript(container: Ast.expr, idx: Ast.expr) =>
        detectType(idx) match {
          case _: IntType =>
            detectType(container) match {
              case _: ArrayType =>
                arraySubscript(container, idx)
              case _: BytesType =>
                bytesSubscript(container, idx)
              case containerType =>
                throw new TypeMismatchError(s"can't index $containerType as array")
            }
          case idxType =>
            throw new TypeMismatchError(s"can't use $idx as array index (need int, got $idxType)")
        }
      case call: Ast.expr.Attribute =>
        translateAttribute(call)
      case call: Ast.expr.Call =>
        translateCall(call)
      case Ast.expr.List(values: Seq[Ast.expr]) =>
        doGuessArrayLiteral(values)
      case ctt: Ast.expr.CastToType =>
        doCastOrArray(ctt)
      case Ast.expr.ByteSizeOfType(typeName) =>
        doByteSizeOfType(typeName)
      case Ast.expr.BitSizeOfType(typeName) =>
        doBitSizeOfType(typeName)
    }
  }

  def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String
  def doCast(value: Ast.expr, typeName: DataType): String = translate(value)
  def doByteSizeOfType(typeName: Ast.typeId): String = doIntLiteral(
    CommonSizeOf.bitToByteSize(
      CommonSizeOf.getBitsSizeOfType(
        typeName.nameAsStr, detectCastType(typeName)
      )
    )
  )
  def doBitSizeOfType(typeName: Ast.typeId): String = doIntLiteral(
    CommonSizeOf.getBitsSizeOfType(
      typeName.nameAsStr, detectCastType(typeName)
    )
  )
  def byteSizeOfValue(attrName: String, valType: DataType): String = doIntLiteral(
    CommonSizeOf.bitToByteSize(
      CommonSizeOf.getBitsSizeOfType(attrName, valType)
    )
  )
  def byteSizeOfClassSpec(cs: ClassSpec): String =
    doIntLiteral(CommonSizeOf.getByteSizeOfClassSpec(cs))

  def doArrayLiteral(t: DataType, value: Seq[Ast.expr]): String = "[" + value.map((v) => translate(v)).mkString(", ") + "]"
  def doByteArrayLiteral(arr: Seq[Byte]): String = "[" + arr.map(_ & 0xff).mkString(", ") + "]"
  def doByteArrayNonLiteral(elts: Seq[Ast.expr]): String = ???

  def doLocalName(s: String): String = doName(s)
  def doName(s: String): String
  def doInternalName(id: Identifier): String = ???
  def userTypeField(userType: UserType, value: Ast.expr, attrName: String): String =
    anyField(value, attrName)
  def kaitaiStructField(value: Ast.expr, name: String): String =
    anyField(value, name)

  def doEnumByLabel(enumTypeAbs: List[String], label: String): String
  def doEnumById(enumTypeAbs: List[String], id: String): String

  // Predefined methods of various types
  def strConcat(left: Ast.expr, right: Ast.expr): String = s"${translate(left)} + ${translate(right)}"
  def boolToInt(value: Ast.expr): String =
    doIfExp(value, Ast.expr.IntNum(1), Ast.expr.IntNum(0))

  def kaitaiStreamSize(value: Ast.expr): String = anyField(value, "size")
  def kaitaiStreamEof(value: Ast.expr): String = anyField(value, "is_eof")
  def kaitaiStreamPos(value: Ast.expr): String = anyField(value, "pos")

  // Special convenience definition method + helper
  override def bytesToStr(value: Ast.expr, expr: Ast.expr): String =
    bytesToStr(translate(value), expr)
  def bytesToStr(value: String, expr: Ast.expr): String

  // Helper that does simple "one size fits all" attribute calling, if it is useful
  // for the language
  def anyField(value: Ast.expr, attrName: String): String =
    s"${translate(value)}.${doName(attrName)}"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy