From 6fddf1cf0d294851023ca753e84fadd4f7197c99 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Thu, 24 Oct 2019 15:09:11 +0200 Subject: [PATCH] Improve documentation --- docs/api/custom-platform.md | 10 +-- docs/lang/operators.md | 11 +++- docs/lang/types.md | 67 +++++++++++++++----- src/test/scala/millfork/test/EnumSuite.scala | 20 ++++++ 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/docs/api/custom-platform.md b/docs/api/custom-platform.md index d1efdc7f..1b0e2883 100644 --- a/docs/api/custom-platform.md +++ b/docs/api/custom-platform.md @@ -131,8 +131,8 @@ Only used for 6502-based targets. Cannot be used together with `zp_pointers`. A segment named `default` is always required. Default: `default`. In all options below, `NAME` refers to a segment name. -* `default_code_segment` – the default segment for code and initialized arrays. -Note that the default segment for uninitialized arrays and variables is always `default`. +* `default_code_segment` – the default segment for code and const arrays. +Note that the default segment for writable arrays and variables is always `default`. Default: `default` * `ram_init_segment` – the segment storing a copy of initial values for preinitialized writable arrays and variables. @@ -141,11 +141,13 @@ Default: none. * `segment_NAME_start` – the first address used for automatic allocation in the segment. Note that on 6502-like targets, the `default` segment shouldn't start before $200, as the $0-$1FF range is reserved for the zeropage and the stack. -The `main` function will be placed as close to the beginning of its segment as possible, but not necessarily at `segment_NAME_start` +The first object defined in `segment_NAME_layout` (usually the `main` function) +will be placed as close to the beginning of its segment as possible, +but not necessarily at `segment_NAME_start` * `segment_NAME_end` – the last address in the segment -* `segment_NAME_codeend` – the last address in the segment for code and initialized arrays. +* `segment_NAME_codeend` – the last address in the segment for code and const arrays. Only uninitialized variables are allowed between `segment_NAME_codeend` and `segment_NAME_end`. Default: the same as `segment_NAME_end`. diff --git a/docs/lang/operators.md b/docs/lang/operators.md index 4cf2d713..23347ab0 100644 --- a/docs/lang/operators.md +++ b/docs/lang/operators.md @@ -145,11 +145,15 @@ Note you cannot mix those operators, so `a <= b < c` is not valid. **WARNING:** Currently in cases like `a < f() < b`, `f()` may be evaluated an undefined number of times (the current implementation calls it twice, but do not rely on this behaviour). +The `==` and `!=` operators also work for non-arithmetic types. + * `==`: equality `enum == enum` `byte == byte` `simple word == simple word` `word == constant` +`simple word == word` (zpreg) +`word == simple word` (zpreg) `simple long == simple long` * `!=`: inequality @@ -157,12 +161,15 @@ Note you cannot mix those operators, so `a <= b < c` is not valid. `byte != byte` `simple word != simple word` `word != constant` +`simple word != word` (zpreg) +`word != simple word` (zpreg) `simple long != simple long` * `>`, `<`, `<=`, `>=`: inequality `byte > byte` -`simple word > word` -`word > simple word` +`simple word > simple word` +`simple word > word` (zpreg) +`word > simple word` (zpreg) `simple long > simple long` Currently, `>`, `<`, `<=`, `>=` operators perform signed comparison diff --git a/docs/lang/types.md b/docs/lang/types.md index 71e9fca9..cb6dbb12 100644 --- a/docs/lang/types.md +++ b/docs/lang/types.md @@ -2,10 +2,13 @@ # Types -Millfork puts extra limitations on which types can be used in which contexts. - ## Numeric types +Millfork puts extra limitations on which types can be used in which contexts. +1-byte arithmetic types work in every context. +2-byte arithmetic types work in context that are not overly complicated. +3-byte and larger types have limited capabilities. + * `byte` – 1-byte value of undefined signedness, defaulting to unsigned * `word` – 2-byte value of undefined signedness, defaulting to unsigned @@ -17,7 +20,7 @@ Millfork puts extra limitations on which types can be used in which contexts. * `long` – 4-byte value of undefined signedness, defaulting to unsigned (alias: `int32`) -* `int40`, `int48`,... `int128` – even larger types +* `int40`, `int48`,... `int128` – even larger unsigned types * `sbyte` – signed 1-byte value @@ -47,26 +50,43 @@ Numeric types can be converted automatically: * from a type of defined signedness to a type of undefined signedness (`sbyte`→`byte`) +Numeric types can be also converted explicitly from a smaller to an equal or bigger size. +This is useful in situations like preventing overflow or underflow, +or forcing zero extension or sign extension: + + byte a + a = 30 + a * a // expression of type byte, equals 132 + word(a) * a // expression of type word, equals 900 + + word x + byte y + y = $80 + x = y // does zero extension and assigns value $0080 + x = sbyte(y) // does sign extension and assigns value $FF80 + ## Typed pointers For every type `T`, there is a pointer type defined called `pointer.T`. Unlike raw pointers, they are not subject to arithmetic. -If the type `T` is of size 1, you can index the pointer like a raw pointer. - -If the type `T` is of size 2, you can index the pointer only with the constant 0. +You can index the pointer like a raw pointer or an array. +An expression like `p[n]` accesses the `n`th object in an array of consecutive objects pointed to by `p`. You can create pointer values by suffixing `.pointer` to the name of a variable, function or array. +You can replace C-style pointer arithmetic by combining indexing and `.pointer`: C `p+5`, Millfork `p[5].pointer`. + Examples: pointer.t p - p.pointer // expression of type pointer, pointing to the same location in memory as 'p' + p = t1.pointer // assigning a pointer + p.raw // expression of type pointer, pointing to the same location in memory as 'p' p.lo // equivalent to 'p.raw.lo' p.hi // equivalent to 'p.raw.lo' p[0] // valid only if the type 't' is of size 1 or 2, accesses the pointed element - p[i] // valid only if the type 't' is of size 1, equivalent to 't(p.raw[i])' + p[i] // accessing the ith element; if 'sizeof(t) == 1', then equivalent to 't(p.raw[i])' p->x // valid only if the type 't' has a field called 'x', accesses the field 'x' of the pointed element p->x.y[0]->z[0][6] // you can stack it @@ -85,7 +105,7 @@ there is a pointer type defined called `function.A.to.B`, which represents funct B function_name(A parameter) B function_name() // if A is void -Examples: +To call a pointed-to function, use `call`. Examples: word i function.void.to.word p1 = f1.pointer @@ -101,18 +121,18 @@ The value of the pointer `f.pointer` may not be the same as the value of the fun ## Boolean types -Boolean types can be used as conditions. They have two possible values, `true` and `false`, although +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. * several boolean types based on the CPU flags that may be used only as a return type for a function written in assembly: - true if flag set | true if flag clear | 6502 flag | 8080 flag | Z80 flag | LR35902 flag - -----------------|--------------------|-----------|-----------|----------|------------- - `set_carry` | `clear_carry` | C | C | C | C - `set_zero` | `clear_zero` | Z | Z | Z | Z - `set_overflow` | `clear_overflow` | V | P¹ | P/V | _n/a_² - `set_negative` | `clear_negative` | N | S | S | _n/a_² + true if flag set | true if flag clear | 6502 flag | 6809 flag | 8080 flag | Z80 flag | LR35902 flag + -----------------|--------------------|-----------|-----------|-----------|----------|------------- + `set_carry` | `clear_carry` | C | C | C | C | C + `set_zero` | `clear_zero` | Z | Z | Z | Z | Z + `set_overflow` | `clear_overflow` | V | V | P¹ | P/V | *n/a*² + `set_negative` | `clear_negative` | N | N | S | S | *n/a*² 1\. 8080 does not have a dedicated overflow flag, so since Z80 reuses the P flag for overflow, 8080 uses the same type names for compatibility. @@ -123,6 +143,8 @@ Examples: bool f() = true + bool g(byte x) = x == 7 || x > 100 + void do_thing(bool b) { if b { do_one_thing() } else { do_another_thing() } @@ -135,6 +157,9 @@ Examples: #elseif ARCH_I80 SCF ? RET + #elseif ARCH_6809 + ORCC #1 + ? RTS #else #error #endif @@ -174,6 +199,14 @@ Assignment between numeric types and enumerations is not possible without an exp a[0] // won't compile a[EB] // ok + enum X {} // enum with no variants + enum Y { // enum with renumberedvariants + YA = 5 + YB // YB is internally represented as 6 + } + array a2[X] // won't compile + array a2[Y] // won't compile + Plain enumerations have their variants equal to `byte(0)` to `byte(.count - 1)`. @@ -190,7 +223,7 @@ A struct is represented in memory as a contiguous area of variables laid out one Struct can have a maximum size of 255 bytes. Larger structs are not supported. -You can access a field of a struct with the dot: +You can access a field of a struct with a dot: struct point { word x, word y } diff --git a/src/test/scala/millfork/test/EnumSuite.scala b/src/test/scala/millfork/test/EnumSuite.scala index 37d09604..01ddc642 100644 --- a/src/test/scala/millfork/test/EnumSuite.scala +++ b/src/test/scala/millfork/test/EnumSuite.scala @@ -39,6 +39,26 @@ class EnumSuite extends FunSuite with Matchers { """.stripMargin){_=>} } + test("Enum renumber test") { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)( + """ + | enum ugly { + | u0, u1 + | u7 = 7, u8, + | u9 + | } + | ugly output0 @$c000 + | ugly output1 @$c001 + | void main () { + | output0 = u1 + | output1 = u9 + | } + """.stripMargin){ m => + m.readByte(0xc000) should equal(1) + m.readByte(0xc001) should equal(9) + } + } + test("Enum arrays") { EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)( """