diff --git a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala index 0dee55827..be04abc67 100644 --- a/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala +++ b/shared/src/main/scala/io/kaitai/struct/ClassTypeProvider.scala @@ -4,17 +4,24 @@ import io.kaitai.struct.datatype.DataType import io.kaitai.struct.datatype.DataType._ import io.kaitai.struct.exprlang.Ast import io.kaitai.struct.format._ -import io.kaitai.struct.precompile.{EnumNotFoundError, FieldNotFoundError, TypeNotFoundError, TypeUndecidedError} +import io.kaitai.struct.precompile.{EnumNotFoundError, ExpressionError, FieldNotFoundError, TypeNotFoundError, TypeUndecidedError} import io.kaitai.struct.translators.TypeProvider class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends TypeProvider { var nowClass = topClass val allClasses: ClassSpecs = classSpecs - var _currentIteratorType: Option[DataType] = None + /** + * Type of the `_` variable in the expression. That variable is defined in + * `repeat-until` and `valid: expr` contexts and refers to the attribute + * just parsed. + */ + var _lastParsedType: Option[DataType] = None + /** + * Type of the `_on` variable in the expression. That variable is defined in + * `cases.<case>` contexts and refers to the value of `switch-on` expression. + */ var _currentSwitchType: Option[DataType] = None - def currentIteratorType: DataType = _currentIteratorType.get - def currentSwitchType: DataType = _currentSwitchType.get override def determineType(attrName: String): DataType = { determineType(nowClass, attrName) @@ -30,14 +37,20 @@ class ClassTypeProvider(classSpecs: ClassSpecs, var topClass: ClassSpec) extends topClass.toDataType case Identifier.PARENT => if (inClass.parentClass == UnknownClassSpec) - throw new RuntimeException(s"Unable to derive ${Identifier.PARENT} type in ${inClass.name.mkString("::")}") + throw new ExpressionError(s"Unable to derive '${Identifier.PARENT}' type in '${inClass.nameAsStr}'") inClass.parentClass.toDataType case Identifier.IO => KaitaiStreamType case Identifier.ITERATOR => - currentIteratorType + _lastParsedType match { + case Some(value) => value + case None => throw new ExpressionError(s"Context variable '$attrName' is available only in the 'repeat-until' and 'valid/expr' attributes") + } case Identifier.SWITCH_ON => - currentSwitchType + _currentSwitchType match { + case Some(value) => value + case None => throw new ExpressionError(s"Context variable '$attrName' is available only in the 'cases.<case>' expressions") + } case Identifier.INDEX => CalcIntType case Identifier.SIZEOF => diff --git a/shared/src/main/scala/io/kaitai/struct/ConstructClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/ConstructClassCompiler.scala index 4637773bd..8725ec9ed 100644 --- a/shared/src/main/scala/io/kaitai/struct/ConstructClassCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/ConstructClassCompiler.scala @@ -87,7 +87,7 @@ class ConstructClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extend case RepeatExpr(expr) => s"Array(${translator.translate(expr)}, $typeStr)" case RepeatUntil(expr) => - provider._currentIteratorType = Some(dataType) + provider._lastParsedType = Some(dataType) s"RepeatUntil(lambda obj_, list_, this: ${translator.translate(expr)}, $typeStr)" case RepeatEos => s"GreedyRange($typeStr)" diff --git a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala index 22c746953..5d8621228 100644 --- a/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/GraphvizClassCompiler.scala @@ -196,7 +196,7 @@ class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends case ValidationInEnum() => "must be defined in the enum" case ValidationExpr(expr) => - provider._currentIteratorType = Some(dataType) + provider._lastParsedType = Some(dataType) s"must satisfy ${expression(expr, fullPortName, STYLE_EDGE_VALID)}" } case None => return @@ -212,7 +212,7 @@ class GraphvizClassCompiler(classSpecs: ClassSpecs, topClass: ClassSpec) extends expression(ex, s"$currentTable:$portName", STYLE_EDGE_REPEAT) + " times</TD></TR>") case RepeatUntil(ex) => - provider._currentIteratorType = Some(dataType) + provider._lastParsedType = Some(dataType) out.puts("<TR><TD COLSPAN=\"4\" PORT=\"" + portName + "\">repeat until " + expression(ex, s"$currentTable:$portName", STYLE_EDGE_REPEAT) + "</TD></TR>") diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala index 97693c473..3a1090d0f 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CSharpCompiler.scala @@ -280,7 +280,7 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)} = new ${kaitaiType2NativeType(ArrayTypeInStream(dataType))}();") } - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts("{") out.inc out.puts("var i = 0;") @@ -300,8 +300,8 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("}") } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: expr): Unit = { - out.puts(s"for (var i = 0; i < ${expression(repeatExpr)}; i++)") + override def condRepeatExprHeader(countExpr: expr): Unit = { + out.puts(s"for (var i = 0, _end = ${expression(countExpr)}; i < _end; ++i)") out.puts("{") out.inc } @@ -311,12 +311,12 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def condRepeatExprFooter: Unit = fileFooter(null) - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { out.puts("{") out.inc out.puts("var i = 0;") - out.puts(s"${kaitaiType2NativeType(dataType)} ${translator.doName("_")};") - out.puts("do {") + out.puts(s"${kaitaiType2NativeType(itemType)} ${translator.doName(Identifier.ITERATOR)};") + out.puts("while (true) {") out.inc } @@ -330,13 +330,17 @@ class CSharpCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)}.Add($tempVar);") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) - out.puts("i++;") - out.dec - out.puts(s"} while (!(${expression(untilExpr)}));") + override def condRepeatUntilFooter(untilExpr: expr): Unit = { + out.puts(s"if (${expression(untilExpr)}) {") + out.inc + out.puts("break;") out.dec out.puts("}") + out.puts("++i;") + out.dec + out.puts("}") // close while (true) + out.dec + out.puts("}") // close scope of i and _ variables } override def handleAssignmentSimple(id: Identifier, expr: String): Unit = diff --git a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala index b0fdee657..b162c1645 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/CppCompiler.scala @@ -571,7 +571,7 @@ class CppCompiler( outSrc.puts(s"${privateMemberName(id)} = ${newVector(dataType)};") } - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { outSrc.puts("{") outSrc.inc outSrc.puts("int i = 0;") @@ -591,10 +591,8 @@ class CppCompiler( outSrc.puts("}") } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: Ast.expr): Unit = { - val lenVar = s"l_${idToStr(id)}" - outSrc.puts(s"const int $lenVar = ${expression(repeatExpr)};") - outSrc.puts(s"for (int i = 0; i < $lenVar; i++) {") + override def condRepeatExprHeader(countExpr: Ast.expr): Unit = { + outSrc.puts(s"for (int i = 0, _end = ${expression(countExpr)}; i < _end; ++i) {") outSrc.inc } @@ -606,12 +604,12 @@ class CppCompiler( outSrc.puts("}") } - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { outSrc.puts("{") outSrc.inc outSrc.puts("int i = 0;") - outSrc.puts(s"${kaitaiType2NativeType(dataType.asNonOwning())} ${translator.doName("_")};") - outSrc.puts("do {") + outSrc.puts(s"${kaitaiType2NativeType(itemType.asNonOwning())} ${translator.doName(Identifier.ITERATOR)};") + outSrc.puts("while (true) {") outSrc.inc } @@ -640,13 +638,17 @@ class CppCompiler( outSrc.puts(s"${privateMemberName(id)}->push_back($wrappedTempVar);") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) - outSrc.puts("i++;") - outSrc.dec - outSrc.puts(s"} while (!(${expression(untilExpr)}));") + override def condRepeatUntilFooter(untilExpr: expr): Unit = { + outSrc.puts(s"if (${expression(untilExpr)}) {") + outSrc.inc + outSrc.puts("break;") outSrc.dec outSrc.puts("}") + outSrc.puts("++i;") + outSrc.dec + outSrc.puts("}") // close while (true) + outSrc.dec + outSrc.puts("}") // close scope of i and _ variables } override def handleAssignmentSimple(id: Identifier, expr: String): Unit = { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala index 49e4343e1..a6da11992 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/GoCompiler.scala @@ -300,12 +300,12 @@ class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) // function works even on `nil` slices (https://go.dev/tour/moretypes/15) } - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts(s"for i := 0;; i++ {") out.inc val eofVar = translator.allocateLocalVar() - out.puts(s"${translator.localVarName(eofVar)}, err := this._io.EOF()") + out.puts(s"${translator.localVarName(eofVar)}, err := $io.EOF()") translator.outAddErrCheck() out.puts(s"if ${translator.localVarName(eofVar)} {") out.inc @@ -320,8 +320,8 @@ class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"$name = append($name, $expr)") } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: Ast.expr): Unit = { - out.puts(s"for i := 0; i < int(${expression(repeatExpr)}); i++ {") + override def condRepeatExprHeader(countExpr: Ast.expr): Unit = { + out.puts(s"for i, _end := 0, int(${expression(countExpr)}); i < _end; i++ {") out.inc // FIXME: Go throws a fatal compile error when the `i` variable is not used (unused variables // can only use the blank identifier `_`, see https://go.dev/doc/effective_go#blank), so we have @@ -334,8 +334,11 @@ class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def handleAssignmentRepeatExpr(id: Identifier, r: TranslatorResult): Unit = handleAssignmentRepeatEos(id, r) - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit = { - out.puts(s"for i := 1;; i++ {") + override def condRepeatUntilHeader(itemType: DataType): Unit = { + out.puts("{") + out.inc + out.puts("i := 0") + out.puts(s"for {") out.inc } @@ -346,15 +349,17 @@ class GoCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)} = append(${privateMemberName(id)}, $tempVar)") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) + override def condRepeatUntilFooter(untilExpr: Ast.expr): Unit = { out.puts(s"if ${expression(untilExpr)} {") out.inc out.puts("break") out.dec out.puts("}") + out.puts("i++;") out.dec - out.puts("}") + out.puts("}") // close for + out.dec + out.puts("}") // close scope of i variable } private def castToType(r: TranslatorResult, dataType: DataType): TranslatorResult = { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala index ee19058d3..7f087bc08 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaCompiler.scala @@ -355,7 +355,7 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = out.puts(s"${privateMemberName(id)} = new ${kaitaiType2JavaType(ArrayTypeInStream(dataType))}();") - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts("{") out.inc out.puts("int i = 0;") @@ -377,8 +377,8 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("}") } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: expr): Unit = { - out.puts(s"for (int i = 0; i < ${expression(repeatExpr)}; i++) {") + override def condRepeatExprHeader(countExpr: expr): Unit = { + out.puts(s"for (int i = 0, _end = ${expression(countExpr)}; i < _end; ++i) {") out.inc importList.add("java.util.ArrayList") @@ -387,12 +387,12 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit = handleAssignmentRepeatEos(id, expr) - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { out.puts("{") out.inc - out.puts(s"${kaitaiType2JavaType(dataType)} ${translator.doName("_")};") + out.puts(s"${kaitaiType2JavaType(itemType)} ${translator.doName(Identifier.ITERATOR)};") out.puts("int i = 0;") - out.puts("do {") + out.puts("while (true) {") out.inc importList.add("java.util.ArrayList") @@ -408,13 +408,17 @@ class JavaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)}.add($tempVar);") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) - out.puts("i++;") - out.dec - out.puts(s"} while (!(${expression(untilExpr)}));") + override def condRepeatUntilFooter(untilExpr: expr): Unit = { + out.puts(s"if (${expression(untilExpr)}) {") + out.inc + out.puts("break;") out.dec out.puts("}") + out.puts("++i;") + out.dec + out.puts("}") // close while (true) + out.dec + out.puts("}") // close scope of i and _ variables } override def handleAssignmentSimple(id: Identifier, expr: String): Unit = diff --git a/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala index a039b4560..86836b244 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/JavaScriptCompiler.scala @@ -303,7 +303,7 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = out.puts(s"${privateMemberName(id)} = [];") - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts("var i = 0;") out.puts(s"while (!$io.isEof()) {") out.inc @@ -319,8 +319,8 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("}") } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: expr): Unit = { - out.puts(s"for (var i = 0; i < ${expression(repeatExpr)}; i++) {") + override def condRepeatExprHeader(countExpr: expr): Unit = { + out.puts(s"for (var i = 0, _end = ${expression(countExpr)}; i < _end; ++i) {") out.inc } @@ -332,9 +332,10 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("}") } - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { + // "var" variables in any case have a scope of surrounding function, no need a scope to isolate them out.puts("var i = 0;") - out.puts("do {") + out.puts("while (true) {") out.inc } @@ -344,11 +345,15 @@ class JavaScriptCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)}.push($tmpName);") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) - out.puts("i++;") + override def condRepeatUntilFooter(untilExpr: expr): Unit = { + out.puts(s"if (${expression(untilExpr)}) {") + out.inc + out.puts("break;") + out.dec + out.puts("}") + out.puts("++i;") out.dec - out.puts(s"} while (!(${expression(untilExpr)}));") + out.puts("}") // close while (true) } override def handleAssignmentSimple(id: Identifier, expr: String): Unit = { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala index 1f24674e9..5fd88d3d2 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/LuaCompiler.scala @@ -170,7 +170,7 @@ class LuaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)} = {}") } - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts("local i = 0") out.puts(s"while not $io:is_eof() do") out.inc @@ -181,8 +181,8 @@ class LuaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("end") } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: Ast.expr): Unit = { - out.puts(s"for i = 0, ${expression(repeatExpr)} - 1 do") + override def condRepeatExprHeader(countExpr: Ast.expr): Unit = { + out.puts(s"for i = 0, ${expression(countExpr)} - 1 do") out.inc } override def condRepeatExprFooter: Unit = { @@ -190,13 +190,13 @@ class LuaCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("end") } - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { + // Lua allows shadowing of variables, no need a scope to isolate them out.puts("local i = 0") out.puts("while true do") out.inc } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) + override def condRepeatUntilFooter(untilExpr: Ast.expr): Unit = { out.puts(s"if ${expression(untilExpr)} then") out.inc out.puts("break") diff --git a/shared/src/main/scala/io/kaitai/struct/languages/NimCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/NimCompiler.scala index 21868c146..8822391ff 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/NimCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/NimCompiler.scala @@ -155,7 +155,7 @@ class NimCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) // empty sequences (see https://narimiran.github.io/nim-basics/#_result_variable) } - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts("block:") out.inc out.puts("var i: int") @@ -167,19 +167,18 @@ class NimCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.dec out.dec } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: Ast.expr): Unit = { - out.puts(s"for i in 0 ..< int(${expression(repeatExpr)}):") + override def condRepeatExprHeader(countExpr: Ast.expr): Unit = { + out.puts(s"for i in 0 ..< int(${expression(countExpr)}):") out.inc } - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { out.puts("block:") out.inc out.puts("var i: int") out.puts("while true:") out.inc } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) + override def condRepeatUntilFooter(untilExpr: Ast.expr): Unit = { out.puts(s"if ${expression(untilExpr)}:") out.inc out.puts("break") diff --git a/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala index c2c4d1476..64433f7c3 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/PHPCompiler.scala @@ -282,7 +282,7 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = out.puts(s"${privateMemberName(id)} = [];") - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts("$i = 0;") out.puts(s"while (!$io->isEof()) {") out.inc @@ -297,8 +297,8 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) super.condRepeatEosFooter } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: Ast.expr): Unit = { - out.puts(s"$$n = ${expression(repeatExpr)};") + override def condRepeatExprHeader(countExpr: Ast.expr): Unit = { + out.puts(s"$$n = ${expression(countExpr)};") out.puts("for ($i = 0; $i < $n; $i++) {") out.inc } @@ -306,9 +306,9 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit = handleAssignmentRepeatEos(id, expr) - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { out.puts("$i = 0;") - out.puts("do {") + out.puts("while (1) {") out.inc } @@ -318,11 +318,17 @@ class PHPCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)}[] = $tmpName;") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) + override def condRepeatUntilFooter(untilExpr: Ast.expr): Unit = { + out.puts(s"if ${expression(untilExpr)} {") + out.inc + out.puts("break;") + out.dec + out.puts("}") out.puts("$i++;") out.dec - out.puts(s"} while (!(${expression(untilExpr)}));") + out.puts("}") // close while (1) + out.dec + out.puts("}") // close scope of i variable } override def handleAssignmentSimple(id: Identifier, expr: String): Unit = { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/PerlCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/PerlCompiler.scala index c8bfe5b0c..1fbb7ec68 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/PerlCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/PerlCompiler.scala @@ -246,7 +246,9 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = out.puts(s"${privateMemberName(id)} = [];") - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { + // Perl allows shadowing of variables, no need a scope to isolate them + out.puts("my $i = 0;") out.puts(s"while (!$io->is_eof()) {") out.inc } @@ -254,18 +256,24 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def handleAssignmentRepeatEos(id: Identifier, expr: String): Unit = out.puts(s"push @{${privateMemberName(id)}}, $expr;") - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: expr): Unit = { - val nVar = s"$$n_${idToStr(id)}" - out.puts(s"my $nVar = ${expression(repeatExpr)};") - out.puts(s"for (my $$i = 0; $$i < $nVar; $$i++) {") + override def condRepeatEosFooter: Unit = { + out.puts("$i++;") + out.dec + out.puts("}") + } + + override def condRepeatExprHeader(countExpr: expr): Unit = { + out.puts(s"for (my $$i = 0, $$_end = ${expression(countExpr)}; $$i < $$_end; ++$$i) {") 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("do {") + override def condRepeatUntilHeader(itemType: DataType): Unit = { + // Perl allows shadowing of variables, no need a scope to isolate them + out.puts("my $i = 0;") + out.puts("while (1) {") out.inc } @@ -279,10 +287,11 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"push @{${privateMemberName(id)}}, $tmpName;") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) + override def condRepeatUntilFooter(untilExpr: expr): Unit = { + out.puts(s"last if (${expression(untilExpr)});") + out.puts("$i++;") out.dec - out.puts(s"} until (${expression(untilExpr)});") + out.puts("}") // close while (1) } override def handleAssignmentSimple(id: Identifier, expr: String): Unit = @@ -352,7 +361,6 @@ class PerlCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def switchRequiresIfs(onType: DataType): Boolean = true override def switchIfStart(id: Identifier, on: Ast.expr, onType: DataType): Unit = { - typeProvider._currentSwitchType = Some(translator.detectType(on)) out.puts(s"my $$_on = ${expression(on)};") } diff --git a/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala index 37a75f323..adbb82262 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/PythonCompiler.scala @@ -300,7 +300,7 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = out.puts(s"${privateMemberName(id)} = []") - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts("i = 0") out.puts(s"while not $io.is_eof():") out.inc @@ -313,14 +313,14 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) universalFooter } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: expr): Unit = { - out.puts(s"for i in range(${expression(repeatExpr)}):") + override def condRepeatExprHeader(countExpr: expr): Unit = { + out.puts(s"for i in range(${expression(countExpr)}):") 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 = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { out.puts("i = 0") out.puts("while True:") out.inc @@ -332,8 +332,7 @@ class PythonCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)}.append($tmpName)") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) + override def condRepeatUntilFooter(untilExpr: expr): Unit = { out.puts(s"if ${expression(untilExpr)}:") out.inc out.puts("break") diff --git a/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala index ccda2646e..7a3398e97 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/RubyCompiler.scala @@ -306,7 +306,7 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = out.puts(s"${privateMemberName(id)} = []") - override def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit = { + override def condRepeatEosHeader(io: String): Unit = { out.puts("i = 0") out.puts(s"while not $io.eof?") out.inc @@ -319,8 +319,8 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) super.condRepeatEosFooter } - override def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: expr): Unit = { - out.puts(s"(${expression(repeatExpr)}).times { |i|") + override def condRepeatExprHeader(countExpr: expr): Unit = { + out.puts(s"(${expression(countExpr)}).times { |i|") out.inc } override def handleAssignmentRepeatExpr(id: Identifier, expr: String): Unit = @@ -331,9 +331,9 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("}") } - override def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { + override def condRepeatUntilHeader(itemType: DataType): Unit = { out.puts("i = 0") - out.puts("begin") + out.puts("while true") out.inc } @@ -343,11 +343,11 @@ class RubyCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"${privateMemberName(id)} << $tmpName") } - override def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: expr): Unit = { - typeProvider._currentIteratorType = Some(dataType) + override def condRepeatUntilFooter(untilExpr: expr): Unit = { + out.puts(s"break if ${expression(untilExpr)}") out.puts("i += 1") out.dec - out.puts(s"end until ${expression(untilExpr)}") + out.puts("end") } override def handleAssignmentSimple(id: Identifier, expr: String): Unit = diff --git a/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala index b0e885e7b..3503cb8ab 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/RustCompiler.scala @@ -273,19 +273,14 @@ class RustCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.inc } - override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = { - // this line required for handleAssignmentRepeatUntil - typeProvider._currentIteratorType = Some(dataType) + override def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit = { out.puts(s"*${RustCompiler.privateMemberName(id, writeAccess = true)} = Vec::new();") } - override def condRepeatEosHeader(id: Identifier, - io: String, - dataType: DataType): Unit = { - out.puts("{") - out.inc + override def condRepeatEosHeader(io: String): Unit = { + // Rust allows shadowing of variables, no need a scope to isolate them out.puts(s"let mut _i = 0;") - out.puts(s"while !_io.is_eof() {") + out.puts(s"while !$io.is_eof() {") out.inc } @@ -297,28 +292,18 @@ class RustCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts("_i += 1;") out.dec out.puts("}") - out.dec - out.puts("}") } - override def condRepeatExprHeader(id: Identifier, - io: String, - dataType: DataType, - repeatExpr: Ast.expr): Unit = { - val lenVar = s"l_${idToStr(id)}" - out.puts(s"let $lenVar = ${expression(repeatExpr)};") - out.puts(s"for _i in 0..$lenVar {") + override def condRepeatExprHeader(countExpr: Ast.expr): Unit = { + out.puts(s"let _end = ${expression(countExpr)};") + out.puts(s"for _i in 0.._end {") out.inc } - override def condRepeatUntilHeader(id: Identifier, - io: String, - dataType: DataType, - repeatExpr: Ast.expr): Unit = { - out.puts("{") - out.inc + override def condRepeatUntilHeader(itemType: DataType): Unit = { + // Rust allows shadowing of variables, no need a scope to isolate them out.puts("let mut _i = 0;") - out.puts("while {") + out.puts("loop {") out.inc } @@ -331,7 +316,8 @@ class RustCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) isRaw: Boolean): Unit = { out.puts(s"${RustCompiler.privateMemberName(id, writeAccess = true)}.push($expr);") var copy_type = "" - if (typeProvider._currentIteratorType.isDefined && translator.is_copy_type(typeProvider._currentIteratorType.get)) { + // typeProvider._lastParsedType is set in CommonReads.attrParse0 + if (translator.is_copy_type(typeProvider._lastParsedType.get)) { copy_type = "*" } val t = localTemporaryName(id) @@ -339,19 +325,15 @@ class RustCompiler(typeProvider: ClassTypeProvider, config: RuntimeConfig) out.puts(s"let ${translator.doLocalName(Identifier.ITERATOR)} = $copy_type$t.last().unwrap();") } - override def condRepeatUntilFooter(id: Identifier, - io: String, - dataType: DataType, - repeatExpr: Ast.expr): Unit = { - // this line required by kaitai code - typeProvider._currentIteratorType = Some(dataType) - out.puts("_i += 1;") - out.puts(s"let x = !(${expression(repeatExpr)});") - out.puts("x") - out.dec - out.puts("} {}") + override def condRepeatUntilFooter(untilExpr: Ast.expr): Unit = { + out.puts(s"if ${expression(untilExpr)} {") + out.inc + out.puts("break") out.dec out.puts("}") + out.puts("_i += 1;") + out.dec + out.puts("}") // close loop } def getRawIdExpr(varName: Identifier, rep: RepeatSpec): String = { diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/CommonReads.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/CommonReads.scala index de049dc14..41f45661e 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/components/CommonReads.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/components/CommonReads.scala @@ -64,11 +64,11 @@ trait CommonReads extends LanguageCompiler { (ExtraAttrs.forAttr(attr, this) ++ List(attr)).foreach(a => condRepeatInitAttr(a.id, a.dataType)) attr.cond.repeat match { case RepeatEos => - condRepeatEosHeader(id, io, attr.dataType) - case RepeatExpr(repeatExpr: Ast.expr) => - condRepeatExprHeader(id, io, attr.dataType, repeatExpr) - case RepeatUntil(untilExpr: Ast.expr) => - condRepeatUntilHeader(id, io, attr.dataType, untilExpr) + condRepeatEosHeader(io) + case RepeatExpr(countExpr: Ast.expr) => + condRepeatExprHeader(countExpr) + case RepeatUntil(_) => + condRepeatUntilHeader(attr.dataType) case NoRepeat => } @@ -88,7 +88,9 @@ trait CommonReads extends LanguageCompiler { case _: RepeatExpr => condRepeatExprFooter case RepeatUntil(untilExpr: Ast.expr) => - condRepeatUntilFooter(id, io, attr.dataType, untilExpr) + // Set the type of the `_` variable in expression + typeProvider._lastParsedType = Some(attr.dataType) + condRepeatUntilFooter(untilExpr) case NoRepeat => } } diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala index 9beb9f707..bb9f2fc82 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/components/LanguageCompiler.scala @@ -133,14 +133,47 @@ abstract class LanguageCompiler( def condRepeatInitAttr(id: Identifier, dataType: DataType): Unit - def condRepeatEosHeader(id: Identifier, io: String, dataType: DataType): Unit + /** + * Generates start of loop until end-of-stream is reached. + * + * @param io rendered expression that evaluates to reference of a stream which + * should be checked + */ + def condRepeatEosHeader(io: String): Unit + /** + * Generates end of loop until end-of-stream is reached. + */ def condRepeatEosFooter: Unit - def condRepeatExprHeader(id: Identifier, io: String, dataType: DataType, repeatExpr: Ast.expr): Unit + /** + * Generates start of "for 0..<count>" loop which loops specified count of times. + * + * @param countExpr expression that evaluates into number of repetitions. + * That expression should be evaluated only once before loop is started + */ + def condRepeatExprHeader(countExpr: Ast.expr): Unit + /** + * Generates end of "for 0..<count>" loop. + */ def condRepeatExprFooter: Unit - def condRepeatUntilHeader(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit - def condRepeatUntilFooter(id: Identifier, io: String, dataType: DataType, untilExpr: Ast.expr): Unit + /** + * Generates start of "repeat ... until" loop which parses element of type `itemType` + * on each iteration. + * + * @param itemType Type of the element that can be used to declare variable for + * holding that element so it will accessible in the condition in the footer. + * In most languages condition in special "repeat ... until" loop cannot + * access variables, defined in body of loop + */ + def condRepeatUntilHeader(itemType: DataType): Unit + /** + * Generates end of "repeat ... until" loop which checks the specified condition. + * + * @param untilExpr condition that evaluates to boolean value where `true` means + * that loop will be finished + */ + def condRepeatUntilFooter(untilExpr: Ast.expr): Unit def attrProcess(proc: ProcessExpr, varSrc: Identifier, varDest: Identifier, rep: RepeatSpec): Unit diff --git a/shared/src/main/scala/io/kaitai/struct/languages/components/ValidateOps.scala b/shared/src/main/scala/io/kaitai/struct/languages/components/ValidateOps.scala index 2c346726b..632cc1781 100644 --- a/shared/src/main/scala/io/kaitai/struct/languages/components/ValidateOps.scala +++ b/shared/src/main/scala/io/kaitai/struct/languages/components/ValidateOps.scala @@ -62,7 +62,7 @@ trait ValidateOps extends ExceptionNames { ) case ValidationExpr(expr) => blockScopeHeader - typeProvider._currentIteratorType = Some(attr.dataType) + typeProvider._lastParsedType = Some(attr.dataType) handleAssignmentTempVar( attr.dataType, translator.translate(Ast.expr.Name(Ast.identifier(Identifier.ITERATOR))), diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala index 1ac6035bb..3a5a9e6e3 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/Exceptions.scala @@ -8,7 +8,7 @@ import io.kaitai.struct.translators.MethodArgType * Base class for all expression-related errors, not localized to a certain path * in source file. */ -sealed abstract class ExpressionError(msg: String) extends RuntimeException(msg) +sealed class ExpressionError(msg: String) extends RuntimeException(msg) class TypeMismatchError(msg: String) extends ExpressionError(msg) class TypeUndecidedError(msg: String) extends ExpressionError(msg) class WrongMethodCall(val dataType: MethodArgType, val methodName: String, val expectedSigs: Iterable[String], val actualSig: String) diff --git a/shared/src/main/scala/io/kaitai/struct/precompile/TypeValidator.scala b/shared/src/main/scala/io/kaitai/struct/precompile/TypeValidator.scala index d80741484..bacde295a 100644 --- a/shared/src/main/scala/io/kaitai/struct/precompile/TypeValidator.scala +++ b/shared/src/main/scala/io/kaitai/struct/precompile/TypeValidator.scala @@ -70,11 +70,11 @@ class TypeValidator(specs: ClassSpecs) extends PrecompileStep { checkAssert[BooleanType](ifExpr, "boolean", path, "if") ) - provider._currentIteratorType = Some(attr.dataType) val problemsRepeat: Iterable[CompilationProblem] = attr.cond.repeat match { case RepeatExpr(expr) => checkAssert[IntType](expr, "integer", path, "repeat-expr") case RepeatUntil(expr) => + provider._lastParsedType = Some(attr.dataType) checkAssert[BooleanType](expr, "boolean", path, "repeat-until") case RepeatEos | NoRepeat => None