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

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

package io.kaitai.struct.translators

import io.kaitai.struct.ImportList
import io.kaitai.struct.datatype.DataType
import io.kaitai.struct.datatype.DataType._
import io.kaitai.struct.format.Identifier
import io.kaitai.struct.exprlang.Ast
import io.kaitai.struct.languages.LuaCompiler
import io.kaitai.struct.Utils

class LuaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider)
    with MinSignedIntegers {
  override def doIntLiteral(n: BigInt): String = {
    if (n > Long.MaxValue && n <= Utils.MAX_UINT64) {
      // See :
      //
      // - "A numeric constant (...), if its value fits in an integer or it is a hexadecimal
      //   constant, it denotes an integer; otherwise (that is, a decimal integer numeral that
      //   overflows), it denotes a float."
      // - "Hexadecimal numerals with neither a radix point nor an exponent always denote an
      //   integer value; if the value overflows, it wraps around to fit into a valid integer."
      //
      // This is written only in the Lua 5.4 manual, but applies to Lua 5.3 too (experimentally
      // verified).
      "0x" + n.toString(16)
    } else {
      super.doIntLiteral(n)
    }
  }

  override val asciiCharQuoteMap: Map[Char, String] = Map(
    '\t' -> "\\t",
    '\n' -> "\\n",
    '\r' -> "\\r",
    '"' -> "\\\"",
    '\\' -> "\\\\",

    '\u0007' -> "\\a",
    '\b' -> "\\b",
    '\u000b' -> "\\v",
    '\f' -> "\\f",
    '\u001b' -> "\\027"
  )

  override def strLiteralUnicode(code: Char): String =
    "\\u{%04x}".format(code.toInt)

  override def numericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) = {
    (detectType(left), detectType(right), op) match {
      case (_: IntType, _: IntType, Ast.operator.Div) =>
        s"math.floor(${translate(left)} / ${translate(right)})"
      case _ =>
        super.numericBinOp(left, op, right)
    }
  }

  override def arraySubscript(container: Ast.expr, idx: Ast.expr): String = {
    // Lua indexes start at 1, so we need to offset them
    s"${translate(container)}[${translate(idx)} + 1]"
  }
  override def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String = {
    importList.add("local utils = require(\"utils\")")

    // http://lua-users.org/wiki/TernaryOperator (section Boxing/unboxing, using functions)
    s"utils.box_unwrap((${translate(condition)}) and utils.box_wrap(${translate(ifTrue)}) or (${translate(ifFalse)}))"
  }

  override def doBoolLiteral(n: Boolean): String =
    if (n) "true" else "false"
  override def doArrayLiteral(t: DataType, value: Seq[Ast.expr]): String =
    "{" + value.map((v) => translate(v)).mkString(", ") + "}"
  override def doByteArrayLiteral(arr: Seq[Byte]): String =
    "\"" + decEscapeByteArray(arr) + "\""

  override def doLocalName(s: String) = s match {
    case Identifier.ITERATOR => "_"
    case Identifier.INDEX => "i"
    case _ => s"self.${doName(s)}"
  }
  override def doName(s: String): String =
    s
  override def doInternalName(id: Identifier): String =
    s"self.${LuaCompiler.publicMemberName(id)}"

  override def doEnumByLabel(enumTypeAbs: List[String], label: String): String =
    s"${LuaCompiler.types2class(enumTypeAbs)}.$label"
  override def doEnumById(enumTypeAbs: List[String], id: String): String =
    s"${LuaCompiler.types2class(enumTypeAbs)}($id)"

  override def strConcat(left: Ast.expr, right: Ast.expr): String =
    s"${translate(left)} .. ${translate(right)}"
  override def strToInt(s: Ast.expr, base: Ast.expr): String = {
    val baseStr = translate(base)
    val add = baseStr match {
      case "10" => ""
      case _ => s", $baseStr"
    }
    s"tonumber(${translate(s)}$add)"
  }
  override def enumToInt(v: Ast.expr, et: EnumType): String =
    s"${translate(v)}.value"
  override def boolToInt(v: Ast.expr): String =
    s"(${translate(v)} and 1 or 0)"
  override def floatToInt(v: Ast.expr): String =
    s"(${translate(v)} > 0) and math.floor(${translate(v)}) or math.ceil(${translate(v)})"
  override def intToStr(i: Ast.expr, base: Ast.expr): String = {
    val baseStr = translate(base)
    baseStr match {
      case "10" => s"tostring(${translate(i)})"
      case _ => throw new UnsupportedOperationException(baseStr)
    }
  }
  override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String = {
    importList.add("local str_decode = require(\"string_decode\")")

    s"str_decode.decode($bytesExpr, ${translate(encoding)})"
  }
  override def bytesSubscript(container: Ast.expr, idx: Ast.expr): String = {
    s"string.byte(${translate(container)}, ${translate(idx)} + 1)"
  }
  override def bytesFirst(a: Ast.expr): String =
    s"string.byte(${translate(a)}, 1)"
  override def bytesLast(a: Ast.expr): String = {
    val table = translate(a)
    s"string.byte(${table}, #${table})"
  }
  override def bytesMin(a: Ast.expr): String = {
    importList.add("local utils = require(\"utils\")")

    s"utils.byte_array_min(${translate(a)})"
  }
  override def bytesMax(a: Ast.expr): String = {
    importList.add("local utils = require(\"utils\")")

    s"utils.byte_array_max(${translate(a)})"
  }
  override def strLength(s: Ast.expr): String =
    s"string.len(${translate(s)})"
  override def strReverse(s: Ast.expr): String =
    s"string.reverse(${translate(s)})"
  override def strSubstring(s: Ast.expr, from: Ast.expr, to: Ast.expr): String =
    s"string.sub(${translate(s)}, ${translate(from)} + 1, ${translate(to)})"

  override def arrayFirst(a: Ast.expr): String =
    s"${translate(a)}[1]"
  override def arrayLast(a: Ast.expr): String = {
    val table = translate(a)
    s"${table}[#${table}]"
  }
  override def arraySize(a: Ast.expr): String =
    s"#${translate(a)}"
  override def arrayMin(a: Ast.expr): String = {
    importList.add("local utils = require(\"utils\")")

    s"utils.array_min(${translate(a)})"
  }
  override def arrayMax(a: Ast.expr): String = {
    importList.add("local utils = require(\"utils\")")

    s"utils.array_max(${translate(a)})"
  }

  override def kaitaiStreamSize(value: Ast.expr): String =
    s"${translate(value)}:size()"
  override def kaitaiStreamEof(value: Ast.expr): String =
    s"${translate(value)}:is_eof()"
  override def kaitaiStreamPos(value: Ast.expr): String =
    s"${translate(value)}:pos()"

  override def binOp(op: Ast.operator): String = op match {
    case Ast.operator.BitXor => "~"
    case _ => super.binOp(op)
  }
  override def cmpOp(op: Ast.cmpop): String = op match {
    case Ast.cmpop.NotEq => "~="
    case _ => super.cmpOp(op)
  }
  override def booleanOp(op: Ast.boolop): String = op match {
    case Ast.boolop.Or => "or"
    case Ast.boolop.And => "and"
  }
  override def unaryOp(op: Ast.unaryop): String = op match {
    case Ast.unaryop.Not => "not"
    case _ => super.unaryOp(op)
  }

  /**
   * Converts byte array (Seq[Byte]) into decimal-escaped Lua-style literal
   * characters (i.e. like \255).
   *
   * @param arr byte array to escape
   * @return array contents decimal-escaped as string
   */
  private def decEscapeByteArray(arr: Seq[Byte]): String =
    arr.map((x) => "\\%03d".format(x & 0xff)).mkString
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy