diff --git a/docs/lang/operators.md b/docs/lang/operators.md index 53dd5a36..242bca64 100644 --- a/docs/lang/operators.md +++ b/docs/lang/operators.md @@ -225,8 +225,17 @@ Other kinds of expressions than the above (even `nonet(byte + byte + byte)`) wil * `hi`, `lo`: most/least significant byte of a word `hi(word)` -Furthermore, any type that can be assigned to a variable -can be used to convert from one type to another of the same size. +Furthermore, any type that can be assigned to a variable can be used to convert +either from one type either to another type of the same size, +or from a 1-byte integer type to a compatible 2-byte integer type. + +`byte` → `word` +`word` → `pointer` +some enum → `byte` +`byte` → some enum +but not +`word` → `byte` +some enum → `word` diff --git a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala index f4625e1e..2dfccb9d 100644 --- a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala @@ -130,6 +130,24 @@ class AbstractExpressionCompiler[T <: AbstractCode] { } count <= 1 } + + def validateTypeCastAndGetSourceExpressionType(ctx: CompilationContext, typ: Type, params: List[Expression]): Type = { + var failed = false + if (typ.name == "pointer") { + ctx.log.error("Cannot cast into pointer") + failed = true + } + if (params.length != 1) { + ctx.log.error("Type casting should have exactly one argument") + failed = true + } + val sourceType = getExpressionType(ctx, params.head) + if (typ.size != sourceType.size && !sourceType.isAssignableTo(typ)) { + ctx.log.error("Cannot cast a type to an incompatible type of different size") + failed = true + } + sourceType + } } object AbstractExpressionCompiler { diff --git a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala index 0a1283ee..c4906862 100644 --- a/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/mos/MosExpressionCompiler.scala @@ -1066,22 +1066,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] { case _ => env.maybeGet[Type](f.functionName) match { case Some(typ) => - var failed = false - if (typ.name == "pointer") { - ctx.log.error("Cannot cast into pointer") - failed = true - } - if (params.length != 1) { - ctx.log.error("Type casting should have exactly one argument") - failed = true - } - val sourceType = getExpressionType(ctx, params.head) - if (typ.size != sourceType.size){ - ctx.log.error("Cannot cast a type to a type of different size") - failed = true - } + val sourceType = validateTypeCastAndGetSourceExpressionType(ctx, typ, params) val newExprTypeAndVariable = exprTypeAndVariable.map(i => sourceType -> i._2) - return if (failed) Nil else compile(ctx, params.head, newExprTypeAndVariable, branches) + return compile(ctx, params.head, newExprTypeAndVariable, branches) case None => // fallthrough to the lookup below } diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index 16268d48..75ecf54e 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -782,20 +782,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case _ => env.maybeGet[Type](f.functionName) match { case Some(typ) => - var failed = false - if (typ.name == "pointer") { - ctx.log.error("Cannot cast into pointer") - failed = true - } - if (params.length != 1) { - ctx.log.error("Type casting should have exactly one argument") - failed = true - } - val sourceType = getExpressionType(ctx, params.head) - if (typ.size != sourceType.size) { - ctx.log.error("Cannot cast a type to a type of different size") - failed = true - } + val sourceType = validateTypeCastAndGetSourceExpressionType(ctx, typ, params) return sourceType.size match { case 1 => targetifyA(ctx, target, compileToA(ctx, params.head), isSigned = sourceType.isSigned) case 2 => targetifyHL(ctx, target, compileToHL(ctx, params.head)) diff --git a/src/test/scala/millfork/test/WordMathSuite.scala b/src/test/scala/millfork/test/WordMathSuite.scala index 38eb4cea..9f4d8db2 100644 --- a/src/test/scala/millfork/test/WordMathSuite.scala +++ b/src/test/scala/millfork/test/WordMathSuite.scala @@ -20,6 +20,19 @@ class WordMathSuite extends FunSuite with Matchers { """.stripMargin)(_.readWord(0xc000) should equal(1280)) } + test("Cast word addition") { + EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" + | byte output @$c000 + | word a + | void main () { + | output = add(155, 166) + | } + | byte add(byte a, byte b) { + | return hi(word(a) + word(b)) + | } + """.stripMargin)(_.readByte(0xc000) should equal(1)) + } + test("Word subtraction") { EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(""" | word output @$c000 @@ -132,7 +145,7 @@ class WordMathSuite extends FunSuite with Matchers { | output = get(5, 6) | } | byte get(byte mx, byte my) { - | return map[((mx + 00000) << 5) + my] + | return map[(word(mx) << 5) + my] | } """.stripMargin){ m => m.readByte(0xc3a6) should equal(77)