diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CSharpAsyncCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CSharpAsyncCompiler.scala new file mode 100644 index 000000000..0fad125f5 --- /dev/null +++ b/shared/src/main/scala/io/kaitai/struct/languages/CSharpAsyncCompiler.scala @@ -0,0 +1,677 @@ +package io.kaitai.struct.languages + +import io.kaitai.struct._ +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.{CSharpAsyncTranslator, TypeDetector} + +class CSharpAsyncCompiler(val typeProvider: ClassTypeProvider, config: RuntimeConfig) + extends LanguageCompiler(typeProvider, config) + with UpperCamelCaseClasses + with ObjectOrientedLanguage + with SingleOutputFile + with AllocateIOLocalVar + with EveryReadIsExpression + with UniversalDoc + with FixedContentsUsingArrayByteLiteral + with SwitchIfOps + with NoNeedForFullClassPath { + import CSharpAsyncCompiler._ + + val translator = new CSharpAsyncTranslator(typeProvider, importList) + + override def indent: String = " " + override def outFileName(topClassName: String): String = s"${type2class(topClassName)}.cs" + + override def outImports(topClass: ClassSpec) = + importList.toList.map((x) => s"using $x;").mkString("", "\n", "\n") + + override def fileHeader(topClassName: String): Unit = { + outHeader.puts(s"// $headerComment") + outHeader.puts + + var ns = "Kaitai" + if (!config.dotNetNamespace.isEmpty) + ns = config.dotNetNamespace + + if (ns != "Kaitai") + importList.add("Kaitai") + + out.puts + out.puts(s"namespace $ns") + out.puts(s"{") + out.inc + } + + override def fileFooter(topClassName: String): Unit = { + out.dec + out.puts("}") + } + + override def classHeader(name: String): Unit = { + importList.add("System") + importList.add("Kaitai.Async") + importList.add("System.Threading.Tasks") + + out.puts(s"public partial class ${type2class(name)} : $kstructName") + out.puts(s"{") + out.inc + + // `FromFile` is generated only for parameterless types + if (typeProvider.nowClass.params.isEmpty) { + out.puts(s"public static ${type2class(name)} FromFile(string fileName)") + out.puts(s"{") + out.inc + out.puts(s"return new ${type2class(name)}(new $kstreamName(fileName));") + out.dec + out.puts("}") + out.puts + } + } + + override def classFooter(name: String): Unit = fileFooter(name) + + + private var _className: String = null + + override def classConstructorHeader(name: String, parentType: DataType, rootClassName: String, isHybrid: Boolean, params: List[ParamDefSpec]): Unit = { + _className = name + + typeProvider.nowClass.meta.endian match { + case Some(_: CalcEndian) | Some(InheritedEndian) => + out.puts(s"private bool? ${privateMemberName(EndianIdentifier)};") + case _ => + // no _is_le variable + } + + val addEndian = if (isHybrid) ", bool? isLe = null" else "" + + val pIo = paramName(IoIdentifier) + val pParent = paramName(ParentIdentifier) + val pRoot = paramName(RootIdentifier) + + val paramsArg = Utils.join(params.map((p) => + s"${kaitaiType2NativeType(p.dataType)} ${paramName(p.id)}" + ), "", ", ", ", ") + + out.puts( + s"public ${type2class(name)}($paramsArg" + + s"$kstreamName $pIo, " + + s"${kaitaiType2NativeType(parentType)} $pParent = null, " + + s"${type2class(rootClassName)} $pRoot = null$addEndian) : base($pIo)" + ) + out.puts(s"{") + out.inc + handleAssignmentSimple(ParentIdentifier, pParent) + + handleAssignmentSimple( + RootIdentifier, + if (name == rootClassName) s"$pRoot ?? this" else pRoot + ) + + if (isHybrid) + handleAssignmentSimple(EndianIdentifier, "isLe") + + // Store parameters passed to us + params.foreach((p) => handleAssignmentSimple(p.id, paramName(p.id))) + } + + override def classConstructorFooter: Unit = fileFooter(null) + + override def runRead(): Unit = null + + override def runReadCalc(): Unit = { + out.puts + out.puts(s"if (${privateMemberName(EndianIdentifier)} == null) {") + out.inc + out.puts("throw new Exception(\"Unable to decide on endianness\");") + importList.add("System") + out.dec + out.puts(s"} else if (${privateMemberName(EndianIdentifier)} == true) {") + out.inc + out.puts("await ReadLEAsync();") + out.dec + out.puts("} else {") + out.inc + out.puts("await ReadBEAsync();") + out.dec + out.puts("}") + } + + override def readHeader(endian: Option[FixedEndian], isEmpty: Boolean) = { + val suffix = endian match { + case Some(e) => s"${e.toSuffix.toUpperCase}" + case None => "" + } + out.puts(s"public async Task<${type2class(_className)}> Read${suffix}Async()") + out.puts("{") + out.inc + } + + override def readFooter(): Unit = { + out.puts("return this;") + out.dec + out.puts("}") + } + + override def attributeDeclaration(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = { + out.puts(s"private ${kaitaiType2NativeTypeNullable(attrType, isNullable)} ${privateMemberName(attrName)};") + } + + override def attributeReader(attrName: Identifier, attrType: DataType, isNullable: Boolean): Unit = { + out.puts(s"public ${kaitaiType2NativeTypeNullable(attrType, isNullable)} ${publicMemberName(attrName)} { get { return ${privateMemberName(attrName)}; } }") + } + + override def universalDoc(doc: DocSpec): Unit = { + out.puts + doc.summary.foreach { summary => + out.puts("/// ") + out.putsLines("/// ", XMLUtils.escape(summary)) + out.puts("/// ") + } + + doc.ref.foreach { docRef => + out.puts("/// ") + + val refStr = docRef match { + case TextRef(text) => XMLUtils.escape(text) + case ref: UrlRef => ref.toAhref + } + + out.putsLines("/// ", s"Reference: $refStr") + out.puts("/// ") + } + } + + override def attrParseHybrid(leProc: () => Unit, beProc: () => Unit): Unit = { + out.puts(s"if (${privateMemberName(EndianIdentifier)} == true) {") + out.inc + leProc() + out.dec + out.puts("} else {") + out.inc + beProc() + out.dec + out.puts("}") + } + + override def attrFixedContentsParse(attrName: Identifier, contents: String): Unit = + out.puts(s"${privateMemberName(attrName)} = $normalIO.EnsureFixedContents($contents);") + + override def attrProcess(proc: ProcessExpr, varSrc: Identifier, varDest: Identifier): Unit = { + val srcName = privateMemberName(varSrc) + val destName = privateMemberName(varDest) + + proc match { + case ProcessXor(xorValue) => + out.puts(s"$destName = $normalIO.ProcessXor($srcName, ${expression(xorValue)});") + case ProcessZlib => + out.puts(s"$destName = $normalIO.ProcessZlib($srcName);") + case ProcessRotate(isLeft, rotValue) => + val expr = if (isLeft) { + expression(rotValue) + } else { + s"8 - (${expression(rotValue)})" + } + out.puts(s"$destName = $normalIO.ProcessRotateLeft($srcName, $expr, 1);") + case ProcessCustom(name, args) => + val procClass = types2class(name) + val procName = s"_process_${idToStr(varSrc)}" + out.puts(s"$procClass $procName = new $procClass(${args.map(expression).mkString(", ")});") + out.puts(s"$destName = $procName.Decode($srcName);") + } + } + + override def allocateIO(varName: Identifier, rep: RepeatSpec): String = { + val privateVarName = privateMemberName(varName) + + val ioName = s"io_$privateVarName" + + val args = rep match { + case RepeatEos | RepeatExpr(_) => s"$privateVarName[$privateVarName.Count - 1]" + case RepeatUntil(_) => translator.doName(Identifier.ITERATOR2) + case NoRepeat => privateVarName + } + + out.puts(s"var $ioName = new $kstreamName($args);") + ioName + } + + override def useIO(ioEx: expr): String = { + out.puts(s"$kstreamName io = ${expression(ioEx)};") + "io" + } + + override def pushPos(io: String): Unit = + out.puts(s"long _pos = $io.Pos;") + + override def seek(io: String, pos: Ast.expr): Unit = + out.puts(s"await $io.SeekAsync(${expression(pos)});") + + override def popPos(io: String): Unit = + out.puts(s"await $io.SeekAsync(_pos);") + + override def alignToByte(io: String): Unit = + out.puts(s"$io.AlignToByte();") + + override def instanceClear(instName: InstanceIdentifier): Unit = { + out.puts(s"${flagForInstName(instName)} = false;") + } + + override def instanceSetCalculated(instName: InstanceIdentifier): Unit = { + out.puts(s"${flagForInstName(instName)} = true;") + } + + override def condIfHeader(expr: expr): Unit = { + out.puts(s"if (${expression(expr)}) {") + out.inc + } + + override def condIfFooter(expr: expr): Unit = fileFooter(null) + + override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean): Unit = { + importList.add("System.Collections.Generic") + + if (needRaw) + out.puts(s"${privateMemberName(RawIdentifier(id))} = new List();") + out.puts(s"${privateMemberName(id)} = new ${kaitaiType2NativeType(ArrayTypeInStream(dataType))}();") + out.puts("{") + out.inc + out.puts("var i = 0;") + out.puts(s"while (!$io.IsEof) {") + out.inc + } + + override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit = { + out.puts(s"${privateMemberName(id)}.Add($expr);") + } + + override def condRepeatEosFooter: Unit = { + out.puts("i++;") + out.dec + out.puts("}") + out.dec + out.puts("}") + } + + override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, repeatExpr: expr): Unit = { + importList.add("System.Collections.Generic") + + if (needRaw) + out.puts(s"${privateMemberName(RawIdentifier(id))} = new List((int) (${expression(repeatExpr)}));") + out.puts(s"${privateMemberName(id)} = new ${kaitaiType2NativeType(ArrayTypeInStream(dataType))}((int) (${expression(repeatExpr)}));") + out.puts(s"for (var i = 0; i < ${expression(repeatExpr)}; i++)") + out.puts("{") + out.inc + } + + override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit = { + out.puts(s"${privateMemberName(id)}.Add($expr);") + } + + override def condRepeatExprFooter: Unit = fileFooter(null) + + override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = { + importList.add("System.Collections.Generic") + + if (needRaw) + out.puts(s"${privateMemberName(RawIdentifier(id))} = new List();") + out.puts(s"${privateMemberName(id)} = new ${kaitaiType2NativeType(ArrayTypeInStream(dataType))}();") + out.puts("{") + out.inc + out.puts("var i = 0;") + out.puts(s"${kaitaiType2NativeType(dataType)} ${translator.doName("_")};") + out.puts("do {") + out.inc + } + + override def handleAssignmentRepeatUntil(id: Identifier, expr: String, isRaw: Boolean): Unit = { + val (typeDecl, tempVar) = if (isRaw) { + ("byte[] ", translator.doName(Identifier.ITERATOR2)) + } else { + ("", translator.doName(Identifier.ITERATOR)) + } + out.puts(s"$typeDecl$tempVar = $expr;") + out.puts(s"${privateMemberName(id)}.Add($tempVar);") + } + + override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, needRaw: Boolean, untilExpr: expr): Unit = { + typeProvider._currentIteratorType = Some(dataType) + out.puts("i++;") + out.dec + out.puts(s"} while (!(${expression(untilExpr)}));") + out.dec + out.puts("}") + } + + 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"${kaitaiType2NativeType(dataType)} $id = $expr;") + + override def parseExpr(dataType: DataType, assignType: DataType, io: String, defEndian: Option[FixedEndian]): String = { + dataType match { + case t: ReadableType => + s"await $io.Read${Utils.capitalize(t.apiCall(defEndian))}Async()" + case blt: BytesLimitType => + s"await $io.ReadBytesAsync(${expression(blt.size)})" + case _: BytesEosType => + s"await $io.ReadBytesFullAsync()" + case BytesTerminatedType(terminator, include, consume, eosError, _) => + s"await $io.ReadBytesTermAsync($terminator, $include, $consume, $eosError)" + case BitsType1 => + s"await $io.ReadBitsIntAsync(1) != 0" + case BitsType(width: Int) => + s"await $io.ReadBitsIntAsync($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) => "null" + case Some(fp) => translator.translate(fp) + case None => "this" + } + val addEndian = t.classSpec.get.meta.endian match { + case Some(InheritedEndian) => s", ${privateMemberName(EndianIdentifier)}" + case _ => "" + } + s", $parent, ${privateMemberName(RootIdentifier)}$addEndian" + } + s"await (new ${types2class(t.name)}($addParams$io$addArgs)).ReadAsync()" + } + } + + override def bytesPadTermExpr(expr0: String, padRight: Option[Int], terminator: Option[Int], include: Boolean) = { + val expr1 = padRight match { + case Some(padByte) => s"$kstreamName.BytesStripRight($expr0, $padByte)" + case None => expr0 + } + val expr2 = terminator match { + case Some(term) => s"$kstreamName.BytesTerminate($expr1, $term, $include)" + case None => expr1 + } + expr2 + } + + override def userTypeDebugRead(id: String): Unit = + out.puts(s"await $id.ReadAsync();") + + override def switchRequiresIfs(onType: DataType): Boolean = onType match { + case _: IntType | _: EnumType | _: StrType => false + case _ => true + } + + // + + val NAME_SWITCH_ON = Ast.expr.Name(Ast.identifier(Identifier.SWITCH_ON)) + + override def switchStart(id: Identifier, on: Ast.expr): Unit = + out.puts(s"switch (${expression(on)}) {") + + override def switchCaseFirstStart(condition: Ast.expr): Unit = switchCaseStart(condition) + + override def switchCaseStart(condition: Ast.expr): Unit = { + out.puts(s"case ${expression(condition)}: {") + out.inc + } + + override def switchCaseEnd(): Unit = { + out.puts("break;") + out.dec + out.puts("}") + } + + override def switchElseStart(): Unit = { + out.puts("default: {") + out.inc + } + + override def switchEnd(): Unit = + out.puts("}") + + // + + // + + override def switchIfStart(id: Identifier, on: Ast.expr, onType: DataType): Unit = { + out.puts("{") + out.inc + out.puts(s"${kaitaiType2NativeType(onType)} ${expression(NAME_SWITCH_ON)} = ${expression(on)};") + } + + def switchCmpExpr(condition: Ast.expr): String = + expression( + Ast.expr.Compare( + NAME_SWITCH_ON, + Ast.cmpop.Eq, + condition + ) + ) + + override def switchIfCaseFirstStart(condition: Ast.expr): Unit = { + out.puts(s"if (${switchCmpExpr(condition)})") + out.puts("{") + out.inc + } + + override def switchIfCaseStart(condition: Ast.expr): Unit = { + out.puts(s"else if (${switchCmpExpr(condition)})") + out.puts("{") + out.inc + } + + override def switchIfCaseEnd(): Unit = { + out.dec + out.puts("}") + } + + override def switchIfElseStart(): Unit = { + out.puts("else") + out.puts("{") + out.inc + } + + override def switchIfEnd(): Unit = { + out.dec + out.puts("}") + } + + // + + override def instanceDeclaration(attrName: InstanceIdentifier, attrType: DataType, isNullable: Boolean): Unit = { + out.puts(s"private bool ${flagForInstName(attrName)};") + out.puts(s"private ${kaitaiType2NativeTypeNullable(attrType, isNullable)} ${privateMemberName(attrName)};") + } + + override def instanceHeader(className: String, instName: InstanceIdentifier, dataType: DataType, isNullable: Boolean): Unit = { + out.puts(s"public ${kaitaiType2NativeTypeNullable(dataType, isNullable)} ${publicMemberName(instName)}") + out.puts("{") + out.inc + out.puts("get") + out.puts("{") + out.inc + instanceCheckCacheAndReturn(instName, dataType) + out.puts(s"return Get${publicMemberName(instName)}().GetAwaiter().GetResult();") + out.dec + out.puts("}") + out.dec + out.puts("}") + + out.puts(s"public async Task<${kaitaiType2NativeTypeNullable(dataType, isNullable)}> Get${publicMemberName(instName)}()") + out.puts("{") + out.inc + } + + override def instanceFooter: Unit = { + out.dec + out.puts("}") + } + + override def instanceCheckCacheAndReturn(instName: InstanceIdentifier, dataType: DataType): Unit = { + out.puts(s"if (${flagForInstName(instName)})") + out.inc + instanceReturn(instName, dataType) + out.dec + } + + override def instanceReturn(instName: InstanceIdentifier, attrType: DataType): Unit = { + out.puts(s"return ${privateMemberName(instName)};") + } + + override def instanceCalculate(instName: Identifier, dataType: DataType, value: expr): Unit = + // Perform explicit cast as unsigned integers can't be directly assigned to the default int type + handleAssignmentSimple(instName, s"(${kaitaiType2NativeType(dataType)}) (${expression(value)})") + + def flagForInstName(ksName: Identifier) = s"f_${idToStr(ksName)}" + + override def enumDeclaration(curClass: String, enumName: String, enumColl: Seq[(Long, String)]): Unit = { + val enumClass = type2class(enumName) + + out.puts + out.puts(s"public enum $enumClass") + out.puts(s"{") + out.inc + + enumColl.foreach { case (id, label) => + out.puts(s"${Utils.upperCamelCase(label)} = $id,") + } + + out.dec + out.puts("}") + } + + def idToStr(id: Identifier): String = { + id match { + case SpecialIdentifier(name) => name + case NamedIdentifier(name) => Utils.lowerCamelCase(name) + case NumberedIdentifier(idx) => s"_${NumberedIdentifier.TEMPLATE}$idx" + case InstanceIdentifier(name) => Utils.lowerCamelCase(name) + case RawIdentifier(innerId) => "_raw_" + idToStr(innerId) + } + } + + override def publicMemberName(id: Identifier): String = { + id match { + case SpecialIdentifier(name) => s"M${Utils.upperCamelCase(name)}" + case NamedIdentifier(name) => Utils.upperCamelCase(name) + case NumberedIdentifier(idx) => s"${NumberedIdentifier.TEMPLATE.capitalize}_$idx" + case InstanceIdentifier(name) => Utils.upperCamelCase(name) + case RawIdentifier(innerId) => s"M_Raw${publicMemberName(innerId)}" + } + } + + override def privateMemberName(id: Identifier): String = { + id match { + case SpecialIdentifier(name) => s"m${Utils.lowerCamelCase(name)}" + case _ => s"_${idToStr(id)}" + } + } + + override def localTemporaryName(id: Identifier): String = s"_t_${idToStr(id)}" + + override def paramName(id: Identifier): String = s"p_${idToStr(id)}" + + override def ksErrorName(err: KSError): String = CSharpAsyncCompiler.ksErrorName(err) + + override def attrValidateExpr( + attrId: Identifier, + attrType: DataType, + checkExpr: Ast.expr, + errName: String, + errArgs: List[Ast.expr] + ): Unit = { + val errArgsStr = errArgs.map(translator.translate).mkString(", ") + out.puts(s"if (!(${translator.translate(checkExpr)}))") + out.puts("{") + out.inc + out.puts(s"throw new $errName($errArgsStr);") + out.dec + out.puts("}") + } +} + +object CSharpAsyncCompiler extends LanguageCompilerStatic + with StreamStructNames + with UpperCamelCaseClasses + with ExceptionNames { + override def getCompiler( + tp: ClassTypeProvider, + config: RuntimeConfig + ): LanguageCompiler = new CSharpAsyncCompiler(tp, config) + + /** + * Determine .NET data type corresponding to a KS data type. + * + * @param attrType KS data type + * @return .NET data type + */ + def kaitaiType2NativeType(attrType: DataType): String = { + attrType match { + case Int1Type(false) => "byte" + case IntMultiType(false, Width2, _) => "ushort" + case IntMultiType(false, Width4, _) => "uint" + case IntMultiType(false, Width8, _) => "ulong" + + case Int1Type(true) => "sbyte" + case IntMultiType(true, Width2, _) => "short" + case IntMultiType(true, Width4, _) => "int" + case IntMultiType(true, Width8, _) => "long" + + case FloatMultiType(Width4, _) => "float" + case FloatMultiType(Width8, _) => "double" + + case BitsType(_) => "ulong" + + case CalcIntType => "int" + case CalcFloatType => "double" + case _: BooleanType => "bool" + + case _: StrType => "string" + case _: BytesType => "byte[]" + + case AnyType => "object" + case KaitaiStructType | CalcKaitaiStructType => kstructName + case KaitaiStreamType => kstreamName + + case t: UserType => types2class(t.name) + case EnumType(name, _) => types2class(name) + + case at: ArrayType => s"List<${kaitaiType2NativeType(at.elType)}>" + + case st: SwitchType => kaitaiType2NativeType(st.combinedType) + } + } + + def kaitaiType2NativeTypeNullable(t: DataType, isNullable: Boolean): String = { + val r = kaitaiType2NativeType(t) + if (isNullable) { + t match { + case _: NumericType | _: BooleanType => s"$r?" + case _ => r + } + } else { + r + } + } + + def types2class(typeName: Ast.typeId): String = + // FIXME: handle absolute + types2class(typeName.names) + def types2class(names: Iterable[String]) = names.map(type2class).mkString(".") + + override def kstructName = "KaitaiAsyncStruct" + override def kstreamName = "KaitaiAsyncStream" + override def ksErrorName(err: KSError): String = err match { + case EndOfStreamError => "EndOfStreamException" + case _ => err.name + } + + override def type2class(name: String): String = Utils.upperCamelCase(name) +} 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 12e44167b..f60eb0f57 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 @@ -12,6 +12,7 @@ object LanguageCompilerStatic { "construct" -> ConstructClassCompiler, "cpp_stl" -> CppCompiler, "csharp" -> CSharpCompiler, + "csharpasync" -> CSharpAsyncCompiler, "graphviz" -> GraphvizClassCompiler, "go" -> GoCompiler, "html" -> HtmlClassCompiler, diff --git a/shared/src/main/scala/io/kaitai/struct/translators/CSharpAsyncTranslator.scala b/shared/src/main/scala/io/kaitai/struct/translators/CSharpAsyncTranslator.scala new file mode 100644 index 000000000..5f4a8cbe3 --- /dev/null +++ b/shared/src/main/scala/io/kaitai/struct/translators/CSharpAsyncTranslator.scala @@ -0,0 +1,131 @@ +package io.kaitai.struct.translators + +import io.kaitai.struct.{ImportList, Utils} +import io.kaitai.struct.datatype.DataType +import io.kaitai.struct.datatype.DataType._ +import io.kaitai.struct.exprlang.Ast +import io.kaitai.struct.exprlang.Ast._ +import io.kaitai.struct.format.Identifier +import io.kaitai.struct.languages.CSharpAsyncCompiler + +class CSharpAsyncTranslator(provider: TypeProvider, importList: ImportList) extends BaseTranslator(provider) { + override def doArrayLiteral(t: DataType, value: Seq[expr]): String = { + val nativeType = CSharpAsyncCompiler.kaitaiType2NativeType(t) + val commaStr = value.map((v) => translate(v)).mkString(", ") + s"new List<$nativeType> { $commaStr }" + } + + override def doByteArrayLiteral(arr: Seq[Byte]): String = + s"new byte[] { ${arr.map(_ & 0xff).mkString(", ")} }" + override def doByteArrayNonLiteral(elts: Seq[Ast.expr]): String = + s"new byte[] { ${elts.map(translate).mkString(", ")} }" + + override val asciiCharQuoteMap: Map[Char, String] = Map( + '\t' -> "\\t", + '\n' -> "\\n", + '\r' -> "\\r", + '"' -> "\\\"", + '\\' -> "\\\\", + + '\0' -> "\\0", + '\7' -> "\\a", + '\f' -> "\\f", + '\13' -> "\\v", + '\b' -> "\\b" + ) + + override def strLiteralGenericCC(code: Char): String = strLiteralUnicode(code) + + override def numericBinOp(left: Ast.expr, op: Ast.operator, right: Ast.expr) = { + (detectType(left), detectType(right), op) match { + case (_: IntType, _: IntType, Ast.operator.Mod) => + s"${CSharpAsyncCompiler.kstreamName}.Mod(${translate(left)}, ${translate(right)})" + case _ => + super.numericBinOp(left, op, right) + } + } + + override def doName(s: String) = + if (s.startsWith("_")) { + s match { + case Identifier.SWITCH_ON => "on" + case Identifier.INDEX => "i" + case _ => s"M${Utils.upperCamelCase(s)}" + } + } else { + s"${Utils.upperCamelCase(s)}" + } + + override def doEnumByLabel(enumTypeAbs: List[String], label: String): String = + s"${enumClass(enumTypeAbs)}.${Utils.upperCamelCase(label)}" + override def doEnumById(enumTypeAbs: List[String], id: String): String = + s"((${enumClass(enumTypeAbs)}) $id)" + + def enumClass(enumTypeAbs: List[String]): String = { + val enumTypeRel = Utils.relClass(enumTypeAbs, provider.nowClass.name) + CSharpAsyncCompiler.types2class(enumTypeRel) + } + + override def doStrCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr) = { + if (op == Ast.cmpop.Eq) { + s"${translate(left)} == ${translate(right)}" + } else if (op == Ast.cmpop.NotEq) { + s"${translate(left)} != ${translate(right)}" + } else { + s"(${translate(left)}.CompareTo(${translate(right)}) ${cmpOp(op)} 0)" + } + } + + override def doBytesCompareOp(left: Ast.expr, op: Ast.cmpop, right: Ast.expr): String = + s"(${CSharpAsyncCompiler.kstreamName}.ByteArrayCompare(${translate(left)}, ${translate(right)}) ${cmpOp(op)} 0)" + + override def arraySubscript(container: expr, idx: expr): String = + s"${translate(container)}[${translate(idx)}]" + override def doIfExp(condition: expr, ifTrue: expr, ifFalse: expr): String = + s"(${translate(condition)} ? ${translate(ifTrue)} : ${translate(ifFalse)})" + override def doCast(value: Ast.expr, typeName: DataType): String = + s"((${CSharpAsyncCompiler.kaitaiType2NativeType(typeName)}) (${translate(value)}))" + + // Predefined methods of various types + override def strToInt(s: expr, base: expr): String = { + importList.add("System") + s"Convert.ToInt64(${translate(s)}, ${translate(base)})" + } + override def enumToInt(v: expr, et: EnumType): String = + translate(v) + override def floatToInt(v: expr): String = + s"(long) (${translate(v)})" + override def intToStr(i: expr, base: expr): String = { + importList.add("System") + s"Convert.ToString((long) (${translate(i)}), ${translate(base)})" + } + override def bytesToStr(bytesExpr: String, encoding: Ast.expr): String = + s"System.Text.Encoding.GetEncoding(${translate(encoding)}).GetString($bytesExpr)" + override def strLength(s: expr): String = + s"${translate(s)}.Length" + + // FIXME: This is not fully Unicode aware, but might be better than nothing. + // http://stackoverflow.com/a/228060/2055163 + override def strReverse(s: expr): String = + s"new string(Array.Reverse(${translate(s)}.ToCharArray()))" + + override def strSubstring(s: expr, from: expr, to: expr): String = + s"${translate(s)}.Substring(${translate(from)}, ${translate(to)} - ${translate(from)})" + + override def arrayFirst(a: expr): String = + s"${translate(a)}[0]" + override def arrayLast(a: expr): String = { + val v = translate(a) + s"$v[$v.Count - 1]" + } + override def arraySize(a: expr): String = + s"${translate(a)}.Count" + override def arrayMin(a: Ast.expr): String = { + importList.add("System.Linq") + s"${translate(a)}.Min()" + } + override def arrayMax(a: Ast.expr): String = { + importList.add("System.Linq") + s"${translate(a)}.Max()" + } +}