Code Monkey home page Code Monkey logo

karols / millfork Goto Github PK

View Code? Open in Web Editor NEW
245.0 245.0 18.0 3.92 MB

Millfork: a middle-level programming language targeting 6502- and Z80-based microcomputers and home consoles

Home Page: https://karols.github.io/millfork/

License: GNU General Public License v3.0

Scala 93.35% Java 0.07% JavaScript 6.58%
6502 atari800 commodore-64 famicom intel8080 microcomputers millfork motorola-6809 nes programming-language retrocomputing z80 zxspectrum

millfork's People

Contributors

agg23 avatar ambez05 avatar bsutherland avatar freddyoffenga avatar garydos avatar jettmonstersgoboom avatar karols avatar kobrasadetin avatar mookiexl avatar nippur72 avatar retrac0 avatar zbyti avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

millfork's Issues

Can I change nullchar value somehow?

On Atari in atasciiscr encoding 'space' is encoded as char(0), and therefore all functions operating on null-terminated strings are unusable for this encoding.
It would be nice, to be able to change it somehow.

Interrupt functions don't properly restore accumulator and index registers

As the title states, there's a problem with interrupt functions in that they don't restore the registers
even though they do store them at the beginning of the function.

Take the compiled on_nmi function found in nes_routines.mfk for example, the registers are stored onto the stack, but never restored, simply pulled off the stack and thrown away:

on_nmi:
	PHA
	TXA
	PHA
	TYA
	PHA
	CLD
	LDA __reg
	PHA
	LDA $0017
	PHA
	LDA $0018
	PHA
	LDA $0019
	PHA
	JSR nmi
	PLA
	STA $0019
	PLA
	STA $0018
	PLA
	STA $0017
	PLA
	STA __reg
	PLA
	PLA
	PLA
	RTI

Array declaration - request for improvement

06e5da4

It would be nice to have:

const word dlAddr = $3000
const word lms1Addr = $4000
const word lms2Addr = $4060
const word lms3Addr = $40c0

array(byte) dl @ dlAddr = [
  $70,$70,$70,
  $52,lms1Addr,
  $52,lms2Addr,
  $52,lms3Addr,
  $41,dlAddr
]

// or

array(byte) dl @ dlAddr = [
  $70,$70,$70,
  $52,lms1Addr.lo,lms1Addr.hi,
  $52,lms2Addr.lo,lms2Addr.hi,
  $52,lms3Addr.lo,lms3Addr.hi,
  $41,dlAddr.lo,dlAddr.hi
]

Assignment operations on pointer accessors are too complex

Attempting to pair pointer accessors with assignment operations generates a complexity error.

byte constant
constant = $50
word value
value = $850

pointer.word value_pointer
value_pointer = value.pointer

...

value_pointer[0] += constant

Results in:

Too complex left-hand-side expression
    value_pointer[0] += constant

However, the equivalent expression, value_pointer[0] = value_pointer[0] + constant, compiles and runs just fine.

Macro issues with word

It appears macros cannot properly consume multi-byte values in a manner that preserves their type information.

Exception in thread "main" millfork.env.UndefinedIdentifierException: TypedThing `value.hi` is not defined
	at millfork.env.Environment.$anonfun$get$1(Environment.scala:328)
	at scala.Option.fold(Option.scala:251)
	at millfork.env.Environment.get(Environment.scala:330)
	at millfork.env.Environment.$anonfun$get$2(Environment.scala:330)
	at scala.Option.fold(Option.scala:251)
	at millfork.env.Environment.get(Environment.scala:330)
	at millfork.env.Environment.$anonfun$get$2(Environment.scala:330)
	at scala.Option.fold(Option.scala:251)
	at millfork.env.Environment.get(Environment.scala:330)
	at millfork.compiler.AbstractExpressionCompiler$.getExpressionTypeImpl(AbstractExpressionCompiler.scala:319)
	at millfork.compiler.AbstractExpressionCompiler$.getExpressionType(AbstractExpressionCompiler.scala:217)
	at millfork.compiler.AbstractExpressionCompiler$.checkAssignmentTypeAndGetSourceType(AbstractExpressionCompiler.scala:554)
	at millfork.compiler.mos.MosExpressionCompiler$.compileAssignment(MosExpressionCompiler.scala:2088)
	at millfork.compiler.mos.MosStatementCompiler$.compile(MosStatementCompiler.scala:188)
	at millfork.compiler.AbstractStatementCompiler.$anonfun$compile$1(AbstractStatementCompiler.scala:14)
	at scala.collection.immutable.List.map(List.scala:286)
	at millfork.compiler.AbstractStatementCompiler.compile(AbstractStatementCompiler.scala:14)
	at millfork.compiler.mos.MosStatementCompiler$.compile(MosStatementCompiler.scala:197)
	at millfork.compiler.AbstractStatementCompiler.$anonfun$compile$1(AbstractStatementCompiler.scala:14)
	at scala.collection.immutable.List.map(List.scala:286)
	at millfork.compiler.AbstractStatementCompiler.compile(AbstractStatementCompiler.scala:14)
	at millfork.compiler.mos.MosCompiler$.compile(MosCompiler.scala:19)
	at millfork.output.AbstractAssembler.compileFunction(AbstractAssembler.scala:694)
	at millfork.output.AbstractAssembler.$anonfun$assemble$13(AbstractAssembler.scala:261)
	at millfork.output.AbstractAssembler.$anonfun$assemble$13$adapted(AbstractAssembler.scala:260)
	at scala.Option.foreach(Option.scala:407)
	at millfork.output.AbstractAssembler.$anonfun$assemble$12(AbstractAssembler.scala:260)
	at millfork.output.AbstractAssembler.$anonfun$assemble$12$adapted(AbstractAssembler.scala:259)
	at scala.collection.immutable.List.foreach(List.scala:392)
	at millfork.output.AbstractAssembler.assemble(AbstractAssembler.scala:259)
	at millfork.Main$.assembleForMos(Main.scala:276)
	at millfork.Main$.main(Main.scala:104)
	at millfork.Main.main(Main.scala)

Minimal repro:

byte output

word test = $0380

void main() {
    test_macro(test)
}

macro void test_macro(word value) {
    if value.hi > 0 {
        output = 1
    }
}

void irq() {

}

void nmi() {

}

Support Unicode encodings

It would be convenient if Millfork allowed string literals for UTF-8 and UTF-16. This might seem like an odd feature to have, but I've been working on supporting rendering Unicode text on the Commander X16, especially CJK text. UTF-8 would also make sense for platforms such as the Altair 8800 where encoding is mostly a matter of agreement between the software running on the computer and the terminal being used to access it.

Manual variable address assignment causes conflicts

I apologize for the many bugs I've filed in the past day. Yet here's another 😃

I was attempting to create a repro for strange array interactions, possibly occurring when you force array types to overlap (say you want a static allocated area for use, and you overlay an array of structs on top of that). However, in doing so, I believe I discovered an even more dangerous issue resulting from manual address assignment.

struct Sprite {
    byte size,
    byte x,
    byte y,
    byte z
}

array(byte) simpleArray [4] @ $0
array(Sprite) sprites [20] @ $4

const array values = [5, 6, 7, 8, 9, 10, 11, 12]

void main() {
    byte i
    simpleArray[0] = 1
    simpleArray[1] = 2
    simpleArray[2] = 3
    simpleArray[3] = 4

    i = 0

    while i < 7 {
        sprites[i].size = values[i]
        sprites[i].x = values[i + 1]
        sprites[i].y = values[i + 2]
        sprites[i].z = values[i + 3]

        i += 4
    }

    while(true) {}
}

void irq() {

}

void nmi() {

}

This was the code I was playing with for my original repro. When this is built and the label file is generated, we find that main$i was assigned in the middle of that memory range.

__rwdata_size = $0
main = $8000
.wh__00005 = $8017
.fp__00007 = $8063
.ew__00008 = $8069
.ew__00012 = $806c
on_reset = $806d
vwait1 = $8084
vwait2 = $8089
clean_byte = $8092
irq = $80bb
on_nmi = $80bc
on_irq = $80e3
values.array = $810a
__rwdata_init_end = $8112
segment.chrrom.bank = $0
main$i = $4
__zeropage_last = $17
__zeropage_end = $18
segment.default.bank = $ff
segment.default.heapstart = $200
segment.default.length = $600
segment.default.end = $7ff
segment.chrrom.end = $1fff
segment.chrrom.length = $2000
segment.prgrom.length = $7eee
segment.prgrom.heapstart = $8000
segment.prgrom.start = $8112
segment.prgrom.end = $ffff

>O2 Optimization discards inner boolean expression in macros

Repro:

bool output

byte input
byte y

void main() {
    bool result
    input = 13
    y = 12
    test(result, input)

    if (result) {
        output = true
    }
}

macro void test(bool returnVal, byte x) {
    returnVal = x >= y
    returnVal = returnVal && x <= y + 8
}

void irq() {

}

void nmi() {

}

Compiling with -O3 results in the following assembly:

main:
; 
;line:9:repro-macro-bool.mfk
;       y = 12
    LDA #$C
    STA y
; 
;line:8:repro-macro-bool.mfk
;       input = 13
    LDA #$D
    STA input
; 
;line:12:repro-macro-bool.mfk
;       if (result) {
    ; DISCARD_AF
    ; DISCARD_XF
    ; DISCARD_YF
    RTS
; 
;line

This does work in some capacity if you use byte everywhere, but it appears the optimizer is analyzing the provided values and hardcoding the results based on them (which is logical). I do believe there is more to this issue than just that, however, as I initially encountered this scenario with values that were not known at compile time.

VariableType `array` is not defined

i tried to compile the pong example for nes and i got this error message:
ERROR: (main.mfk:1259:29) VariableType array is not defined
segment(chrrom) const array graphics @ $0000 = file("tiles.chr", 0)
why does this happen?
i didn't do any changes to the file.

Implement KIL opcode for 6502

It can be very useful during the development process to halt current execution outside of your debugger, perhaps in cases of unrecoverable errors. While you can accomplish this with a spin lock, this is somewhat a less elegant solution. To this end, it would be nice if one of the undocumented 6502 KIL opcodes were implemented.

Cannot resolve label with >O2 optimization due to complex expression in return value

Creating a function that returns a complex expression (in this case specifically boolean) fails when using O3 or greater, due to optimizing out a label, but still requiring it elsewhere.

struct Entity {
    byte x,
    byte y
}

array(Entity) entities [2]

byte output

void main() {
    if test(0, 200) {
        output = 1
    }
}

inline bool test(byte i, byte y) {
    return y >= entities[i].y && y <= entities[i].y + 8
}

void irq() {

}

void nmi() {

}

Fails compiled with -fillegals -t nes_small -g -s -fsource-in-asm -O3

"Out of high memory" error when compiling for a8 platform

The following code is compiling for c64 platform but gives error when compiling for a8. The examples for a8 (rainbow and a8_os_test) compile properly.

const word size=8190;
const word sizepl=8192;

array flags[sizepl]

void main()
{
        byte iter;
	word i, prime, k, count;

        for iter,1,to,10
        {
                count = 0;
                for i,0,paralleluntil,sizepl
                {
                     flags[i] = 1
                }
                for i,0,to,size
                {
			if (flags[i]==1)
                        {
				prime = i*2 + 3;
				k = i + prime;
				while (k <= size) {
					flags[k] = 0;
					k += prime;
				}
				count+=1;
			}
		}
        }
}

gives:

C:\millfork-0.3.14>java -jar millfork.jar -t a8 sieve.mfk
FATAL: Out of high memory
Exception in thread "main" java.lang.AssertionError: Out of high memory
at millfork.error.ConsoleLogger.fatal(ConsoleLogger.scala:93)
at millfork.output.VariableAllocator.allocateBytes(VariableAllocator.scala:161)
at millfork.output.VariableAllocator.allocateBytes(VariableAllocator.scala:119)
at millfork.env.Environment.$anonfun$allocateVariables$7(Environment.scala:199)
at scala.collection.TraversableLike.$anonfun$flatMap$1(TraversableLike.scala:245)
at scala.collection.mutable.HashMap$$anon$2.$anonfun$foreach$3(HashMap.scala:158)
at scala.collection.mutable.HashTable.foreachEntry(HashTable.scala:237)
at scala.collection.mutable.HashTable.foreachEntry$(HashTable.scala:230)
at scala.collection.mutable.HashMap.foreachEntry(HashMap.scala:44)
at scala.collection.mutable.HashMap$$anon$2.foreach(HashMap.scala:158)
at scala.collection.TraversableLike.flatMap(TraversableLike.scala:245)
at scala.collection.TraversableLike.flatMap$(TraversableLike.scala:242)
at scala.collection.AbstractTraversable.flatMap(Traversable.scala:108)
at millfork.env.Environment.allocateVariables(Environment.scala:151)
at millfork.output.AbstractAssembler.assemble(AbstractAssembler.scala:571)
at millfork.Main$.assembleForMos(Main.scala:262)
at millfork.Main$.main(Main.scala:90)
at millfork.Main.main(Main.scala)

Macros do not cast to signed types

When an unsigned argument is provided to a signed macro parameter, it is expected that the argument would be treated as signed.

byte input @ $0
byte output @ $1

void main() {
    input = $FF
    
    test_signed_macro(input)

    while (true) {}
}

macro void test_signed_macro(sbyte value) {
    if value > 3 {
        output = 1
    }
}

void irq() {

}

void nmi() {

}

Current output = 1

However, if you cast inline, you get the expected output = 0

macro void test_signed_macro(sbyte value) {
    if sbyte(value) > 3 {
        output = 1
    }
}

More helpful error messages ?

Tried to build my alien rescue project for Gameboy and received this, I only get one warning
WARN: Shift by zero.
but I don't know where that is coming from as there are no line numbers or source output to show it ?

FATAL: What
Exception in thread "System-2" java.lang.AssertionError: What
at millfork.error.ConsoleLogger.fatal(ConsoleLogger.scala:88)
at millfork.output.AbstractAssembler.assemble(AbstractAssembler.scala:490)
at millfork.Main$.assembleForI80(Main.scala:293)
at millfork.Main$.main(Main.scala:83)
at millfork.Main.main(Main.scala)

VICE breakpoints

Since going from code to exact line in ASM is difficult, could you add a breakpoint system?
the Vice labels file isn't actually a labels fill it can run "all" vice monitor commands including
break $XXXX

so in my 64tass label system, I abuse the local label system and if I find anything that has BREAK in its name, I don't add it as a label but add the address as a break $XXXX

so my code might be

  • = $1000
    start
    lda #0
    sta $d020
    lda #4
    _BREAK
    sta $d021

this then makes a labels file as
al $1000 .start
break $1007

allowing me to easily add and remove breakpoints from my code ready for when it auto loads the prg.

Sadly this is probably a VICE exclusive feature.

Documentation for compiler architecture

I realize it would probably take a good deal of work, but it would be nice to have some documentation around the structure of the compiler and its execution flow. I would be interested in contributing to fix the bugs I've reported (big thanks for your prompt responses and fixes), but its difficult to understand the current setup without investing a great deal of energy.

Combining addition with shifts breaks assignment

Compiling the following code for 6502:

byte input
byte input_2
byte output

void main() {
  input = 3
  input_2 = 2

  output = (input + input_2) << 3

  while(true) {}
}

void nmi() {}

void irq() {}

Results in the following relevant assembly:

main:
    LDA #3
    STA input
    LDA #2
    STA input_2
    LDA input
    CLC
    ADC input_2
    ASL
    ASL
    ASL
.wh__00001:
.fp__00003:
    JMP .wh__00001

For some reason the assignment is omitted.

Output directory should be created if necessary

Attempt to build without previously creating the build directory:

java -jar millfork.jar main.mfk -o build/rom.nes -t nes_small
Exception in thread "main" java.nio.file.NoSuchFileException: build/rom.nes
        at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)
        at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
        at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116)
        at java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:219)
        at java.base/java.nio.file.spi.FileSystemProvider.newOutputStream(FileSystemProvider.java:478)
        at java.base/java.nio.file.Files.newOutputStream(Files.java:223)
        at java.base/java.nio.file.Files.write(Files.java:3488)
        at millfork.Main$.$anonfun$main$13(Main.scala:141)
        at scala.collection.immutable.Map$Map1.foreach(Map.scala:128)
        at millfork.Main$.main(Main.scala:125)
        at millfork.Main.main(Main.scala)

Instead, build/ should be created.

Complex boolean expressions silently fail

I was able to create a repro of complex boolean expressions failing on 6502, but it seems to be partially fixed in latest master. Now my repro appears to generate the correct code, but doing the same in my project fails.

const byte MAX_SIZE = 4

macro void is_room_within_bounds(byte x, byte y, byte width, byte height, bool output) {
  output = x < MAX_SIZE && y < MAX_SIZE && x + width < MAX_SIZE && y + height < MAX_SIZE
}

byte temp

bool in_bounds

void main() {
    byte x
    byte y
    byte width
    byte height

    temp = 2
    x = temp
    
    temp = 3
    y = temp

    temp = 2
    width = temp

    temp = 2
    height = temp

    is_room_within_bounds(x, y, width, height, in_bounds)

    while(true) {}
}


void nmi() {
}

void irq() {

}

I seem to remember there previously being a limitation on the complexity of boolean expressions. Have you removed (or raised) this limitation? I'll see if I can find a working repro...

Update build instructions

run sbt 'set test in assembly := {}' compile (sbt "set test in assembly := {}" compile on Windows) to compile the project

run sbt 'set test in assembly := {}' assembly (sbt "set test in assembly := {}" assembly on Windows) to build the

can you seperate the windows instructions out to a seperate section. eg

run sbt 'set test in assembly := {}' compile
run sbt 'set test in assembly := {}' assembly
Windows
sbt "set test in assembly := {}"
sbt "set test in assembly := {}"

the way github higlights these lines it's unclear. I accidentally cut the entire line and obviously it all went side-ways

Add optional mutable keyword

It would be nice to have the option of a const-by-default-like experience. I think this could be done in a backwards-compatible way by adding a mut keyword, and a warning that can be enabled optionally for when mutability is not declared.

problem with -t a8

java -jar millfork.jar --version
millfork version 0.3.18

java -jar millfork.jar -Xr -t a8 rainbow.mfk 

ERROR: (a8_os.mfk:23:12) `os_SEIOCB` has a non-constant value
       const byte os_SEIOCB  = 0*IOCBSZ      // ##rev2## screen editor IOCB index
                  ^
INFO:  Did you forget to import an appropriate module?
ERROR: (a8_os.mfk:23:27) The constant IOCBSZ is undefined
       const byte os_SEIOCB  = 0*IOCBSZ      // ##rev2## screen editor IOCB index
                                 ^
INFO:  Did you forget to import an appropriate module?
ERROR: (a8_os.mfk:24:12) `os_MAXIOC` has a non-constant value
       const byte os_MAXIOC  = 8*IOCBSZ      // first invalid IOCB index
                  ^
INFO:  Did you forget to import an appropriate module?
ERROR: (a8_os.mfk:24:27) The constant IOCBSZ is undefined
       const byte os_MAXIOC  = 8*IOCBSZ      // first invalid IOCB index
                                 ^
INFO:  Did you forget to import an appropriate module?
ERROR: (a8_os.mfk:681:12) Constant value 52224 too big for type byte
       const byte os_ICSORG = $CC00             // ##rev2## international character set origin
                  ^
ERROR: (a8_os.mfk:682:12) Constant value 57344 too big for type byte
       const byte os_DCSORG = $E000             // ##rev2## domestic character set origin
                  ^
ERROR: (a8_os.mfk:686:12) Constant value 58368 too big for type byte
       const byte os_EDITRV = $E400         // editor handler vector table
                  ^
ERROR: (a8_os.mfk:687:12) Constant value 58384 too big for type byte
       const byte os_SCRENV = $E410         // screen handler vector table
                  ^
ERROR: (a8_os.mfk:688:12) Constant value 58400 too big for type byte
       const byte os_KEYBDV = $E420         // keyboard handler vector table
                  ^
ERROR: (a8_os.mfk:689:12) Constant value 58416 too big for type byte
       const byte os_PRINTV = $E430         // printer handler vector table
                  ^
ERROR: (a8_os.mfk:690:12) Constant value 58432 too big for type byte
       const byte os_CASETV = $E440         // cassette handler vector table
                  ^
ERROR: (a8_os.mfk:694:12) Constant value 58448 too big for type byte
       const byte os_DISKIV = $E450         // vector to initialize DIO
                  ^
ERROR: (a8_os.mfk:695:12) Constant value 58451 too big for type byte
       const byte os_DSKINV = $E453         // vector to DIO
                  ^
ERROR: (a8_os.mfk:696:12) Constant value 58454 too big for type byte
       const byte os_CIOV   = $E456         // vector to CIO
                  ^
ERROR: (a8_os.mfk:697:12) Constant value 58457 too big for type byte
       const byte os_SIOV   = $E459         // vector to SIO
                  ^
ERROR: (a8_os.mfk:699:12) Constant value 58460 too big for type byte
       const byte os_SETVBV = $E45C         // vector to set VBLANK parameters
                  ^
ERROR: (a8_os.mfk:700:12) Constant value 58463 too big for type byte
       const byte os_SYSVBV = $E45F         // vector to process immediate VBLANK
                  ^
ERROR: (a8_os.mfk:701:12) Constant value 58466 too big for type byte
       const byte os_XITVBV = $E462         // vector to process deferred VBLANK
                  ^
ERROR: (a8_os.mfk:703:12) Constant value 58469 too big for type byte
       const byte os_SIOINV = $E465         // vector to initialize SIO
                  ^
ERROR: (a8_os.mfk:704:12) Constant value 58472 too big for type byte
       const byte os_SENDEV = $E468         // vector to enable SEND
                  ^
ERROR: (a8_os.mfk:705:12) Constant value 58475 too big for type byte
       const byte os_INTINV = $E46B         // vector to initialize interrupt handler
                  ^
ERROR: (a8_os.mfk:706:12) Constant value 58478 too big for type byte
       const byte os_CIOINV = $E46E         // vector to initialize CIO
                  ^
ERROR: (a8_os.mfk:708:12) Constant value 58481 too big for type byte
       const byte os_BLKBDV = $E471         // vector to power-up display
                  ^
ERROR: (a8_os.mfk:709:12) Constant value 58484 too big for type byte
       const byte os_WARMSV = $E474         // vector to warmstart
                  ^
ERROR: (a8_os.mfk:710:12) Constant value 58487 too big for type byte
       const byte os_COLDSV = $E477         // vector to coldstart
                  ^
ERROR: (a8_os.mfk:712:12) Constant value 58490 too big for type byte
       const byte os_RBLOKV = $E47A         // vector to read cassette block
                  ^
ERROR: (a8_os.mfk:713:12) Constant value 58493 too big for type byte
       const byte os_CSOPIV = $E47D         // vector to open cassette for input
                  ^
ERROR: (a8_os.mfk:715:12) Constant value 58496 too big for type byte
       const byte os_VCTABL = $E480         // RAM vector initial value table
                  ^
ERROR: (a8_os.mfk:716:12) Constant value 58496 too big for type byte
       const byte os_PUPDIV = $E480         // ##rev2## vector to power-up display
                  ^
ERROR: (a8_os.mfk:717:12) Constant value 58499 too big for type byte
       const byte os_SLFTSV = $E483         // ##rev2## vector to self-test
                  ^
ERROR: (a8_os.mfk:718:12) Constant value 58502 too big for type byte
       const byte os_PHENTV = $E486         // ##rev2## vector to enter peripheral handler
                  ^
ERROR: (a8_os.mfk:719:12) Constant value 58505 too big for type byte
       const byte os_PHUNLV = $E489         // ##rev2## vector to unlink peripheral handler
                  ^
ERROR: (a8_os.mfk:720:12) Constant value 58508 too big for type byte
       const byte os_PHINIV = $E48C         // ##rev2## vector to initialize peripheral handler
                  ^
ERROR: (a8_os.mfk:721:12) Constant value 58511 too big for type byte
       const byte os_GPDVV  = $E48F         // ##rev2## generic parallel device handler vector
                  ^
ERROR: (a8_os.mfk:725:12) Constant value 65530 too big for type byte
       const byte os_NMIVEC = $FFFA
                  ^
ERROR: (a8_os.mfk:726:12) Constant value 65532 too big for type byte
       const byte os_RESVEC = $FFFC
                  ^
ERROR: (a8_os.mfk:727:12) Constant value 65534 too big for type byte
       const byte os_IRQVEC = $FFFE
                  ^
ERROR: Codegen failed
FATAL: Build halted due to previous errors
Exception in thread "main" java.lang.AssertionError: Build halted due to previous errors
	at millfork.error.ConsoleLogger.fatal(ConsoleLogger.scala:94)
	at millfork.error.ConsoleLogger.assertNoErrors(ConsoleLogger.scala:109)
	at millfork.Main$.assembleForMos(Main.scala:296)
	at millfork.Main$.main(Main.scala:105)
	at millfork.Main.main(Main.scala)

for

OK, if constants
for i,1,to,0

WRONG, if variable
j=1
for i,j,to,0

FATAL: Value at index 0 is a Integer, not a Constant

Combining O3 or greater with -fillegals, along with some loop expression (perhaps continue), results in the following error:

FATAL: Value at index 0 is a Integer, not a Constant
Exception in thread "main" java.lang.AssertionError: Value at index 0 is a Integer, not a Constant
	at millfork.error.ConsoleLogger.fatal(ConsoleLogger.scala:93)
	at millfork.assembly.mos.opt.AssemblyMatchingContext.getImpl(RuleBasedAssemblyOptimization.scala:180)
	at millfork.assembly.mos.opt.AssemblyMatchingContext.get(RuleBasedAssemblyOptimization.scala:186)
	at millfork.assembly.mos.opt.UndocumentedOptimizations$.$anonfun$UseSbx$4(UndocumentedOptimizations.scala:137)
	at millfork.assembly.mos.opt.RuleBasedAssemblyOptimization.$anonfun$optimizeImpl$2(RuleBasedAssemblyOptimization.scala:70)
	at millfork.assembly.mos.opt.RuleBasedAssemblyOptimization.$anonfun$optimizeImpl$2$adapted(RuleBasedAssemblyOptimization.scala:59)
	at scala.collection.TraversableLike$WithFilter.$anonfun$foreach$1(TraversableLike.scala:877)
	at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)
	at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)
	at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)
	at scala.collection.TraversableLike$WithFilter.foreach(TraversableLike.scala:876)
	at millfork.assembly.mos.opt.RuleBasedAssemblyOptimization.optimizeImpl(RuleBasedAssemblyOptimization.scala:59)
	at millfork.assembly.mos.opt.RuleBasedAssemblyOptimization.optimizeImpl(RuleBasedAssemblyOptimization.scala:98)
	at millfork.assembly.mos.opt.RuleBasedAssemblyOptimization.optimizeImpl(RuleBasedAssemblyOptimization.scala:98)
	at millfork.assembly.mos.opt.RuleBasedAssemblyOptimization.optimize(RuleBasedAssemblyOptimization.scala:50)
	at millfork.output.AbstractAssembler.$anonfun$compileFunction$2(AbstractAssembler.scala:699)
	at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
	at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
	at scala.collection.immutable.List.foldLeft(List.scala:89)
	at millfork.output.AbstractAssembler.compileFunction(AbstractAssembler.scala:698)
	at millfork.output.AbstractAssembler.$anonfun$assemble$13(AbstractAssembler.scala:261)
	at millfork.output.AbstractAssembler.$anonfun$assemble$13$adapted(AbstractAssembler.scala:260)
	at scala.Option.foreach(Option.scala:407)
	at millfork.output.AbstractAssembler.$anonfun$assemble$12(AbstractAssembler.scala:260)
	at millfork.output.AbstractAssembler.$anonfun$assemble$12$adapted(AbstractAssembler.scala:259)
	at scala.collection.immutable.List.foreach(List.scala:392)
	at millfork.output.AbstractAssembler.assemble(AbstractAssembler.scala:259)
	at millfork.Main$.assembleForMos(Main.scala:276)
	at millfork.Main$.main(Main.scala:104)
	at millfork.Main.main(Main.scala)

Minimal repro:

array(byte) input [16] @ $300
const byte COUNT = 16

void main() {
    inner()
}

void inner() {
    byte j
    for j,0,until,COUNT {
        if input[j] == 0 {
            continue
        }
    }
}

void irq() {

}

void nmi() {

}

Built with java -jar millfork.jar repro-optimize.mfk -o build/rom.nes -fillegals -t nes_small -O3

Booleans in struct arrays are allocated, but assignment does not occur

It appears that when arrays of structs containing boolean values are created, the complier properly calculates the size of the struct, but when attempting to assign a value to that field, the assignment does not occur.

struct Sprite {
    byte size,
    byte size2,
    byte size3,
    byte size4,
    bool x1,
    bool x,
    bool y,
    bool z
}

byte foo @ $0
bool bar @ $1
array(Sprite) sprites [20] @ $2

void main() {
    foo = 111
    bar = true

    sprites[0].size = 123
    sprites[0].size2 = 222
    sprites[0].x1 = false
    sprites[0].x = true
    sprites[0].y = true
    sprites[0].z = true

    while(true) {}
}

void irq() {

}

void nmi() {

}
Address Value
0000 111
0001 1
0002 123
0003 222
0004 0
0005 0
0006 0
0007 0

While you probably shouldn't waste space in this manner instead of using bits instead, it's nice to use booleans for testing and rapid prototyping. Besides, there's no reason to expect it not to work.

Allow file length to be omitted

It would be nice if it were possible to specify the start offset but no length, implying that the entire file should be included to the end of file. This would be useful for .sid files, for example, which start with a header, then data to the end of file.

Optimisation suggestion

Given

#if CBM_64 || CBM_264
    set_bg_color(white)
#endif
#if CBM_64 || CBM_264 || ZX_SPECTRUM
    set_border(red)
#endif

which becomes

;line:135:c64_vic.mfk
    LDA #1
    STA $D021
;line:139:c64_vic.mfk
    LDA #2
    STA $D020

could be

LDX #1
STX $D021
INX
STX $D020

pointer/word argument can get clobbered by subsequent function calls.

As the title states, pointer parameters are not guaranteed to still be correct if you call another function with its own pointer/word parameter. The sample code below (which was extracted from a larger project) illustrates this problem. Compile it and you'll see that player gets overwritten after a nested series of function calls.

In the larger project that this example comes from, I was able to work around this bug by simply making the parameter into a global variable, thus ensuring that its value couldn't be overwritten. Another workaround I tried was first transferring the parameter variable to a static local variable, but that static local variable was overwritten as well. Maybe the compiler is optimizing memory usage incorrectly?

Tested on Windows using release 0.3.8.

//compile with -t c64
import c64_basic
import stdio

struct Box {
    byte x,
    byte y,
    byte width,
    byte height
}

struct Phys_Obj {
    Box pos,
    byte xfrac,
    byte yfrac,
    sbyte xvel,
    sbyte yvel,
    byte xaccel,
    byte yaccel,
    byte xspeed,
    bool on_ground,
    bool jumping,
    bool can_jump
}

Phys_Obj player1

inline pointer get_metatile_column(word metatiles_column) {
    //This is the function where the original player pointer
    //gets corrupted.
    return pointer(metatiles_column)
}

byte get_tile(byte column, byte row) {
    byte tile
    pointer metatiles_column

    metatiles_column = get_metatile_column($0000)
    tile = $00
    return tile
}

void check_background_collis(byte screenx, byte screeny, byte spritewidth, byte spriteheight) {
    byte new_column1
    byte new_column2
    byte new_row1
    byte new_row2
    byte i
    byte limit
    byte current_tile
    
    current_tile = get_tile(0,0)
}

void check_player_collis_and_update_player_loc(pointer.Phys_Obj player) {
    sbyte temp_vel
    byte old_playerx
    byte old_playerx_frac
    byte old_playery
    byte old_playery_frac
    
    old_playerx = player1.pos.x
    old_playery = player1.pos.y    
    old_playerx_frac = player1.xfrac
    old_playery_frac = player1.yfrac

    check_background_collis(player1.pos.x, player1.pos.y, player1.pos.width, player1.pos.height)
}

inline void game_logic(pointer.Phys_Obj player) {
    player->pos.x = 0
    putword_basic(player->pos.x)
    putchar(' ')
    
    //comment out this function call and the player
    //pointer should still point to the correct location
    check_player_collis_and_update_player_loc(player)
    //after the function call, player points to garbage
    
    //expected val: 0
    //if player no longer points to player1: garbage (47 on my system)
    putword_basic(player->pos.x)
}

void main() {
    pointer.Phys_Obj player
    player = pointer.Phys_Obj(player1.addr)
    game_logic(player)
}

Struct performance test

import stdio

#define USE_WORDS=0
array c64_screen[1000] @$400

struct game_object {
#if USE_WORDS == 1
	word xpos 
	word ypos 
	word xdirection 
	word ydirection 
#else 
	byte xpos 
	byte ypos 
	byte xdirection 
	byte ydirection 
#endif
	function.void.to.void ptr
}

array(game_object) objects[8]

#if USE_WORDS == 1
array(word) objects_xpos[8]
array(word) objects_ypos[8]
array(word) objects_xdir[8]
array(word) objects_ydir[8]
#else
array(byte) objects_xpos[8]
array(byte) objects_ypos[8]
array(byte) objects_xdir[8]
array(byte) objects_ydir[8]
#endif
array(function.void.to.void) objects_pointer[8]

const array hex_table=[$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$1,$2,$3,$4,$5,$6]
void put_bin(word offset,byte v) {
byte x 
	for x,0,until,8 {
		c64_screen[x]=$30 
		if v&(1<<(7-x)) !=0 {
			c64_screen[x]=$31
		}	
	}
}
void put_hex(word offset,byte value) {
	c64_screen[offset] = hex_table[value>>4]
	c64_screen[offset+1] = hex_table[value&$f]
}

void nullop(){
	vic_border+=1
}

asm byte cls() @$e544 extern

void main() {
byte s 
word size
pointer.game_object go
	cls()
#if USE_WORDS == 1
	putstrz("words"z)
#else 
	putstrz("bytes"z)
#endif
	new_line()
	new_line()
	putstrz("struct array with indexing"z)
	new_line()
	putstrz("objects[x].xpos"z)
	new_line()
	new_line()
	new_line()
	new_line()
	new_line()
	new_line()
	new_line()
	putstrz("struct array with pointer"z)
	new_line()
	putstrz("ptr = objects[x]"z)
	new_line()
	putstrz("ptr->xpos"z)
	new_line()
	new_line()
	new_line()
	new_line()
	putstrz("raw array"z)
	new_line()
	putstrz("objectsxpos[x]"z)

	for s,0,until,8 {
		objects[s].xpos = s<<3
		objects[s].ypos = s<<3
		objects[s].xdirection = 1
		objects[s].ydirection = 1
		objects[s].ptr = nullop.pointer
		objects_xpos[s] = s<<3
		objects_ypos[s] = s<<3
		objects_xdir[s] = 1
		objects_ydir[s] = 1
		objects_pointer[s] = nullop.pointer
	}


	asm {
		sei 
	}

	while true {		
		while vic_raster!=$40  { }
		vic_border=1 
		for s,0,until,8 {
			objects[s].xpos = objects[s].xpos + objects[s].xdirection
			objects[s].ypos = objects[s].ypos + objects[s].ydirection
			call(objects[s].ptr)
		}
		vic_border=0
		put_hex(32+(2*40),vic_raster-$40)

		while vic_raster!=$80  { }
		vic_border=3 
		for s,0,until,8 {
			go = objects[s].pointer	// can we add to GO ? is there a more efficient way here ?
			go->xpos = go->xpos + go->xdirection
			go->ypos = go->ypos + go->ydirection
			call(go->ptr)
		}
		vic_border=0
		put_hex(32+(400),vic_raster-$80)

		while vic_raster!=$b0  { }
		vic_border=2 
		for s,0,until,8 {
			objects_xpos[s] = objects_xpos[s] + objects_xdir[s]
			objects_ypos[s] = objects_ypos[s] + objects_ydir[s]
			call(objects_pointer[s])
		}

		vic_border=0
		put_hex(32+(640),vic_raster-$b0)
	}
}

as requested on twitter. a test of raw array's vs structs , setting USE_WORDS = 1 to use 16 bit numbers vs bytes

Problem compiling simple Hello world with milfork for X16 target on windows 10

I'm trying to compile a simple milfork hello world using the visual studio code plugin but getting :

ARGS: "D:\Dev\millfork-0.3.12\millfork.exe" "d:\Dev\millfork-0.3.12\hello.mfk" -o "d:\Dev\millfork-0.3.12\hello.prg" -fno-cmos-ops -t x16_experimental
WARN: JMP bug prevention should be enabled for non-CMOS architecture
ERROR: CMOS opcodes enabled for non-CMOS architecture
ERROR: Parse failed
FATAL: Build halted due to previous errors
Compile Error
Exception in thread "System-2" java.lang.AssertionError: Build halted due to previous errors
at millfork.error.ConsoleLogger.fatal(ConsoleLogger.scala:93)
at millfork.error.ConsoleLogger.assertNoErrors(ConsoleLogger.scala:108)
at millfork.parser.AbstractSourceLoadingQueue.run(AbstractSourceLoadingQueue.scala:72)
at millfork.Main$.assembleForMos(Main.scala:222)
at millfork.Main$.main(Main.scala:90)
at millfork.Main.main(Main.scala)

as you can see I added in -fno-cmos-ops as that is the only thing I could find that relates to the error message but it doesn't seem to make much difference.

Also tried for command line.

Build instructions ?

Could you post a tutorial on how to build Millfork ?
I'd love to get the latest goodies as there have been no new releases in a while :(
I've never used scala or sbt before, I did try stumbling around but have had no luck ?
likely not due to millfork, likely I don't know what i'm doing and finding weird errors with jdk versions etc ? version numbers of the software used to build would help in this situation too.

thanks

Performance drop. array(byte) Vs. array(bool)

When we use array(bool) instead of array(byte) in sieve 1899 benchmark we have performance drop.

java -jar millfork.jar --version
millfork version 0.3.18

java -jar $HOME/Programs/Millfork/millfork.jar -Xr -t a8 sieve1899.mfk
less is better
586 jiffies - array(byte)
680 jiffies - array(bool)

Millfork-sieve1899.zip

Adding support for more targets

The following targets should work after creating platform definitions and minimal libraries:

  • Acorn Atom
  • Acorn Electron
  • Altair 8800
  • Amstrad PCW (standalone disk)
  • Apple IIc and III
  • Atari 5200
  • Atari 7800
  • Bashkiria 2M
  • BBC Master
  • Commodore 65
  • Commodore CBM-II series
  • Dragon 32/64
  • Fujitsu FM-7
  • Galaksija
  • Jupiter Ace
  • Mega 65
  • MSX series (disk)
  • MSX2 series
  • NEC PC-8000 and PC-6000 series
  • NEC PC Engine/Turbografx-16
  • Nintendo GameBoy Color
  • Orao and Orao 64
  • Oric 1
  • SAM Coupé
  • Sega SG-1000 and SG-3000
  • Sega Master System
  • Sinclair ZX80
  • Sinclair ZX81
  • Sharp MZ series
  • Sharp X1
  • Texas Instruments calculators, especially TI-83
  • Vectrex
  • Vektor-06C
  • Watara Supervision

The following targets should also work, but will require compiler improvements before being usable:

Require 24-bit 65816 support:

  • Acorn Communicator
  • Apple IIgs
  • Nintendo SuperFamicom/SNES

The following targets have been implemented since the creation of this issue:

  • Armstrad CPC series
  • Atari Lynx
  • EasyFlash 3 cartridge for Commodore 64
  • MSX series (cartridge)
  • Nintendo GameBoy
  • Robotron Z1013
  • Tandy Color Computer
  • TRS-80 Model 1 and Model 3

adding bytes

When i'm trying to compile this:

word sum
void main() {
    sum = 246 + 18
}

i'm getting error: "The constant 264 is too big".
But why? sum is declared as word.

If one of operands is bigger than 255 or if casted to word, it recognizes result type corectly.
Is it expected behaviour?

interrupt asm function and Atari cycle sensitive DLI

asm function with RTI at the end it's not allowed (compiler error) without interrupt modifier but this modifier forced (incorrectly) PHR into my ASM code.

then I wrote:

interrupt void dli() {
  gtia_colpf2 = $de
  antic_wsync = 1
}

everything works but DLI interrupt it's time sensitive, because of that I want to write this in ASM and push on stack only those registers what I really used.

what I want to do is:

noinline asm void dli() {
 pha
 lda #$de
 sta gtia_colpf2
 sta antic_wsync
 pla
 rti
}

Support struct "inheritance" (extension)

C11 supports struct extension, such that:

struct Foo {
   byte foo1
}

struct Bar {
   byte bar1
}

struct Foo_bar {
   struct Foo
   struct Bar
}

Produces the final struct:

struct Foo_bar {
   byte foo1
   byte bar1
}

While not a make or break feature, this allows some convenience in allowing composition without nesting, which is already represented by the memory structure.

Oddities with for-continue

Currently, the documentation states that for loops are "a bit buggy". Have these issues been investigated/formally discovered, or are they more nebulous?

I am encountering a scenario of two nested for loops iterating over two separate arrays, each with their separate continue statements. The existence of the inner nested continue causes strange behavior with what I believe is an array index overflow. Once I began testing this, I narrowed it down to simply the existence of the continue, as the method does nothing else, and executes successfully if the continue does not exist.

Unfortunately I cannot seem to produce a standalone repro, but here is the relevant code that causes the error functionality (after I trimmed the irrelevant code):

void enemy_tick() {
    byte i
    for i,0,until,MAX_ENEMIES {
        if enemies[i].is_alive == 0 {
            continue
        }
        
        if player.x > enemies[i].x {
            enemies[i].velocity_x = enemies[i].velocity_x + MAX_ENEMY_VELOCITY_PER_TICK
        } else if player.x < enemies[i].x {
            enemies[i].velocity_x = enemies[i].velocity_x - MAX_ENEMY_VELOCITY_PER_TICK
        }

        if player.y > enemies[i].y {
            enemies[i].velocity_y = enemies[i].velocity_y + MAX_ENEMY_VELOCITY_PER_TICK
        } else if player.y < enemies[i].y {
            enemies[i].velocity_y = enemies[i].velocity_y - MAX_ENEMY_VELOCITY_PER_TICK
        }

        collide_player_projectiles(i)
    }
}

inline bool collide_player_projectiles(byte enemy) {
    byte j
    for j,0,until,MAX_PLAYER_PROJECTILES {
        if player_projectiles[j].is_alive == 0 {
            continue
        }

        return true        
    }

    return false
}

Non-constant array range bounds error with constants as array bounds

Trying to compile the .mfk file in the attached zip file:
constError.zip

gives the error below:

C:\programming\c64\miniMIKEgit>"C:\utilities\millfork-0.3.16\millfork.exe" -t c64 ""C:\programming\c64\miniMIKEgit\constError.mfk""
ERROR: (constError.mfk:17:1) Array levelData has non-constant length
array(byte) levelData[MAP_SIZE];
^
INFO: Did you forget to import an appropriate module?
FATAL: Stack overflow levelData
Exception in thread "System-2" java.lang.AssertionError: Stack overflow levelData

Here is the code from the attached file:

// room size in tiles (characters for now)
const byte ROOM_WIDTH      = 8;
const byte ROOM_HEIGHT     = 8;

const byte ROOM_SIZE       = ROOM_WIDTH * ROOM_HEIGHT;

// maximum room count in x & y directions
const byte LEVEL_SIZE_X      = 5;
const byte LEVEL_SIZE_Y      = 5;

// map size in tiles (characters for now)
const byte MAP_WIDTH       = ROOM_WIDTH  * LEVEL_SIZE_X;
const byte MAP_HEIGHT      = ROOM_HEIGHT * LEVEL_SIZE_Y;

const word MAP_SIZE        = MAP_WIDTH * MAP_HEIGHT;

array(byte) levelData[MAP_SIZE];

void main() {
  levelData[0] = 1;
}

negating value for sbyte type seems not working

For variable declared as:

sbyte val

this operation is allowed:

val = -1

but this:

val = -val

throws syntax error.

Workaround is obvious and works:

val = 0 - val

but IMHO good parser should allow that ;)

Global structs on left-hand side don't compile unless referred to elsewhere first

Try compiling the example below to see the bug in action, but basically the compiler complains that the members of a global variable aren't defined until you use the variable elsewhere in the program, after which the program compiles fine.
Tested on Windows using release 0.3.8.

//compile with -t c64
import c64_basic

struct bug {
    byte x
}

bug b

void foo (byte x) {
    byte y
    // b.x //this line prevents the bug
    // y = b.x //this line also prevents the bug
    b.x = x
}

void main() {
    foo(1)
    // putword_basic(b.x) //this line also prevents the bug
}

Cannot compile/include mfk file containing constants only.

I'm fiddling with millfork and atari dsplay lists, and I wanted to prepare some helper files with display list constants. But when file contains ONLY constants definitions, then compiler throws error during compilation, pointing out last line as an source of error.
There is a workaround to that - you only need to add at least one variable, or function definition, and file compiles fine.
Is it forbidden on purpose?

I've attached my definition file which fails. If you will uncomment function at end, it will compile without any issues.
a8_dlist.zip

Potential Support Params

So I'm looking to make a Tactics game for the Commodore 64 and Commodore 128, but I want to also be able to port it to the MSX2 and possibly other architectures (68K, 65816, x86). So this seems to be the best fit, however I need a full 512K GMod2 supported, and 512K MSX2 cart support. Is this system capable of handling such carts. With it me being able to have Cart resident code and code that is copied to RAM, possibly compressed with say Exomizer to get every last drop of space out of it. But then the 128 version might have the "ROM banks" execute from its extra RAM and it loads data from disk etc.

Simple variable memory assignment fails

It appears that basic scenarios don't actually assign memory addresses to variables. Compiling the following snippet will result in no variables being allocated, and therefore an entire PRG-ROM of JMP $8000 (as there's no variables to write to).

byte foo
byte bar

void main() {
    foo = 123
    bar = 222

    while(true) {}
}

void irq() {

}

void nmi() {

}

However, this scenario succeeds as soon as you assign both to an address manually (e.g. byte foo @ $0).

Assignment from dereferenced pointers can fail within loops on 6502

Full repro, designed for NES (-t nes_simple), but similarly fails on -t c64:

struct Sprite {
    byte y
}

array(Sprite) sprites [20]
Sprite test

void main() {
    byte i
    test = Sprite(1)
    pointer.Sprite test_pointer
    test_pointer = test.pointer

    sprites[0] = Sprite(5)
    pointer.Sprite current_sprite
    current_sprite = pointer.Sprite(sprites.pointer)
    for i,0,until,20 {
        current_sprite->y = test_pointer->y
    }
}

void irq() {

}

void nmi() {

}

Throws the error:

Exception in thread "main" scala.NotImplementedError: an implementation is missing
        at scala.Predef$.$qmark$qmark$qmark(Predef.scala:288)
        at millfork.compiler.mos.MosExpressionCompiler$.compileAssignment(MosExpressionCompiler.scala:2241)
        at millfork.compiler.mos.MosStatementCompiler$.compile(MosStatementCompiler.scala:186)
        at millfork.compiler.AbstractStatementCompiler.$anonfun$compile$1(AbstractStatementCompiler.scala:14)
        at scala.collection.immutable.List.map(List.scala:290)
        at millfork.compiler.AbstractStatementCompiler.compile(AbstractStatementCompiler.scala:14)
        at millfork.compiler.AbstractStatementCompiler.compileWhileStatement(AbstractStatementCompiler.scala:44)
        at millfork.compiler.mos.MosStatementCompiler$.compile(MosStatementCompiler.scala:298)
        at millfork.compiler.AbstractStatementCompiler.$anonfun$compile$1(AbstractStatementCompiler.scala:14)
        at scala.collection.immutable.List.map(List.scala:286)
        at millfork.compiler.AbstractStatementCompiler.compile(AbstractStatementCompiler.scala:14)
        at millfork.compiler.mos.MosCompiler$.compile(MosCompiler.scala:19)
        at millfork.output.AbstractAssembler.compileFunction(AbstractAssembler.scala:670)
        at millfork.output.AbstractAssembler.$anonfun$assemble$10(AbstractAssembler.scala:237)
        at millfork.output.AbstractAssembler.$anonfun$assemble$10$adapted(AbstractAssembler.scala:236)
        at scala.Option.foreach(Option.scala:407)
        at millfork.output.AbstractAssembler.$anonfun$assemble$9(AbstractAssembler.scala:236)
        at millfork.output.AbstractAssembler.$anonfun$assemble$9$adapted(AbstractAssembler.scala:235)
        at scala.collection.immutable.List.foreach(List.scala:392)
        at millfork.output.AbstractAssembler.assemble(AbstractAssembler.scala:235)
        at millfork.Main$.assembleForMos(Main.scala:262)
        at millfork.Main$.main(Main.scala:90)
        at millfork.Main.main(Main.scala)

This does successfully compile against Z80 targets.

Support logical NOT in boolean expressions

Currently, logical NOT (traditionally !) is not listed as a supported operator, and simple trial an error shows that it will not compile at the moment. This can be trivially substituted with value == false if used with an if, but is less trivial for boolean assignment.

bool foo
byte output @ $1

void main() {
    foo = false

    if !foo {
        output = 1
    }

    while(true) {}
}

void irq() {

}

void nmi() {

}
ERROR: (repro4.mfk:4:1) Failed to parse the module `repro4` in repro4.mfk
       void main() {
       ^
ERROR: (repro4.mfk:7:8) Syntax error
           if !foo {
              ^
ERROR: Parse failed
FATAL: Build halted due to previous errors
Exception in thread "main" java.lang.AssertionError: Build halted due to previous errors
	at millfork.error.ConsoleLogger.fatal(ConsoleLogger.scala:93)
	at millfork.error.ConsoleLogger.assertNoErrors(ConsoleLogger.scala:108)
	at millfork.parser.AbstractSourceLoadingQueue.run(AbstractSourceLoadingQueue.scala:72)
	at millfork.Main$.assembleForMos(Main.scala:222)
	at millfork.Main$.main(Main.scala:90)
	at millfork.Main.main(Main.scala)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.