From 499e650752295b7ed5ffa2be1f44e9fb8b2ee085 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Wed, 13 Jan 2021 19:35:11 +0100 Subject: [PATCH] Define and document more magic suffixes and constants (see #87) --- docs/api/custom-platform.md | 2 +- docs/doc_index.md | 2 + docs/lang/predefined_constants.md | 4 +- docs/lang/suffixes.md | 46 +++++++++++++++++++ docs/lang/syntax.md | 6 +++ docs/lang/types.md | 8 +++- mkdocs.yml | 1 + src/main/scala/millfork/env/Environment.scala | 30 +++++++++--- .../millfork/output/AbstractAssembler.scala | 1 + .../scala/millfork/test/SegmentSuite.scala | 6 +++ 10 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 docs/lang/suffixes.md diff --git a/docs/api/custom-platform.md b/docs/api/custom-platform.md index f41e1b8a..4ee5a58c 100644 --- a/docs/api/custom-platform.md +++ b/docs/api/custom-platform.md @@ -173,7 +173,7 @@ Default: `after_code`. * `segment_NAME_bank` – the bank number the segment belongs to. Default: `0`. For better debugging on NES, RAM segments should use bank number `$ff`. -* `segment_NAME_fill` – the byte value used to fill gaps and other unused space in the bank. Default: `0`. +* `segment_NAME_fill` – the byte value used to fill gaps and other unused space in the segment. Default: `0`. * `segment_NAME_layout` – a comma-separated list of object names that defines in what order the objects are laid out in the segment. One item has to be `*`, it means "all the other objects". diff --git a/docs/doc_index.md b/docs/doc_index.md index ca8dc2f3..2e84773d 100644 --- a/docs/doc_index.md +++ b/docs/doc_index.md @@ -26,6 +26,8 @@ * [Predefined constants](lang/predefined_constants.md) +* [List of magic suffixes](lang/suffixes.md) + * [List of text encodings and escape sequences](lang/text.md) * [Defining custom encodings](lang/custom-encoding.md) diff --git a/docs/lang/predefined_constants.md b/docs/lang/predefined_constants.md index dab73a57..9f305435 100644 --- a/docs/lang/predefined_constants.md +++ b/docs/lang/predefined_constants.md @@ -2,7 +2,9 @@ # Predefined constants -* `byte nullchar` – the null terminator for strings in the default encoding, equivalent to `""z[0]` +* `byte nullchar` – the null terminator for strings in the default encoding, equivalent to `""z[0]`, can be overriden by the `NULLCHAR` feature + +* `byte nullchar_scr` – the null terminator for strings in the screen encoding, equivalent to `""scrz[0]`, can be overriden by the `NULLCHAR_SCR` feature * `null$ nullptr` – the invalid pointer value; the value of the `NULLPTR` feature diff --git a/docs/lang/suffixes.md b/docs/lang/suffixes.md new file mode 100644 index 00000000..478e9ebb --- /dev/null +++ b/docs/lang/suffixes.md @@ -0,0 +1,46 @@ +[< back to index](../doc_index.md) + +# Magic suffixes + +## Byte-related suffixes + +These suffixes can be only applied to arithmetic or pointer variables: + +* `.lo` – the least significant byte of a two-byte variable (word, pointer) (use `lo(_)` for arbitrary expressions) + +* `.hi` – the most significant byte of a two-byte variable (word, pointer) (use `hi(_)` for arbitrary expressions) + +* `.loword` – the least significant byte of a three- or four-byte variable + +* `.hiword` – the most significant byte of a three- or four-byte variable + +* `.b0`, `.b1` etc. – the given byte of the multi-byte arithmetic variable, with `.b0` being the least significant byte + +## Pointer-related suffixes: + +These suffixes can be applied to variables, arrays, functions or pointable expressions (sometimes called _lvalues_): + +* `.addr` – returns address of the object (type `pointer`) (constant unless on Lunix) + +* `.rawaddr` – returns the raw address constant of the object (type `pointer`, the same as `.addr` unless on Lunix, guaranteed to be constant) + +* `.pointer` – returns the typed pointer to the object + +This suffix is available only on expressions that have a type of a typed pointer: + +* `.raw` – a view of the pointer as a raw pointer + +## Segment-related suffixes + +These suffixes can be applied to variables, arrays, or functions: + +* `.segment.bank` (or `.segment` for short) – returns the bank number of the segment the object is in + +* `.segment.start` – returns the start address of the segment the object is in + +* `.segment.end` – returns the last address of the segment the object is in + +* `.segment.fill` – returns the byte value used to fill gaps and other unused space in the segment the object is in + +See also [the list of predefined constants](./predefined_constants.md). + \ No newline at end of file diff --git a/docs/lang/syntax.md b/docs/lang/syntax.md index 2df6ceec..8ea06fbc 100644 --- a/docs/lang/syntax.md +++ b/docs/lang/syntax.md @@ -107,6 +107,12 @@ For every variable `x` larger than a byte, extra subvariables are defined: * constituent bytes, from low to high: `x.b0`, `x.b1`, `x.b2`, etc. * the lowest word: `x.loword` (=`x.b1:x.b0`) + +* if `x` is a typed pointer: + + * a view of as a raw pointer: `x.raw` + +See also [the list of magic suffixes](./suffixes.md). ### Constant declarations diff --git a/docs/lang/types.md b/docs/lang/types.md index f10cc866..dc1127ed 100644 --- a/docs/lang/types.md +++ b/docs/lang/types.md @@ -158,7 +158,9 @@ Functions that are interrupt pointers have their own pointer types: Boolean types can be used as conditions. They have two possible values, `true` and `false`. -* `bool` – a 1-byte boolean value. An uninitialized variable of type `bool` may contain an invalid value. +* `bool` – a 1-byte boolean value. +An uninitialized variable of type `bool` may contain an invalid value. +The value `false` is stored as 0, `true` as 1. * several boolean types based on the CPU flags that may be used only as a return type for a function written in assembly: @@ -174,6 +176,10 @@ Boolean types can be used as conditions. They have two possible values, `true` a 2\. LR35902 does not support these types due to the lack of appropriate flags +You can convert from a boolean type to an arithmetic type by simply casting: + + byte c = byte(x >= 0x80) + Examples: bool f() = true diff --git a/mkdocs.yml b/mkdocs.yml index 2c5345d4..ca30eb40 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,7 @@ nav: - Types: lang/types.md - Literals: lang/literals.md - Predefined constants: lang/predefined_constants.md + - List of magic suffixes: lang/suffixes.md - Text encodings: lang/text.md - Custom text encodings: lang/custom-encoding.md - Operators: lang/operators.md diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 8169f9d1..4adad685 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -512,6 +512,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa addUnexpandedPointerConstant(s"segment.$segment.end") addUnexpandedWordConstant(s"segment.$segment.length") addUnexpandedByteConstant(s"segment.$segment.bank") + addUnexpandedByteConstant(s"segment.$segment.fill") } addThing(ConstantThing("$0000", Constant.WordZero, p), None) addThing(FlagBooleanType("set_carry", @@ -1431,8 +1432,21 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa private def registerAddressConstant(thing: ThingInMemory, position: Option[Position], options: CompilationOptions, targetType: Option[Type]): Unit = { val b = get[Type]("byte") + val w = get[Type]("word") + val ptr = get[Type]("pointer") + val segment = thing.bank(options) + for (bankNumber <- options.platform.bankNumbers.get(segment)) { + addThing(ConstantThing(thing.name + ".segment", NumericConstant(bankNumber, 1), b), position) + addThing(ConstantThing(thing.name + ".segment.bank", NumericConstant(bankNumber, 1), b), position) + } + for (bankFill <- options.platform.bankFill.get(segment)) { + addThing(ConstantThing(thing.name + ".segment.fill", NumericConstant(bankFill, 1), b), position) + } + addThing(ConstantThing(thing.name + ".segment.start", UnexpandedConstant(s"segment.$segment.start", 2), ptr), position) + addThing(ConstantThing(thing.name + ".segment.heapstart", UnexpandedConstant(s"segment.$segment.heapstart", 2), ptr), position) + addThing(ConstantThing(thing.name + ".segment.end", UnexpandedConstant(s"segment.$segment.end", 2), ptr), position) + addThing(ConstantThing(thing.name + ".segment.length", UnexpandedConstant(s"segment.$segment.length", 2), w), position) if (!thing.zeropage && options.flag(CompilationFlag.LUnixRelocatableCode)) { - val w = get[Type]("word") val relocatable = UninitializedMemoryVariable(thing.name + ".addr", w, VariableAllocationMethod.Static, None, Set.empty, defaultVariableAlignment(options, 2), isVolatile = false) val addr = relocatable.toAddress addThing(relocatable, position) @@ -1445,9 +1459,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa addThing(RelativeVariable(thing.name + ".pointer.lo", addr, b, zeropage = false, None, isVolatile = false), position) } val rawaddr = thing.toAddress - addThing(ConstantThing(thing.name + ".rawaddr", rawaddr, get[Type]("pointer")), position) - addThing(ConstantThing(thing.name + ".rawaddr.hi", rawaddr.hiByte, get[Type]("byte")), position) - addThing(ConstantThing(thing.name + ".rawaddr.lo", rawaddr.loByte, get[Type]("byte")), position) + addThing(ConstantThing(thing.name + ".rawaddr", rawaddr, ptr), position) + addThing(ConstantThing(thing.name + ".rawaddr.hi", rawaddr.hiByte, b), position) + addThing(ConstantThing(thing.name + ".rawaddr.lo", rawaddr.loByte, b), position) thing match { case f: FunctionInMemory if f.canBePointedTo => val actualAddr = if (f.requiresTrampoline(options)) { @@ -1468,10 +1482,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa } } else { val addr = thing.toAddress - addThing(ConstantThing(thing.name + ".addr", addr, get[Type]("pointer")), position) + addThing(ConstantThing(thing.name + ".addr", addr, ptr), position) addThing(ConstantThing(thing.name + ".addr.hi", addr.hiByte, b), position) addThing(ConstantThing(thing.name + ".addr.lo", addr.loByte, b), position) - addThing(ConstantThing(thing.name + ".rawaddr", addr, get[Type]("pointer")), position) + addThing(ConstantThing(thing.name + ".rawaddr", addr, ptr), position) addThing(ConstantThing(thing.name + ".rawaddr.hi", addr.hiByte, b), position) addThing(ConstantThing(thing.name + ".rawaddr.lo", addr.loByte, b), position) targetType.foreach { tt => @@ -2175,6 +2189,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa Subvariable(".hiword", 0, w), Subvariable(".hiword.lo", 1, b), Subvariable(".hiword.hi", 0, b), + Subvariable(".lo", 2, b), Subvariable(".b0", 2, b), Subvariable(".b1", 1, b), Subvariable(".b2", 0, b) @@ -2185,6 +2200,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa Subvariable(".hiword", 1, w), Subvariable(".hiword.lo", 1, b), Subvariable(".hiword.hi", 2, b), + Subvariable(".lo", 0, b), Subvariable(".b0", 0, b), Subvariable(".b1", 1, b), Subvariable(".b2", 2, b)) @@ -2195,6 +2211,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa Subvariable(".loword.hi", 2, b), Subvariable(".hiword.lo", 1, b), Subvariable(".hiword.hi", 0, b), + Subvariable(".lo", 3, b), Subvariable(".b0", 3, b), Subvariable(".b1", 2, b), Subvariable(".b2", 1, b), @@ -2206,6 +2223,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa Subvariable(".loword.hi", 1, b), Subvariable(".hiword.lo", 2, b), Subvariable(".hiword.hi", 3, b), + Subvariable(".lo", 0, b), Subvariable(".b0", 0, b), Subvariable(".b1", 1, b), Subvariable(".b2", 2, b), diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index 512ad746..77bec253 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -655,6 +655,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program unimportantLabelMap += s"segment.$segment.heapstart" -> (defaultBank -> allocator.heapStart) unimportantLabelMap += s"segment.$segment.length" -> (defaultBank -> (allocator.endBefore - allocator.startAt)) unimportantLabelMap += s"segment.$segment.bank" -> (defaultBank -> platform.bankNumbers(segment)) + unimportantLabelMap += s"segment.$segment.fill" -> (defaultBank -> platform.bankFill(segment)) } env = rootEnv.allThings diff --git a/src/test/scala/millfork/test/SegmentSuite.scala b/src/test/scala/millfork/test/SegmentSuite.scala index ce1be0dc..7990ffd8 100644 --- a/src/test/scala/millfork/test/SegmentSuite.scala +++ b/src/test/scala/millfork/test/SegmentSuite.scala @@ -19,6 +19,8 @@ class SegmentSuite extends FunSuite with Matchers { | } | byte a5 | byte output @$c000 + | volatile byte output2 @$c001 + | volatile byte output3 @$c002 | void main() { | output = 0 | if a1.addr.hi & $e0 == $80 { output += 1 } @@ -26,10 +28,14 @@ class SegmentSuite extends FunSuite with Matchers { | if a3.addr.hi & $e0 == $80 { output += 1 } | if a4.addr.hi & $e0 == $a0 { output += 1 } | if a5.addr.hi & $e0 == $00 { output += 1 } + | output2 = a1.segment.bank ^ main.segment.bank + | output2 ^= segment.second.bank ^ segment.default.bank + | output3 = lo(main.segment.start) | } """.stripMargin EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(source) { m => m.readByte(0xc000) should equal(source.count(_ == '+')) + m.readByte(0xc001) should equal(0) } } }