diff --git a/shared/src/main/scala/io/kaitai/struct/JuliaClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/JuliaClassCompiler.scala new file mode 100644 index 000000000..9345e72d6 --- /dev/null +++ b/shared/src/main/scala/io/kaitai/struct/JuliaClassCompiler.scala @@ -0,0 +1,110 @@ +package io.kaitai.struct + +import io.kaitai.struct.datatype.DataType.{CalcIntType, KaitaiStreamType, UserType, UserTypeInstream} +import io.kaitai.struct.datatype.{BigEndian, CalcEndian, Endianness, FixedEndian, LittleEndian} +import io.kaitai.struct.exprlang.Ast +import io.kaitai.struct.format._ +import io.kaitai.struct.languages.JuliaCompiler +import io.kaitai.struct.languages.components.ExtraAttrs + +class JuliaClassCompiler( + classSpecs: ClassSpecs, + override val topClass: ClassSpec, + config: RuntimeConfig +) extends ClassCompiler(classSpecs, topClass, config, JuliaCompiler) { + + private val julialang = lang.asInstanceOf[JuliaCompiler] + + override def compileClass(curClass: ClassSpec): Unit = { + provider.nowClass = curClass + + val extraAttrs = List( + AttrSpec(List(), IoIdentifier, KaitaiStreamType), + AttrSpec(List(), RootIdentifier, UserTypeInstream(topClassName, None)), + AttrSpec(List(), ParentIdentifier, curClass.parentType) + ) ++ ExtraAttrs.forClassSpec(curClass, lang) + + // curClass.types.foreach { case (typeName, _) => lang.classForwardDeclaration(curClass.name ++ List(typeName)) } + + if (!curClass.doc.isEmpty) + lang.classDoc(curClass.name, curClass.doc) + + // Enums declaration defines types, so they need to go first + compileEnums(curClass) + + if (lang.config.readStoresPos) + lang.debugClassSequence(curClass.seq) + + // Basic struct declaration + lang.classHeader(curClass.name) + compileAttrDeclarations(curClass.seq ++ curClass.params ++ extraAttrs) + curClass.instances.foreach { case (instName, instSpec) => + compileInstanceDeclaration(instName, instSpec) + } + compileConstructor(curClass) + lang.classFooter(curClass.name) + + // if (curClass.isTopLevel) + // julialang.fromFile(curClass.name) + + compileEagerRead(curClass.seq, curClass.meta.endian) + + julialang.overrideGetProperty(curClass.name, curClass.instances) + compileInstances(curClass) + + compileAttrReaders(curClass.seq ++ extraAttrs) + + curClass.toStringExpr.foreach(expr => lang.classToString(expr)) + // Recursive types + compileSubclasses(curClass) + } + + override def compileInstance(className: List[String], instName: InstanceIdentifier, instSpec: InstanceSpec, endian: Option[Endianness]): Unit = { + // Determine datatype + val dataType = instSpec.dataTypeComposite + + // compileInstanceDeclaration(instName, instSpec) + + if (!lang.innerDocstrings) + compileInstanceDoc(instName, instSpec) + lang.instanceHeader(className, instName, dataType, instSpec.isNullable) + if (lang.innerDocstrings) + compileInstanceDoc(instName, instSpec) + lang.instanceCheckCacheAndReturn(instName, dataType) + + instSpec match { + case vi: ValueInstanceSpec => + lang.attrParseIfHeader(instName, vi.ifExpr) + lang.instanceCalculate(instName, dataType, vi.value) + lang.attrParseIfFooter(vi.ifExpr) + lang.instanceSetCalculated(instName) + case pi: ParseInstanceSpec => + lang.attrParse(pi, instName, endian) + } + + lang.instanceReturn(instName, dataType) + lang.instanceFooter + } + + // override def compileCalcEndian(ce: CalcEndian): Unit = { + // def renderProc(result: FixedEndian): Unit = { + // val v = result match { + // case LittleEndian => Ast.expr.Bool(true) + // case BigEndian => Ast.expr.Bool(false) + // } + // lang.instanceCalculate(IS_LE_ID, CalcIntType, v) + // } + // lang.switchCases[FixedEndian](IS_LE_ID, ce.on, ce.cases, renderProc, renderProc) + // } + + override def compileAttrDeclarations(attrs: List[MemberSpec]): Unit = { + attrs.foreach { (attr) => + val isNullable = if (lang.switchBytesOnlyAsRaw) { + attr.isNullableSwitchRaw + } else { + attr.isNullable || attr.dataType.isInstanceOf[UserType] + } + lang.attributeDeclaration(attr.id, attr.dataTypeComposite, isNullable) + } + } +} diff --git a/shared/src/main/scala/io/kaitai/struct/Main.scala b/shared/src/main/scala/io/kaitai/struct/Main.scala index 7ac34ef6e..cae03f47a 100644 --- a/shared/src/main/scala/io/kaitai/struct/Main.scala +++ b/shared/src/main/scala/io/kaitai/struct/Main.scala @@ -1,7 +1,7 @@ package io.kaitai.struct import io.kaitai.struct.format.{ClassSpec, ClassSpecs, MetaSpec} -import io.kaitai.struct.languages.{GoCompiler, NimCompiler, RustCompiler} +import io.kaitai.struct.languages.{GoCompiler, NimCompiler, RustCompiler, JuliaCompiler} import io.kaitai.struct.languages.components.LanguageCompilerStatic import io.kaitai.struct.precompile._ import io.kaitai.struct.problems.CompilationProblem @@ -80,6 +80,8 @@ object Main { new GraphvizClassCompiler(specs, spec) case GoCompiler => new GoClassCompiler(specs, spec, config) + case JuliaCompiler => + new JuliaClassCompiler(specs, spec, config) case RustCompiler => new RustClassCompiler(specs, spec, config) case ConstructClassCompiler => diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JuliaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JuliaCompiler.scala new file mode 100644 index 000000000..598870e8b --- /dev/null +++ b/shared/src/main/scala/io/kaitai/struct/languages/JuliaCompiler.scala @@ -0,0 +1,644 @@ +package io.kaitai.struct.languages + +import io.kaitai.struct.datatype.DataType.{BooleanType, _} +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.JuliaTranslator +import io.kaitai.struct.{ClassTypeProvider, RuntimeConfig, StringLanguageOutputWriter, Utils, ExternalType} + +class JuliaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) + extends LanguageCompiler(typeProvider, config) + with ObjectOrientedLanguage + with UpperCamelCaseClasses + with SingleOutputFile + with UniversalFooter + with EveryReadIsExpression + with AllocateIOLocalVar + with FixedContentsUsingArrayByteLiteral + with UniversalDoc + with SwitchIfOps { + + import JuliaCompiler._ + + override val translator = new JuliaTranslator(typeProvider, importList) + private val enums = new StringLanguageOutputWriter(indent) + + override def innerDocstrings = true + + override def innerClasses: Boolean = false + + override def universalFooter: Unit = { + out.dec + out.puts("end") + out.puts + } + + override def results(topClass: ClassSpec): Map[String, String] = { + Map(outFileName(topClass.nameAsStr) -> + (outHeader.result + outImports(topClass) + enums.result + out.result) + ) + } + + override def indent: String = " " + override def outFileName(topClassName: String): String = s"${type2module(topClassName)}.jl" + + override def outImports(topClass: ClassSpec): String = + importList.toList.mkString("", "\n", "\n") + + override def fileHeader(topClassName: String): Unit = { + outHeader.puts(s"module ${type2module(topClassName)}") + outHeader.puts(s"# $headerComment") + outHeader.puts + importList.add("export from_file") + importList.add("using KaitaiStruct") + out.puts + } + + override def fileFooter(topClassName: String): Unit = { + fromFile(topClassName) + out.puts("end") + } + + override def externalTypeDeclaration(extType: ExternalType): Unit = { + importList.add(s"import ${type2module(extType.name.head)}: ${types2class(extType.name)}") + } + + override def classHeader(name: List[String]): Unit = { + out.puts(s"mutable struct ${types2class(name)} <: KaitaiStruct.UserType") + out.inc + typeProvider.nowClass.meta.endian match { + case Some(_: CalcEndian) | Some(InheritedEndian) => + out.puts(s"_is_le::Union{Bool,Nothing}") + case _ => + // no _is_le variable + } + if (config.readStoresPos) { + out.puts("_attrStart::Dict") + out.puts("_attrEnd::Dict") + out.puts("_arrStart::Dict") + out.puts("_arrEnd::Dict") + } + } + + override def classFooter(name: List[String]): Unit = { + universalFooter + } + + override def classConstructorHeader(name: List[String], parentType: DataType, rootClassName: List[String], isHybrid: Boolean, params: List[ParamDefSpec]): Unit = { + val endianAdd = if (isHybrid) ", _is_le=nothing" else "" + val paramsList = Utils.join(params.map(p => paramName(p.id)), "", ", ", ",") + + out.puts(s"function ${types2class(name)}(${paramsList}_io, _parent=nothing, _root=nothing$endianAdd)") + out.inc + out.puts("this = new()") + if (isHybrid) + out.puts("this._is_le = _is_le") + + // Store parameters passed to us + params.foreach(p => handleAssignmentSimple(p.id, paramName(p.id))) + + out.puts("this._io = _io") + out.puts("this._parent = _parent") + if (name == rootClassName) { + out.puts("this._root = _root === nothing ? this : _root") + } + else { + out.puts("this._root = _root") + } + if (config.readStoresPos) { + out.puts("this._attrStart = Dict()") + out.puts("this._attrEnd = Dict()") + out.puts("this._arrStart = Dict()") + out.puts("this._arrEnd = Dict()") + } + } + + override def attrInit(attr: AttrLikeSpec): Unit = { + if (attr.isNullable) + out.puts(s"this.${idToStr(attr.id)} = nothing") + } + + override def classConstructorFooter: Unit = { + out.puts("this") + universalFooter + } + + override def runRead(name: List[String]): Unit = { + typeProvider.nowClass.instances.keys.foreach(instanceIdentifier => out.puts(s"this.${idToStr(instanceIdentifier)} = nothing")) + out.puts("_read(this)") + } + + override def runReadCalc(): Unit = { + out.puts(s"if this._is_le == true") + out.inc + out.puts("_read_le(this)") + out.dec + out.puts("elseif this._is_le == false") + out.inc + out.puts("_read_be(this)") + out.dec + out.puts(s"else") + out.inc + out.puts(s"throw(${ksErrorName(UndecidedEndiannessError)}(" + "\"" + typeProvider.nowClass.path.mkString("/", "/", "") + "\"))") + universalFooter + } + + override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean): Unit = { + val suffix = endian match { + case Some(e) => s"_${e.toSuffix}" + case None => "" + } + out.puts(s"function _read$suffix(this::${types2class(typeProvider.nowClass.name)})") + out.inc + } + + override def readFooter(): Unit = { + out.puts("nothing") + universalFooter + } + + override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = { + if (isNullable) { + out.puts(s"${idToStr(attrName)}::Union{Nothing,${kaitaiType2NativeType(attrType)}}") + } else { + out.puts(s"${idToStr(attrName)}::${kaitaiType2NativeType(attrType)}") + } + } + + override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit = { + out.puts(s"${idToStr(attrName)}::Union{Nothing,${kaitaiType2NativeType(attrType)}}") + } + + + + def fromFile(name: String): Unit = { + out.puts(s"function from_file(filename::String)::${type2class(name)}") + out.inc + out.puts(s"${type2class(name)}($kstreamName(open(filename, ${'"'}r${'"'})))") + universalFooter + } + + override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = {} + + override def universalDoc(doc: DocSpec): Unit = { + val docStr = doc.summary match { + case Some(summary) => + val lastChar = summary.last + if (lastChar == '.' || lastChar == '\n') { + summary + } else { + summary + "." + } + case None => + "" + } + + val extraNewline = if (docStr.isEmpty || docStr.last == '\n') "" else "\n" + val refStr = doc.ref.map { + case TextRef(text) => + val seeAlso = new StringLanguageOutputWriter("") + seeAlso.putsLines(" ", text) + s"$extraNewline\nSee also ${seeAlso.result}" + case ref: UrlRef => + val seeAlso = new StringLanguageOutputWriter("") + seeAlso.putsLines(" ", s"${ref.text} - ${ref.url}") + s"$extraNewline\nSee also ${seeAlso.result}" + }.mkString("\n") + + out.putsLines("", "\"\"\"" + docStr + refStr + "\"\"\"") + } + + override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit = + out.puts(s"#ensure_fixed_content") + + override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = { + out.puts("if this._is_le") + out.inc + leProc() + out.dec + out.puts("else") + out.inc + beProc() + blockScopeFooter + } + + 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 xorValueStr = translator.detectType(xorValue) match { + case _: IntType => s"UInt8(${translator.doCast(xorValue, Int1Type(true))})" + case _ => expression(xorValue) + } + s"KaitaiStruct.process_xor($srcExpr, $xorValueStr)" + case ProcessZlib => + importList.add("using CodecZlib") + s"transcode(GzipDecompressor, $srcExpr)" + case ProcessRotate(isLeft, rotValue) => + val expr = if (isLeft) { + expression(rotValue) + } else { + s"8 - (${expression(rotValue)})" + } + s"KaitaiStruct.process_rotate_left($srcExpr, $expr, 1)" + case ProcessCustom(name, args) => + val procClass = type2class(name.last) + importList.add(s"using ${name.map(x => type2class(x)).mkString(".")}") + s"$procClass.decode(${(args.map(expression) :+ srcExpr).mkString(", ")})" + } + handleAssignment(varDest, expr, rep, isRaw = false) + } + + override def normalIO: String = "this._io" + + override def allocateIO(varName: Identifier, rep: RepeatSpec): String = { + val ioName = s"_io_${idToStr(varName)}" + + val args = getRawIdExpr(varName, rep) + + out.puts(s"$ioName = $kstreamName(IOBuffer($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[end]" + } + } + + override def useIO(ioEx: expr): String = { + out.puts(s"io = ${expression(ioEx)}") + "io" + } + + override def pushPos(io: String): Unit = + out.puts(s"_pos = KaitaiStruct.pos($io)") + + override def seek(io: String, pos: Ast.expr): Unit = + out.puts(s"KaitaiStruct.seek($io, ${expression(pos)})") + + override def popPos(io: String): Unit = + out.puts(s"KaitaiStruct.seek($io, _pos)") + + override def alignToByte(io: String): Unit = + out.puts(s"KaitaiStruct.align_to_byte($io)") + + 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("this._attrStart[\"" + name + "\"] = KaitaiStruct.pos(" + io + ")") + case _: RepeatExpr | RepeatEos | _: RepeatUntil => + getOrCreatePosList("_arrStart", name, io) + } + } + } + + 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("this._attrEnd[\"" + name + "\"] = KaitaiStruct.pos(" + io + ")") + case _: RepeatExpr | RepeatEos | _: RepeatUntil => + getOrCreatePosList("_arrEnd", name, io) + } + } + + override def blockScopeHeader: Unit = { + out.puts("begin") + out.inc + } + + override def blockScopeFooter: Unit = { + out.dec + out.puts("end") + } + + def getOrCreatePosList(listName: String, varName: String, io: String): Unit = { + blockScopeHeader + out.puts("_posList = get(" + listName + ", \"" + varName + "\", nothing)") + out.puts("if _posList === nothing ") + out.inc + out.puts("_posList = Vector{Integer}()") + out.puts(listName + "[\"" + varName + "\"] = _posList") + blockScopeFooter + out.puts(s"push!(_posList, KaitaiStruct.pos($io))") + blockScopeFooter + } + + override def condIfHeader(expr: Ast.expr): Unit = { + out.puts(s"if ${expression(expr)}") + out.inc + } + + override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = { + out.puts(s"${privateMemberName(id)} = Vector{${kaitaiType2NativeType(dataType)}}()") + } + + override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + out.puts("i = 0") + out.puts(s"while !KaitaiStruct.iseof($io)") + out.inc + } + override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit = + out.puts(s"push!(${privateMemberName(id)}, $expr)") + + override def condRepeatEosFooter: Unit = { + out.puts("i += 1") + blockScopeFooter + } + + override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: expr): Unit = { + out.puts(s"for i in 1:${expression(repeatExpr)}") + out.inc + } + override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit = + handleAssignmentRepeatEos(id, expr) + + override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { + out.puts("i = 0") + out.puts("while true") + 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"push!(${privateMemberName(id)}, $tmpName)") + } + + override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { + typeProvider._currentIteratorType = Some(dataType) + out.puts(s"if ${expression(untilExpr)}") + out.inc + out.puts("break") + blockScopeFooter + out.puts("i += 1") + blockScopeFooter + } + + 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"KaitaiStruct.read${Utils.capitalize(t.apiCall(defEndian))}($io)" + case blt: BytesLimitType => + s"KaitaiStruct.read_bytes($io, UInt(${expression(blt.size)}))" + case _: BytesEosType => + s"KaitaiStruct.read_bytes_full($io)" + case BytesTerminatedType(terminator, include, consume, eosError, _) => + s"KaitaiStruct.read_bytes_term($io, UInt8($terminator), $include, $consume, $eosError)" + case BitsType1(bitEndian) => + s"KaitaiStruct.read_bits_int_${bitEndian.toSuffix}($io, 1) != 0" + case BitsType(width: Int, bitEndian) => + s"KaitaiStruct.read_bits_int_${bitEndian.toSuffix}($io, $width)" + case t: UserType => + val addParams = Utils.join(t.args.map(a => translator.translate(a)), "", ", ", ", ") + val addArgs = if (t.isExternal(typeProvider.nowClass)) { + "" + } else { + val parent = t.forcedParent match { + case Some(USER_TYPE_NO_PARENT) => "nothing" + case Some(fp) => translator.translate(fp) + case None => "this" + } + val addEndian = t.classSpec.get.meta.endian match { + case Some(InheritedEndian) => ", this._is_le" + case _ => "" + } + s", $parent, this._root$addEndian" + } + s"${userType2class(t)}($addParams$io$addArgs)" + } + } + + override def bytesPadTermExpr(expr0: String, padRight: Option[Int], terminator: Option[Int], include: Boolean): String = { + val expr1 = padRight match { + case Some(padByte) => s"KaitaiStruct.bytes_strip_right($expr0, $padByte)" + case None => expr0 + } + val expr2 = terminator match { + case Some(term) => s"KaitaiStruct.bytes_terminate($expr1, $term, $include)" + case None => expr1 + } + expr2 + } + + override def userTypeDebugRead(id: String, dataType: DataType, assignType: DataType): Unit = + out.puts(s"_read($id)") + + override def switchStart(id: Identifier, on: Ast.expr): Unit = {} + override def switchCaseStart(condition: Ast.expr): Unit = {} + override def switchCaseEnd(): Unit = {} + override def switchElseStart(): Unit = {} + override def switchEnd(): Unit = {} + + override def switchRequiresIfs(onType: DataType): Boolean = true + override def switchIfStart(id: Identifier, on: Ast.expr, onType: DataType): Unit = { + out.puts(s"_on = ${expression(on)}") + } + + override def switchIfCaseFirstStart(condition: Ast.expr): Unit = { + out.puts(s"if _on == ${expression(condition)}") + out.inc + } + + override def switchIfCaseStart(condition: Ast.expr): Unit = { + out.puts(s"elseif _on == ${expression(condition)}") + out.inc + } + + override def switchIfCaseEnd(): Unit = { + out.dec + } + + override def switchIfElseStart(): Unit = { + out.puts(s"else") + out.inc + } + + override def switchIfEnd(): Unit = { + out.puts("end") + } + + override def instanceHeader(className: List[String], instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = { + out.puts(s"function _get_${publicMemberName(instName)}(this::${types2class(className)})") + out.inc + } + + def overrideGetProperty(className: List[String], instances: Map[InstanceIdentifier, InstanceSpec]): Unit = { + if (instances.isEmpty) + return + out.puts(s"function Base.getproperty(obj::${types2class(className)}, sym::Symbol)") + out.inc + var c = "if" + instances.keys.foreach(instName => { + out.puts(s"$c sym === :${publicMemberName(instName)}") + c = "elseif" + out.inc + out.puts(s"return _get_${publicMemberName(instName)}(obj)") + out.dec + }) + out.puts("else") + out.inc + out.puts("return getfield(obj, sym)") + universalFooter + universalFooter + } + + override def instanceCheckCacheAndReturn(instName: InstanceIdentifier, dataType: DataType): Unit = { + out.puts(s"if ${privateMemberName(instName)} !== nothing") + out.inc + out.puts(s"return ${privateMemberName(instName)}") + universalFooter + } + + override def instanceReturn(instName: InstanceIdentifier, attrType: DataType): Unit = { + out.puts(s"${privateMemberName(instName)}") + } + + override def enumDeclaration(curClass: List[String], enumName: String, enumColl: Seq[(Long, EnumValueSpec)]): Unit = { + val fullEnumName: List[String] = curClass :+ enumName + enums.puts(s"@enum ${types2class(fullEnumName)}::Int64 begin") + enums.inc + enumColl.foreach { case (id: Long, label: EnumValueSpec) => enums.puts(s"${enumToStr(fullEnumName, label.name)} = ${translator.doIntLiteral(id)}") } + enums.dec + enums.puts("end") + enums.puts + } + + override def debugClassSequence(seq: List[AttrSpec]): Unit = { + val seqStr = seq.map(attr => "\"" + idToStr(attr.id) + "\"").mkString(", ") + out.puts(s"SEQ_FIELDS = [$seqStr]") + } + + override def classToString(toStringExpr: Ast.expr): Unit = { + out.puts(s"Base.show(io::IO, this::${types2class(typeProvider.nowClass.name)}) = print(io, ${translator.translate(toStringExpr)})") + out.puts + } + + override def idToStr(id: Identifier): String = JuliaCompiler.idToStr(id) + + override def publicMemberName(id: Identifier): String = JuliaCompiler.publicMemberName(id) + + override def privateMemberName(id: Identifier): String = s"this.${idToStr(id)}" + + override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}" + + override def ksErrorName(err: KSError): String = JuliaCompiler.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"if !(${translator.translate(checkExpr)})") + out.inc + out.puts(s"throw(${ksErrorName(err)}($errArgsStr))") + universalFooter + } + + def userType2class(t: UserType): String = { + val name = t.classSpec.get.name + s"${types2class(name)}" + } +} + +object JuliaCompiler extends LanguageCompilerStatic + with UpperCamelCaseClasses + with StreamStructNames + with ExceptionNames { + override def getCompiler( + tp: ClassTypeProvider, + config: RuntimeConfig + ): LanguageCompiler = new JuliaCompiler(tp, config) + + def idToStr(id: Identifier): String = + id match { + case SpecialIdentifier(name) => name + case NamedIdentifier(name) => name + case NumberedIdentifier(idx) => s"_${NumberedIdentifier.TEMPLATE}$idx" + case InstanceIdentifier(name) => s"_m_$name" + case RawIdentifier(innerId) => s"_raw_${idToStr(innerId)}" + } + + def publicMemberName(id: Identifier): String = + id match { + case InstanceIdentifier(name) => name + case _ => idToStr(id) + } + + override def kstreamName: String = "KaitaiStruct.KaitaiStream" + override def kstructName: String = "Any" + override def ksErrorName(err: KSError): String = err match { + case EndOfStreamError => "ErrorException" + case ConversionError => "ArgumentError" + case _ => s"KaitaiStruct.${err.name}" + } + + def types2class(name: List[String]): String = name.map(x => type2class(x)).mkString("_") + def type2module(name: String): String = s"${type2class(name)}Module" + + def kaitaiType2NativeType(attrType: DataType): String = { + attrType match { + case Int1Type(false) => "UInt8" + case IntMultiType(false, Width2, _) => "UInt16" + case IntMultiType(false, Width4, _) => "UInt32" + case IntMultiType(false, Width8, _) => "UInt64" + + case Int1Type(true) => "Int8" + case IntMultiType(true, Width2, _) => "Int16" + case IntMultiType(true, Width4, _) => "Int32" + case IntMultiType(true, Width8, _) => "Int64" + + case FloatMultiType(Width4, _) => "Float32" + case FloatMultiType(Width8, _) => "Float64" + + case BitsType(_, _) => "UInt64" + + case _: BooleanType => "Bool" + case CalcIntType => "Integer" + case CalcFloatType => "Float64" + + case _: StrType => "String" + case _: BytesType => "Vector{UInt8}" + + case AnyType => "Any" + case KaitaiStreamType | OwnedKaitaiStreamType => kstreamName + case KaitaiStructType | CalcKaitaiStructType(_) => kstructName + case _: UserType => "KaitaiStruct.UserType" + + case t: EnumType => s"Union{Enum,Integer}" + + case at: ArrayType => s"Vector{${kaitaiType2NativeType(at.elType)}}" + + case st: SwitchType => kaitaiType2NativeType(st.combinedType) + } + } + + def enumToStr(typeName: List[String], enumName: String): String = { + typeName.mkString("_") + "__" + enumName + } +} diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompilerStatic.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompilerStatic.scala index 99f42503e..b026adf28 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompilerStatic.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompilerStatic.scala @@ -23,6 +23,7 @@ object LanguageCompilerStatic { "php" -> PHPCompiler, "python" -> PythonCompiler, "ruby" -> RubyCompiler, + "julia" -> JuliaCompiler, "rust" -> RustCompiler ) diff --git a/shared/src/main/scala/io/kaitai/struct/translators/JuliaTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/JuliaTranslator.scala new file mode 100644 index 000000000..a0c0456d2 --- /dev/null +++ b/shared/src/main/scala/io/kaitai/struct/translators/JuliaTranslator.scala @@ -0,0 +1,166 @@ +package io.kaitai.struct.translators + +import io.kaitai.struct.{ImportList} +import io.kaitai.struct.datatype.DataType._ +import io.kaitai.struct.exprlang.Ast +import io.kaitai.struct.format.{EnumSpec, Identifier} +import io.kaitai.struct.languages.{JuliaCompiler} +import io.kaitai.struct.exprlang.ConstEvaluator + +class JuliaTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) { + + override def genericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr, extPrec: Int): String = { + (detectType(left), detectType(right), op) match { + case (_: IntType, _: IntType, Ast.operator.Div) => + s"fld(${translate(left)}, ${translate(right)})" + case (_: IntType, _: IntType, Ast.operator.Mod) => + s"KaitaiStruct.mod(${translate(left)}, ${translate(right)})" + case _ => + super.genericBinOp(left, op, right, extPrec) + } + } + + override def binOp(op: Ast.operator): String = { + op match { + case Ast.operator.Add => "+" + case Ast.operator.Sub => "-" + case Ast.operator.Mult => "*" + case Ast.operator.Div => "/" + case Ast.operator.Mod => "%" + case Ast.operator.BitAnd => "&" + case Ast.operator.BitOr => "|" + case Ast.operator.BitXor => "⊻" + case Ast.operator.LShift => "<<" + case Ast.operator.RShift => ">>" + } + } + + override val asciiCharQuoteMap: Map[Char, String] = Map( + '\t' -> "\\t", + '\n' -> "\\n", + '\r' -> "\\r", + '"' -> "\\\"", + '\\' -> "\\\\", + '\u0007' -> "\\a", + '\f' -> "\\f", + '\u000b' -> "\\v", + '\b' -> "\\b", + '$' -> "\\$" + ) + + override def doByteArrayLiteral(arr: Seq[Byte]): String = + s"Vector{UInt8}(${super.doByteArrayLiteral(arr)})" +// "b\"" + Utils.hexEscapeByteArray(arr) + "\"" + override def doByteArrayNonLiteral(elts: Seq[Ast.expr]): String = { + s"Vector{UInt8}([${elts.map(translate).mkString(", ")}])" + } + + override def doLocalName(s: String): String = { + s match { + case Identifier.ITERATOR => "_it" + case Identifier.INDEX => "i" + case _ => s"this.${doName(s)}" + } + } + override def doName(s: String): String = { + s match { + case Identifier.ITERATOR => "_it" + case _ => s + } + } + + override def doInternalName(id: Identifier): String = + s"this.${JuliaCompiler.publicMemberName(id)}" + + override def doEnumByLabel(enumSpec: EnumSpec, label: String): String = { + val isExternal = enumSpec.isExternal(provider.nowClass) + if (isExternal) { + importList.add(s"import ${JuliaCompiler.type2module(enumSpec.name.head)}") + } + + s"${JuliaCompiler.type2module(enumSpec.name.head)}.${JuliaCompiler.enumToStr(enumSpec.name, label)}" + } + + override def doEnumById(enumSpec: EnumSpec, id: String): String = { + val isExternal = enumSpec.isExternal(provider.nowClass) + if (isExternal) { + importList.add(s"import ${JuliaCompiler.type2module(enumSpec.name.head)}") + } + s"KaitaiStruct.resolve_enum(${JuliaCompiler.type2module(enumSpec.name.head)}.${JuliaCompiler.types2class(enumSpec.name)}, $id)" + } + + override def arraySubscript(container: Ast.expr, idx: Ast.expr): String = + s"${translate(container)}[${translateIndex(idx)}]" + def translateIndex(idx: Ast.expr): String = + (ConstEvaluator.evaluateIntConst(idx).get + 1).toString + override def doIfExp(condition: Ast.expr, ifTrue: Ast.expr, ifFalse: Ast.expr): String = + s"(${translate(condition)} ? ${translate(ifTrue)} : ${translate(ifFalse)})" + + // Predefined methods of various types + override def strToInt(s: Ast.expr, base: Ast.expr): String = { + val baseStr = translate(base) + s"parse(Int64, ${translate(s)}, base=$baseStr)" + } + override def enumToInt(v: Ast.expr, et: EnumType): String = + s"Int(${translate(v)})" + override def boolToInt(v: Ast.expr): String = + s"Int(${translate(v)})" + override def floatToInt(v: Ast.expr): String = + s"trunc(${translate(v)})" + override def intToStr(i: Ast.expr): String = { + s"string(${translate(i)})" + } + override def bytesToStr(bytesExpr: String, encoding: String): String = { + importList.add("using StringEncodings") + s"""decode(($bytesExpr), "$encoding")""" + } + + override def strConcat(left: Ast.expr, right: Ast.expr, extPrec: Int): String = + s"${translate(left)} * ${translate(right)}" + + override def bytesLength(value: Ast.expr): String = + s"length(${translate(value)})" + override def bytesSubscript(container: Ast.expr, idx: Ast.expr): String = + s"${translate(container)}[${translateIndex(idx)}]" + override def bytesFirst(container: Ast.expr): String = + s"${translate(container)}[begin]" + override def bytesLast(container: Ast.expr): String = + s"${translate(container)}[end]" + override def bytesMin(b: Ast.expr): String = + s"minimum(${translate(b)})" + override def bytesMax(b: Ast.expr): String = + s"maximum(${translate(b)})" + + + override def strLength(value: Ast.expr): String = + s"length(${translate(value)})" + override def strReverse(value: Ast.expr): String = + s"reverse(${translate(value)})" + override def strSubstring(s: Ast.expr, from: Ast.expr, to: Ast.expr): String = + s"(${translate(s)})[${translateIndex(from)}:${translate(to)}]" + + override def arrayFirst(a: Ast.expr): String = + s"${translate(a)}[begin]" + override def arrayLast(a: Ast.expr): String = + s"${translate(a)}[end]" + override def arraySize(a: Ast.expr): String = + s"Base.size(${translate(a)}, 1)" + override def arrayMin(a: Ast.expr): String = + s"minimum(${translate(a)})" + override def arrayMax(a: Ast.expr): String = + s"maximum(${translate(a)})" + + override def kaitaiStreamSize(value: Ast.expr): String = + s"KaitaiStruct.size(${translate(value)})" + override def kaitaiStreamEof(value: Ast.expr): String = + s"KaitaiStruct.iseof(${translate(value)})" + override def kaitaiStreamPos(value: Ast.expr): String = + s"KaitaiStruct.pos(${translate(value)})" + + override def doInterpolatedStringLiteral(exprs: Seq[Ast.expr]): String = + if (exprs.isEmpty) { + doStringLiteral("") + } else { + exprs.map(anyToStr).mkString(" * ") + } +}