
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
class LuaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) {
override val asciiCharQuoteMap: Map[Char, String] = Map(
'\t' -> "\\t",
'\n' -> "\\n",
'\r' -> "\\r",
'"' -> "\\\"",
'\\' -> "\\\\",
'\7' -> "\\a",
'\b' -> "\\b",
'\13' -> "\\v",
'\f' -> "\\f",
'\33' -> "\\027"
)
override def strLiteralUnicode(code: Char): String =
"\\u{%04x}".format(code.toInt)
override def doSubscript(container: Ast.expr, idx: Ast.expr): String = {
// Lua indexes start at 1, so we need to offset them
val fixedIdx = idx match {
case Ast.expr.IntNum(n) => Ast.expr.IntNum(n + 1)
case _ => idx
}
s"${translate(container)}[${translate(fixedIdx)}]"
}
override def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String =
s"(((${translate(condition)}) and (${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 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)"
// This is very hacky because integers and booleans cannot be compared
override def doNumericCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String = {
val bool2Int = (n: Boolean) => { if (n) "1" else "0" }
(left, right) match {
case (Ast.expr.Bool(l), Ast.expr.Bool(r)) => s"${bool2Int(l)} ${cmpOp(op)} ${bool2Int(r)}"
case (Ast.expr.Bool(l), r) => s"${bool2Int(l)} ${cmpOp(op)} ${translate(r)}"
case (l, Ast.expr.Bool(r)) => s"${translate(l)} ${cmpOp(op)} ${bool2Int(r)}"
case _ => super.doNumericCompareOp(left, op, right)
}
}
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 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)}, ${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