1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-09-27 12:57:41 +00:00

Improve documentation

This commit is contained in:
Karol Stasiak 2019-10-24 15:09:11 +02:00
parent bb419d961f
commit 6fddf1cf0d
4 changed files with 85 additions and 23 deletions

View File

@ -131,8 +131,8 @@ Only used for 6502-based targets. Cannot be used together with `zp_pointers`.
A segment named `default` is always required. A segment named `default` is always required.
Default: `default`. In all options below, `NAME` refers to a segment name. Default: `default`. In all options below, `NAME` refers to a segment name.
* `default_code_segment` the default segment for code and initialized arrays. * `default_code_segment` the default segment for code and const arrays.
Note that the default segment for uninitialized arrays and variables is always `default`. Note that the default segment for writable arrays and variables is always `default`.
Default: `default` Default: `default`
* `ram_init_segment` the segment storing a copy of initial values for preinitialized writable arrays and variables. * `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. * `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. 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_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`. Only uninitialized variables are allowed between `segment_NAME_codeend` and `segment_NAME_end`.
Default: the same as `segment_NAME_end`. Default: the same as `segment_NAME_end`.

View File

@ -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 **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 current implementation calls it twice, but do not rely on this behaviour).
The `==` and `!=` operators also work for non-arithmetic types.
* `==`: equality * `==`: equality
`enum == enum` `enum == enum`
`byte == byte` `byte == byte`
`simple word == simple word` `simple word == simple word`
`word == constant` `word == constant`
`simple word == word` (zpreg)
`word == simple word` (zpreg)
`simple long == simple long` `simple long == simple long`
* `!=`: inequality * `!=`: inequality
@ -157,12 +161,15 @@ Note you cannot mix those operators, so `a <= b < c` is not valid.
`byte != byte` `byte != byte`
`simple word != simple word` `simple word != simple word`
`word != constant` `word != constant`
`simple word != word` (zpreg)
`word != simple word` (zpreg)
`simple long != simple long` `simple long != simple long`
* `>`, `<`, `<=`, `>=`: inequality * `>`, `<`, `<=`, `>=`: inequality
`byte > byte` `byte > byte`
`simple word > word` `simple word > simple word`
`word > simple word` `simple word > word` (zpreg)
`word > simple word` (zpreg)
`simple long > simple long` `simple long > simple long`
Currently, `>`, `<`, `<=`, `>=` operators perform signed comparison Currently, `>`, `<`, `<=`, `>=` operators perform signed comparison

View File

@ -2,10 +2,13 @@
# Types # Types
Millfork puts extra limitations on which types can be used in which contexts.
## Numeric types ## 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 * `byte` 1-byte value of undefined signedness, defaulting to unsigned
* `word` 2-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 * `long` 4-byte value of undefined signedness, defaulting to unsigned
(alias: `int32`) (alias: `int32`)
* `int40`, `int48`,... `int128` even larger types * `int40`, `int48`,... `int128` even larger unsigned types
* `sbyte` signed 1-byte value * `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`) * 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 ## Typed pointers
For every type `T`, there is a pointer type defined called `pointer.T`. For every type `T`, there is a pointer type defined called `pointer.T`.
Unlike raw pointers, they are not subject to arithmetic. 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. 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`.
If the type `T` is of size 2, you can index the pointer only with the constant 0.
You can create pointer values by suffixing `.pointer` to the name of a variable, function or array. 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: Examples:
pointer.t p 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.lo // equivalent to 'p.raw.lo'
p.hi // 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[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 // 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 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(A parameter)
B function_name() // if A is void B function_name() // if A is void
Examples: To call a pointed-to function, use `call`. Examples:
word i word i
function.void.to.word p1 = f1.pointer 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
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. * `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: * 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 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 `set_carry` | `clear_carry` | C | C | C | C | C
`set_zero` | `clear_zero` | Z | Z | Z | Z `set_zero` | `clear_zero` | Z | Z | Z | Z | Z
`set_overflow` | `clear_overflow` | V | P¹ | P/V | _n/a_² `set_overflow` | `clear_overflow` | V | V | P¹ | P/V | *n/a*²
`set_negative` | `clear_negative` | N | S | S | _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, 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. 8080 uses the same type names for compatibility.
@ -123,6 +143,8 @@ Examples:
bool f() = true bool f() = true
bool g(byte x) = x == 7 || x > 100
void do_thing(bool b) { void do_thing(bool b) {
if b { do_one_thing() } if b { do_one_thing() }
else { do_another_thing() } else { do_another_thing() }
@ -135,6 +157,9 @@ Examples:
#elseif ARCH_I80 #elseif ARCH_I80
SCF SCF
? RET ? RET
#elseif ARCH_6809
ORCC #1
? RTS
#else #else
#error #error
#endif #endif
@ -174,6 +199,14 @@ Assignment between numeric types and enumerations is not possible without an exp
a[0] // won't compile a[0] // won't compile
a[EB] // ok 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(<name>.count - 1)`. Plain enumerations have their variants equal to `byte(0)` to `byte(<name>.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. 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 } struct point { word x, word y }

View File

@ -39,6 +39,26 @@ class EnumSuite extends FunSuite with Matchers {
""".stripMargin){_=>} """.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") { test("Enum arrays") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)( EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)(
""" """