From 03412cacba95acdabb52f723c5cbdbf1fa82fe75 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 12 Oct 2024 22:30:18 +0200 Subject: [PATCH 1/8] added examples/cx16/balloonflight.p8 --- compiler/test/TestCompilerOnExamples.kt | 1 + docs/source/todo.rst | 2 +- examples/c64/balloonflight.p8 | 14 +- examples/cx16/balloonflight.p8 | 252 ++++++++++++++++++++ scripts/cx16images.py | 298 ++++++++++++++++++++++++ 5 files changed, 562 insertions(+), 5 deletions(-) create mode 100644 examples/cx16/balloonflight.p8 create mode 100644 scripts/cx16images.py diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 54f32daa6..3ced4659a 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -116,6 +116,7 @@ class TestCompilerOnExamplesCx16: FunSpec({ "amiga", "audioroutines", "automatons", + "balloonflight", "bdmusic", "bobs", "bubbleuniverse", diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 2037337c9..e9fd9dbf7 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -11,7 +11,7 @@ Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ - remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly) -- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. +- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy. - should we add a cleararray builtin function that can efficiently set every element in the array to the given value - improve detection that a variable is not read before being written so that initializing it to zero can be omitted (only happens now if a vardecl is immediately followed by a for loop for instance) diff --git a/examples/c64/balloonflight.p8 b/examples/c64/balloonflight.p8 index 08767250b..3c299752d 100644 --- a/examples/c64/balloonflight.p8 +++ b/examples/c64/balloonflight.p8 @@ -1,8 +1,9 @@ %import syslib %import textio %import math -%zeropage basicsafe +; C64 version of a balloon sprites flying over a mountain landscape. +; There is also a X16 version of this in the examples. main { @@ -37,11 +38,16 @@ main { upwards = false } else { ; determine new height for next mountain - target_height = 9 + math.rnd() % 15 - if upwards + ubyte old_height = target_height + if upwards { mountain = 233 - else + while target_height >= old_height + target_height = 9 + math.rnd() % 15 + } else { mountain = 223 + while target_height <= old_height + target_height = 9 + math.rnd() % 15 + } } while not do_char_scroll { diff --git a/examples/cx16/balloonflight.p8 b/examples/cx16/balloonflight.p8 new file mode 100644 index 000000000..3e5f9a270 --- /dev/null +++ b/examples/cx16/balloonflight.p8 @@ -0,0 +1,252 @@ +%import syslib +%import textio +%import sprites +%import palette +%import math + +; X16 version of a balloon sprites flying over a mountain landscape. +; There is also a C64 version of this in the examples. + +main { + + sub start() { + ubyte target_height = txt.DEFAULT_HEIGHT - 10 + ubyte active_height = txt.DEFAULT_HEIGHT + bool upwards = true + ubyte draw_column = txt.DEFAULT_WIDTH + word moon_x = 640 + word balloon_y = 120 + + ; clear the screen (including all the tiles outside of the visible area) + cx16.vaddr(txt.VERA_TEXTMATRIX_BANK, txt.VERA_TEXTMATRIX_ADDR, 0, 1) + repeat 128 * txt.DEFAULT_HEIGHT { + cx16.VERA_DATA0 = sc:' ' + cx16.VERA_DATA0 = $00 + } + + ; activate balloon and moon sprites + sprites.init(1, 0, $0000, sprites.SIZE_32, sprites.SIZE_64, sprites.COLORS_16, 1) + sprites.init(2, 0, $0400, sprites.SIZE_32, sprites.SIZE_32, sprites.COLORS_16, 2) + spritedata.copy_to_vram() + + ; Scroll! + ; Unlike the C64 version, there is no need to copy the whole text matrix 1 character to the left + ; every 8 pixels. The X16 has a much larger soft scroll register and the displayed tiles wrap around. + + repeat { + sys.waitvsync() + cx16.VERA_L1_HSCROLL ++ + + if cx16.VERA_L1_HSCROLL & 7 == 0 { + + ; set balloon pos + if math.rnd() & 1 != 0 + balloon_y++ + else + balloon_y-- + sprites.pos(1, 100, balloon_y) + + ; set moon pos + moon_x-- + if moon_x < -64 + moon_x = 640 + sprites.pos(2, moon_x, 20) + + ; update slope height + ubyte mountain = 223 ; slope upwards + if active_height < target_height { + active_height++ + upwards = true + } else if active_height > target_height { + mountain = 233 ; slope downwards + active_height-- + upwards = false + } else { + ; determine new height for next mountain + ubyte old_height = target_height + if upwards { + mountain = 233 + while target_height >= old_height + target_height = 28 + (math.rnd() & 31) + } else { + mountain = 223 + while target_height <= old_height + target_height = 28 + (math.rnd() & 31) + } + } + + ; draw new mountain etc. + draw_column++ + ubyte yy + for yy in 0 to active_height-1 { + txt.setcc(draw_column, yy, 32, 2) ; clear top of screen + } + txt.setcc(draw_column, active_height, mountain, 8) ; mountain edge + for yy in active_height+1 to txt.DEFAULT_HEIGHT-1 { + txt.setcc(draw_column, yy, 160, 8) ; draw filled mountain + } + + ubyte clutter = math.rnd() + if clutter > 100 { + ; draw a star + txt.setcc(draw_column, clutter % (active_height-1), sc:'.', math.rnd()) + } + + if clutter > 200 { + ; draw a tree + ubyte tree = sc:'↑' + ubyte treecolor = 5 + if clutter & %00010000 != 0 + tree = sc:'♣' + else if clutter & %00100000 != 0 + tree = sc:'♠' + if math.rnd() > 130 + treecolor = 13 + txt.setcc(draw_column, active_height, tree, treecolor) + } + + if clutter > 235 { + ; draw a camel + txt.setcc(draw_column, active_height, sc:'π', 9) + } + } + } + } +} + +spritedata { + sub copy_to_vram() { + cx16.vaddr(0, $0000, 0, 1) + for cx16.r0 in 0 to 32*64/2-1 + cx16.VERA_DATA0 = @(&balloonsprite + cx16.r0) + + cx16.vaddr(0, $0400, 0, 1) + for cx16.r0 in 0 to 32*32/2-1 + cx16.VERA_DATA0 = @(&moonsprite + cx16.r0) + + for cx16.r1L in 0 to 15 { + palette.set_color(cx16.r1L + 16, balloon_pallette[cx16.r1L]) + palette.set_color(cx16.r1L + 32, moon_pallette[cx16.r1L]) + } + } + + + uword[] balloon_pallette = [ + $f0f, $312, $603, $125, + $717, $721, $332, $a22, + $268, $d31, $764, $488, + $d71, $997, $ba7, $eb3 + ] + + uword[] moon_pallette = [ + $f0f, $444, $444, $555, + $555, $555, $555, $666, + $777, $777, $888, $888, + $999, $aaa, $bbb, $ccc + ] + + balloonsprite: + %asm {{ + .byte $00, $00, $00, $00, $00, $00, $00, $00, $60, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $05, $99, $92, $55, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $05, $ac, $fc, $cf, $c9, $95, $e3, $25, $11, $40, $00, $00, $00 + .byte $00, $00, $08, $3c, $ff, $cf, $ff, $c9, $9c, $c8, $37, $21, $33, $00, $00, $00 + .byte $00, $00, $6b, $af, $ec, $ff, $ff, $9c, $9c, $fa, $33, $97, $23, $30, $00, $00 + .byte $00, $08, $bd, $ef, $fc, $ff, $fc, $99, $99, $fc, $33, $27, $72, $33, $30, $00 + .byte $00, $0b, $be, $ff, $cf, $ef, $fc, $9c, $99, $cf, $23, $32, $77, $13, $30, $00 + .byte $00, $6b, $ee, $ff, $9f, $ef, $fc, $c9, $97, $cf, $d3, $32, $29, $73, $31, $00 + .byte $00, $f8, $fe, $fc, $ff, $ef, $f9, $9c, $99, $ff, $d8, $31, $27, $72, $33, $00 + .byte $3b, $fd, $ee, $fc, $ff, $ef, $f9, $9c, $99, $fe, $d3, $32, $24, $72, $33, $40 + .byte $8b, $bd, $fe, $fc, $ff, $ef, $c7, $c7, $97, $ce, $d8, $33, $22, $72, $33, $30 + .byte $b8, $bd, $ee, $d7, $db, $b8, $2d, $88, $32, $2b, $83, $33, $33, $33, $33, $10 + .byte $18, $b8, $bb, $b8, $be, $bb, $3b, $b8, $38, $bb, $b8, $83, $33, $33, $33, $34 + .byte $1b, $8b, $bb, $8b, $eb, $83, $3b, $b8, $33, $bb, $88, $33, $33, $33, $33, $33 + .byte $8b, $b8, $8b, $88, $be, $88, $38, $88, $38, $8b, $b8, $33, $33, $33, $33, $33 + .byte $8b, $88, $88, $8b, $b8, $83, $38, $88, $38, $8b, $88, $33, $33, $33, $33, $33 + .byte $88, $83, $88, $88, $88, $83, $38, $83, $38, $8b, $83, $33, $33, $33, $33, $33 + .byte $3b, $b8, $88, $88, $bb, $83, $38, $83, $38, $bb, $83, $33, $33, $33, $33, $33 + .byte $6b, $bb, $88, $33, $bb, $b3, $38, $33, $33, $8b, $83, $33, $33, $33, $33, $30 + .byte $1b, $b3, $73, $37, $78, $83, $27, $92, $42, $75, $83, $11, $33, $33, $33, $30 + .byte $0c, $ff, $79, $97, $9f, $f9, $79, $92, $42, $79, $72, $12, $22, $11, $13, $10 + .byte $04, $9c, $c9, $97, $9f, $fc, $79, $97, $44, $c7, $72, $12, $12, $12, $22, $00 + .byte $00, $9c, $c9, $99, $59, $c9, $97, $92, $42, $7c, $21, $21, $22, $12, $21, $00 + .byte $00, $59, $cc, $99, $59, $9c, $79, $97, $22, $75, $21, $41, $22, $14, $10, $00 + .byte $00, $19, $cc, $99, $99, $9c, $97, $94, $22, $97, $21, $12, $11, $22, $10, $00 + .byte $00, $01, $cc, $c9, $95, $99, $97, $77, $47, $92, $11, $22, $12, $21, $00, $00 + .byte $00, $00, $79, $cc, $97, $7c, $c7, $99, $47, $72, $22, $11, $12, $14, $00, $00 + .byte $00, $00, $07, $cc, $99, $7c, $fc, $99, $42, $91, $14, $21, $22, $00, $00, $00 + .byte $00, $00, $01, $7c, $c9, $97, $cc, $79, $27, $72, $21, $12, $10, $00, $00, $00 + .byte $00, $00, $00, $1a, $c5, $95, $fc, $77, $44, $22, $21, $21, $00, $00, $00, $00 + .byte $00, $00, $00, $04, $a7, $5c, $fe, $f7, $9c, $55, $52, $20, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $01, $57, $fe, $f9, $9c, $c5, $21, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $01, $5e, $c9, $95, $11, $10, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $12, $ff, $f2, $10, $00, $60, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $01, $ff, $f1, $10, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $01, $12, $11, $00, $0a, $40, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $06, $11, $11, $00, $06, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $15, $15, $10, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $5e, $ee, $ee, $e5, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $01, $7d, $de, $dd, $d2, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $05, $cc, $55, $55, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $05, $cc, $cc, $55, $51, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $05, $cc, $cc, $55, $55, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $05, $5c, $cc, $51, $51, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $05, $cc, $c5, $56, $54, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $05, $55, $55, $15, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00 + }} + + moonsprite: + %asm {{ + .byte $00, $00, $00, $00, $00, $00, $78, $88, $87, $50, $00, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $ba, $99, $9a, $a9, $88, $76, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $b9, $99, $77, $67, $79, $78, $89, $93, $00, $00, $00, $00 + .byte $00, $00, $00, $76, $69, $ca, $bc, $3b, $99, $78, $66, $99, $60, $00, $00, $00 + .byte $00, $00, $04, $46, $cb, $64, $67, $77, $cb, $bb, $97, $99, $96, $00, $00, $00 + .byte $00, $00, $62, $39, $93, $33, $36, $67, $ac, $cc, $79, $99, $98, $60, $00, $00 + .byte $00, $06, $23, $47, $63, $43, $46, $79, $9b, $c6, $97, $96, $aa, $86, $00, $00 + .byte $00, $06, $34, $36, $63, $43, $67, $37, $a9, $64, $46, $87, $ba, $98, $30, $00 + .byte $00, $62, $46, $47, $76, $63, $66, $b7, $c9, $73, $46, $3a, $bc, $c9, $60, $00 + .byte $00, $13, $39, $73, $33, $74, $36, $99, $b9, $34, $43, $67, $cb, $87, $44, $00 + .byte $06, $43, $36, $37, $96, $76, $76, $3b, $d7, $34, $63, $48, $9a, $b9, $22, $00 + .byte $03, $22, $63, $83, $79, $78, $99, $9a, $77, $77, $96, $33, $62, $9c, $22, $10 + .byte $a3, $32, $36, $77, $bb, $ca, $a7, $63, $63, $97, $92, $33, $22, $9c, $22, $20 + .byte $a6, $32, $37, $a7, $7b, $eb, $a7, $79, $b7, $7c, $93, $23, $22, $38, $86, $10 + .byte $cb, $33, $37, $a7, $9b, $99, $33, $7a, $9d, $dc, $93, $33, $23, $36, $67, $10 + .byte $cb, $23, $33, $77, $79, $99, $67, $76, $8e, $ee, $b7, $33, $63, $43, $22, $10 + .byte $99, $33, $23, $36, $37, $39, $79, $ca, $bd, $dd, $dd, $a4, $a9, $62, $22, $20 + .byte $b6, $72, $73, $37, $a7, $99, $7d, $ce, $de, $dd, $df, $97, $ca, $42, $22, $20 + .byte $bb, $93, $33, $33, $b3, $6b, $37, $d9, $dd, $ed, $df, $c9, $bc, $92, $26, $30 + .byte $0d, $d7, $39, $33, $73, $77, $77, $9b, $cd, $de, $ed, $ec, $96, $aa, $44, $10 + .byte $0c, $cb, $9a, $b3, $39, $67, $76, $bc, $dd, $ee, $dd, $fb, $76, $77, $32, $00 + .byte $0b, $dc, $cb, $33, $ca, $a7, $83, $dd, $cd, $dd, $dd, $dd, $a8, $a7, $a3, $00 + .byte $00, $de, $dc, $73, $76, $73, $37, $ac, $ed, $ed, $dd, $ec, $bc, $b9, $63, $00 + .byte $00, $bd, $ed, $b7, $b9, $c7, $99, $ee, $fd, $ec, $de, $ed, $cc, $cb, $70, $00 + .byte $00, $0c, $ce, $d9, $c7, $9e, $ef, $ff, $ff, $ed, $ed, $db, $ca, $db, $30, $00 + .byte $00, $0b, $9c, $c7, $cd, $de, $ef, $ff, $fe, $ed, $cc, $bc, $ba, $a7, $00, $00 + .byte $00, $00, $bd, $9d, $dd, $de, $ff, $ff, $fe, $ee, $ec, $cc, $c9, $60, $00, $00 + .byte $00, $00, $0b, $ca, $ac, $ee, $ff, $ff, $fe, $cc, $cd, $cd, $96, $00, $00, $00 + .byte $00, $00, $00, $ba, $9d, $cc, $fe, $ff, $fd, $cc, $cc, $97, $60, $00, $00, $00 + .byte $00, $00, $00, $00, $9c, $da, $dd, $dc, $cd, $db, $a9, $83, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $08, $aa, $bd, $dc, $ac, $a9, $96, $00, $00, $00, $00, $00 + .byte $00, $00, $00, $00, $00, $00, $59, $7b, $99, $60, $00, $00, $00, $00, $00, $00 + }} +} diff --git a/scripts/cx16images.py b/scripts/cx16images.py new file mode 100644 index 000000000..27d846524 --- /dev/null +++ b/scripts/cx16images.py @@ -0,0 +1,298 @@ +""" +Tools to convert bitmap images to an appropriate format for the Commander X16. +This means: indexed colors (palette), 12 bits color space (4 bits per channel, for a total of 4096 possible colors) +There are no restrictions on the size of the image. + +Written by Irmen de Jong (irmen@razorvine.net) - Code is in the Public Domain. + +Requirements: Pillow (pip install pillow) +""" + +from PIL import Image, PyAccess +from typing import TypeAlias + +RGBList: TypeAlias = list[tuple[int, int, int]] + +# the 256 default colors of the Commander X16's color palette in (r,g,b) format +default_colors = [] + +_colors="""000,fff,800,afe,c4c,0c5,00a,ee7,d85,640,f77,333,777,af6,08f,bbb +000,111,222,333,444,555,666,777,888,999,aaa,bbb,ccc,ddd,eee,fff +211,433,644,866,a88,c99,fbb,211,422,633,844,a55,c66,f77,200,411 +611,822,a22,c33,f33,200,400,600,800,a00,c00,f00,221,443,664,886 +aa8,cc9,feb,211,432,653,874,a95,cb6,fd7,210,431,651,862,a82,ca3 +fc3,210,430,640,860,a80,c90,fb0,121,343,564,786,9a8,bc9,dfb,121 +342,463,684,8a5,9c6,bf7,120,241,461,582,6a2,8c3,9f3,120,240,360 +480,5a0,6c0,7f0,121,343,465,686,8a8,9ca,bfc,121,242,364,485,5a6 +6c8,7f9,020,141,162,283,2a4,3c5,3f6,020,041,061,082,0a2,0c3,0f3 +122,344,466,688,8aa,9cc,bff,122,244,366,488,5aa,6cc,7ff,022,144 +166,288,2aa,3cc,3ff,022,044,066,088,0aa,0cc,0ff,112,334,456,668 +88a,9ac,bcf,112,224,346,458,56a,68c,79f,002,114,126,238,24a,35c +36f,002,014,016,028,02a,03c,03f,112,334,546,768,98a,b9c,dbf,112 +324,436,648,85a,96c,b7f,102,214,416,528,62a,83c,93f,102,204,306 +408,50a,60c,70f,212,434,646,868,a8a,c9c,fbe,211,423,635,847,a59 +c6b,f7d,201,413,615,826,a28,c3a,f3c,201,403,604,806,a08,c09,f0b""" + +for line in _colors.splitlines(): + for rgb in line.split(","): + r = int(rgb[0], 16) + g = int(rgb[1], 16) + b = int(rgb[2], 16) + default_colors.append((r, g, b)) + + +class BitmapImage: + def __init__(self, filename: str, image: Image = None) -> None: + """Just load the given bitmap image file (any format allowed).""" + if image is not None: + self.img = image + else: + self.img = Image.open(filename) + self.size = self.img.size + self.width, self.height = self.size + + def save(self, filename: str) -> None: + """Save the image to a new file, format based on the file extension.""" + self.img.save(filename) + + def get_image(self) -> Image: + """Gets access to a copy of the Pillow Image class that holds the loaded image""" + return self.img.copy() + + def crop(self, x, y, width, height) -> "BitmapImage": + """Returns a rectangle cropped from the original image""" + cropped = self.img.crop((x, y, x + width, y + height)) + return BitmapImage("", cropped) + + def has_palette(self) -> bool: + """Is it an indexed colors image?""" + return self.img.mode == "P" + + def get_palette(self) -> RGBList: + """Return the image's palette as a list of (r,g,b) tuples""" + return flat_palette_to_rgb(self.img.getpalette()) + + def get_vera_palette(self) -> bytes: + """ + Returns the image's palette as GB0R words (RGB in little-endian), suitable for the Vera palette registers. + The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value. + """ + return rgb_palette_to_vera(self.get_palette()) + + def show(self) -> None: + """Shows the image on the screen""" + if self.img.mode == "P": + self.img.convert("RGB").convert("P").show() + else: + self.img.show() + + def get_pixels_8bpp(self, x: int, y: int, width: int, height: int) -> bytearray: + """ + For 8 bpp (256 color) images: + Get a rectangle of pixel values from the image, returns the bytes as a flat array + """ + assert self.has_palette() + try: + access = PyAccess.new(self.img, readonly=True) + except AttributeError: + access = self.img + data = bytearray(width * height) + index = 0 + for py in range(y, y + height): + for px in range(x, x + width): + data[index] = access.getpixel((px, py)) + index += 1 + return data + + def get_all_pixels_8bpp(self) -> bytes: + """ + For 8 bpp (256 color) images: + Get all pixel values from the image, returns the bytes as a flat array + """ + assert self.has_palette() + return self.img.tobytes() + # try: + # access = PyAccess.new(self.img, readonly=True) + # except AttributeError: + # access = self.img + # data = bytearray(self.width * self.height) + # index = 0 + # for py in range(self.height): + # for px in range(self.width): + # data[index] = access.getpixel((px, py)) + # index += 1 + # return data + + def get_pixels_4bpp(self, x: int, y: int, width: int, height: int) -> bytearray: + """ + For 4 bpp (16 color) images: + Get a rectangle of pixel values from the image, returns the bytes as a flat array. + Every byte encodes 2 pixels (4+4 bits). + """ + assert self.has_palette() + try: + access = PyAccess.new(self.img, readonly=True) + except AttributeError: + access = self.img + data = bytearray(width // 2 * height) + index = 0 + for py in range(y, y + height): + for px in range(x, x + width, 2): + pix1 = access.getpixel((px, py)) + pix2 = access.getpixel((px + 1, py)) + data[index] = pix1 << 4 | pix2 + index += 1 + return data + + def get_all_pixels_4bpp(self) -> bytearray: + """ + For 4 bpp (16 color) images: + Get all pixel values from the image, returns the bytes as a flat array. + Every byte encodes 2 pixels (4+4 bits). + """ + assert self.has_palette() + try: + access = PyAccess.new(self.img, readonly=True) + except AttributeError: + access = self.img + data = bytearray(self.width // 2 * self.height) + index = 0 + for py in range(self.height): + for px in range(0, self.width, 2): + pix1 = access.getpixel((px, py)) + pix2 = access.getpixel((px + 1, py)) + data[index] = pix1 << 4 | pix2 + index += 1 + return data + + def quantize_to(self, palette_rgb12: RGBList, dither: Image.Dither = Image.Dither.FLOYDSTEINBERG) -> None: + """ + Convert the image to one with the supplied palette. + This palette must be in 12 bits colorspace (4 bits so 0-15 per channel) + The resulting image will have its palette extended to 8 bits per channel again. + If you want to display the image on the actual Commander X16, simply take the lower (or upper) 4 bits of every color channel. + Dithering is applied as given (default is Floyd-Steinberg). + """ + palette_image = Image.new("P", (1, 1)) + palette = [] + for r, g, b in palette_rgb12: + palette.append(r << 4 | r) + palette.append(g << 4 | g) + palette.append(b << 4 | b) + palette_image.putpalette(palette) + self.img = self.img.quantize(dither=dither, palette=palette_image) + + def quantize(self, bits_per_pixel: int, preserve_first_16_colors: bool, + dither: Image.Dither = Image.Dither.FLOYDSTEINBERG) -> None: + """ + Convert the image to one with indexed colors (12 bits colorspace palette extended back into 8 bits per channel). + If you want to display the image on the actual Commander X16, simply take the lower (or upper) 4 bits of every color channel. + There is support for either 8 or 4 bits per pixel (256 or 16 color modes). + Dithering is applied as given (default is Floyd-Steinberg). + """ + if bits_per_pixel == 8: + num_colors = 240 if preserve_first_16_colors else 256 + elif bits_per_pixel == 4: + num_colors = 16 + if preserve_first_16_colors: + return self.quantize_to(default_colors[:16]) + elif bits_per_pixel == 2: + assert preserve_first_16_colors==False, "bpp is too small for 16 default colors" + num_colors = 4 + elif bits_per_pixel == 1: + assert preserve_first_16_colors==False, "bpp is too small for 16 default colors" + num_colors = 2 + else: + raise ValueError("only 8,4,2,1 bpp supported") + image = self.img.convert("RGB") + palette_image = image.quantize(colors=num_colors, dither=Image.Dither.NONE, method=Image.Quantize.MAXCOVERAGE) + if len(palette_image.getpalette()) // 3 > num_colors: + palette_image = image.quantize(colors=num_colors - 1, dither=Image.Dither.NONE, method=Image.Quantize.MAXCOVERAGE) + palette_rgb = flat_palette_to_rgb(palette_image.getpalette()) + palette_rgb = list(reversed(sorted(set(palette_8to4(palette_rgb))))) + if preserve_first_16_colors: + palette_rgb = default_colors[:16] + palette_rgb + self.img = image + self.quantize_to(palette_rgb, dither) + + def constrain_size(self, hires: bool = False) -> None: + """ + If the image is larger than the lores or hires screen size, scale it down so that it fits. + If the image already fits, doesn't do anything. + """ + w, h = self.img.size + if hires and (w > 640 or h > 480): + self.img.thumbnail((640, 480)) + elif w > 320 or h > 240: + self.img.thumbnail((320, 240)) + self.size = self.img.size + self.width, self.height = self.size + + +# utility functions + +def channel_8to4(color: int) -> int: + """Accurate conversion of a single 8 bit color channel value to 4 bits""" + return (color * 15 + 135) >> 8 # see https://threadlocalmutex.com/?p=48 + + +def palette_8to4(palette_rgb: RGBList) -> RGBList: + """Accurate conversion of a 24 bits palette (8 bits per channel) to a 12 bits palette (4 bits per channel)""" + converted = [] + for ci in range(len(palette_rgb)): + r, g, b = palette_rgb[ci] + converted.append((channel_8to4(r), channel_8to4(g), channel_8to4(b))) + return converted + + +def reduce_colorspace(palette_rgb: RGBList) -> RGBList: + """ + Convert 24 bits color space (8 bits per channel) to 12 bits color space (4 bits per channel). + The resulting color values are still full 8 bits but their precision is reduced. + You can take either the upper or lower 4 bits of each channel byte to get the actual 4 bits precision. + """ + converted = [] + for r, g, b in palette_rgb: + r = channel_8to4(r) + g = channel_8to4(g) + b = channel_8to4(b) + converted.append((r << 4 | r, g << 4 | g, b << 4 | b)) + return converted + + +def flat_palette_to_rgb(palette: list[int]) -> RGBList: + """Converts the flat palette list usually obtained from Pillow images to a list of (r,g,b) tuples""" + return [(palette[i], palette[i + 1], palette[i + 2]) for i in range(0, len(palette), 3)] + + +def rgb_palette_to_flat(palette: RGBList) -> list[int]: + """Convert a palette of (r,g,b) tuples to a flat list that is usually used by Pillow images""" + result = [] + for r, g, b in palette: + result.append(r) + result.append(g) + result.append(b) + return result + + +def flat_palette_to_vera(palette: list[int]) -> bytearray: + """ + Convert a flat palette list usually obtained from Pillow images, to GB0R words (RGB in little-endian), suitable for Vera palette registers. + The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value. + """ + return rgb_palette_to_vera(flat_palette_to_rgb(palette)) + + +def rgb_palette_to_vera(palette_rgb: RGBList) -> bytearray: + """ + Convert a palette in (r,g,b) format to GB0R words (RGB in little-endian), suitable for Vera palette registers. + The palette must be in 12 bit color space already! Because this routine just takes the upper 4 bits of every channel value. + """ + data = bytearray() + for r, g, b in palette_rgb: + r = r >> 4 + g = g >> 4 + b = b >> 4 + data.append(g << 4 | b) + data.append(r) + return data From fa5479ee5fbe250a71c175f81876223a336ae69a Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 13 Oct 2024 04:30:46 +0200 Subject: [PATCH 2/8] fix ast printing of arrays with duplicate elements --- codeCore/src/prog8/code/ast/AstPrinter.kt | 11 ++++++++++- compilerAst/src/prog8/ast/AstToSourceTextConverter.kt | 8 ++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/codeCore/src/prog8/code/ast/AstPrinter.kt b/codeCore/src/prog8/code/ast/AstPrinter.kt index e96cc6945..47d47eff3 100644 --- a/codeCore/src/prog8/code/ast/AstPrinter.kt +++ b/codeCore/src/prog8/code/ast/AstPrinter.kt @@ -21,7 +21,16 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni else "&" } - is PtArray -> "array len=${node.children.size} ${type(node.type)}" + is PtArray -> { + val valuelist = node.children.map { + when (it) { + is PtNumber -> it.number.toString() + is PtIdentifier -> it.name + else -> "?" + } + }.joinToString(", ") + "array len=${node.children.size} ${type(node.type)} [ $valuelist ]" + } is PtArrayIndexer -> " ${type(node.type)} ${if(node.splitWords) "[splitwords]" else ""}" is PtBinaryExpression -> " ${node.operator} ${type(node.type)}" is PtBuiltinFunctionCall -> { diff --git a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt index 03c64a586..b164580a9 100644 --- a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt +++ b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt @@ -315,16 +315,16 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: } override fun visit(array: ArrayLiteral) { - outputListMembers(array.value.asSequence()) + outputListMembers(array.value) } - private fun outputListMembers(array: Sequence) { + private fun outputListMembers(array: Array) { var counter = 0 output("[") scopelevel++ - for (v in array) { + for ((idx, v) in array.withIndex()) { v.accept(this) - if (v !== array.last()) + if (idx != array.size-1) output(", ") counter++ if (counter > 16) { From 7a0eaf314861257bc412bc097b5a9fd043638bc8 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 13 Oct 2024 03:45:30 +0200 Subject: [PATCH 3/8] Remove array initialization by single value. New compiler and kotlin version. --- .idea/libraries/KotlinJavaRuntime.xml | 20 +-- .../optimizer/ConstantIdentifierReplacer.kt | 122 +++++------------- .../compiler/astprocessing/AstChecker.kt | 41 ++++-- compiler/test/ast/TestAstChecks.kt | 18 +-- compiler/test/codegeneration/TestVariables.kt | 28 ---- docs/source/programming.rst | 5 +- docs/source/syntaxreference.rst | 12 +- docs/source/todo.rst | 11 +- examples/cube3d-float.p8 | 6 +- examples/test.p8 | 79 +++++++----- gradle.properties | 4 +- 11 files changed, 139 insertions(+), 207 deletions(-) diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml index 98e799522..cf8a55908 100644 --- a/.idea/libraries/KotlinJavaRuntime.xml +++ b/.idea/libraries/KotlinJavaRuntime.xml @@ -1,23 +1,23 @@ - + - - + + - + - - + + - + - - + + - + \ No newline at end of file diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index b578073b6..2dbd2bf77 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -268,7 +268,6 @@ class VarConstantValueTypeAdjuster( // Replace all constant identifiers with their actual value, -// and the array var initializer values and sizes. // This is needed because further constant optimizations depend on those. internal class ConstantIdentifierReplacer( private val program: Program, @@ -421,104 +420,41 @@ internal class ConstantIdentifierReplacer( return null } - // convert the initializer range expression from a range or int, to an actual array. + val rangeExpr = decl.value as? RangeExpression ?: return null + + // convert the initializer range expression from a range, to an actual array literal. + val declArraySize = decl.arraysize?.constIndex() + val constRange = rangeExpr.toConstantIntegerRange() + if(constRange?.isEmpty()==true) { + if(constRange.first>constRange.last && constRange.step>=0) + errors.err("descending range with positive step", decl.value?.position!!) + else if(constRange.first { - val rangeExpr = decl.value as? RangeExpression - if(rangeExpr!=null) { - val constRange = rangeExpr.toConstantIntegerRange() - if(constRange?.isEmpty()==true) { - if(constRange.first>constRange.last && constRange.step>=0) - errors.err("descending range with positive step", decl.value?.position!!) - else if(constRange.first { - if(fillvalue !in 0..255) - errors.err("ubyte value overflow", numericLv.position) - } - DataType.ARRAY_B -> { - if(fillvalue !in -128..127) - errors.err("byte value overflow", numericLv.position) - } - DataType.ARRAY_UW -> { - if(fillvalue !in 0..65535) - errors.err("uword value overflow", numericLv.position) - } - DataType.ARRAY_W -> { - if(fillvalue !in -32768..32767) - errors.err("word value overflow", numericLv.position) - } - else -> {} - } - // create the array itself, filled with the fillvalue. - val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray() - return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position) - } - } - DataType.ARRAY_F -> { - val rangeExpr = decl.value as? RangeExpression - if(rangeExpr!=null) { - // convert the initializer range expression to an actual array of floats - val declArraySize = decl.arraysize?.constIndex() - if(declArraySize!=null && declArraySize!=rangeExpr.size()) - errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!) - val constRange = rangeExpr.toConstantIntegerRange() - if(constRange!=null) { - return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), - constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(), + if(declArraySize!=null && declArraySize!=rangeExpr.size()) + errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!) + if(constRange!=null) { + val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE) + return if(eltType in ByteDatatypes) { + ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), + constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(), + position = decl.value!!.position) + } else { + ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), + constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(), position = decl.value!!.position) } } - - val numericLv = decl.value as? NumericLiteral - val size = decl.arraysize?.constIndex() ?: return null - if(rangeExpr==null && numericLv!=null) { - // arraysize initializer is a single int, and we know the array size. - val fillvalue = numericLv.number - if (fillvalue < options.compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > options.compTarget.machine.FLOAT_MAX_POSITIVE) - errors.err("float value overflow", numericLv.position) - else { - val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray() - return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position) - } - } } - DataType.ARRAY_BOOL -> { - val numericLv = decl.value as? NumericLiteral - val size = decl.arraysize?.constIndex() ?: return null - if(numericLv!=null) { - // arraysize initializer is a single value, and we know the array size. - if(numericLv.type!=DataType.BOOL) { - errors.err("initializer value is not a boolean", numericLv.position) - return null - } - val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray() - return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position) + DataType.ARRAY_F -> { + if(declArraySize!=null && declArraySize!=rangeExpr.size()) + errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!) + if(constRange!=null) { + return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), + constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(), + position = decl.value!!.position) } } else -> return null diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index cf29e5563..463b2b465 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -716,12 +716,8 @@ internal class AstChecker(private val program: Program, if(decl.isArray && decl.arraysize==null) { if(decl.type== VarDeclType.MEMORY) err("memory mapped array must have a size specification") - if(decl.value==null) { - valueerr("array variable is missing a size specification or an initialization value") - return - } - if(decl.value is NumericLiteral) { - valueerr("unsized array declaration cannot use a single literal initialization value") + if(decl.value==null || decl.value is NumericLiteral) { + err("array variable is missing a size specification") return } if(decl.value is RangeExpression) @@ -802,6 +798,30 @@ internal class AstChecker(private val program: Program, // array length limits and constant lenghts if(decl.isArray) { + + if(decl.type!=VarDeclType.MEMORY) { + // memory-mapped arrays are initialized with their address, but any other array needs a range or array literal value. + + if (decl.value!=null && decl.value !is ArrayLiteral && decl.value !is RangeExpression) { + var suggestion: String? = null + val arraysize = decl.arraysize?.constIndex() + val numericvalue = decl.value?.constValue(program) + if (numericvalue != null && arraysize != null) { + when (numericvalue.type) { + in IntegerDatatypes -> suggestion = "[${numericvalue.number.toInt()}] * $arraysize" + DataType.FLOAT -> suggestion = "[${numericvalue.number}] * $arraysize" + DataType.BOOL -> suggestion = "[${numericvalue.asBooleanValue}] * $arraysize" + else -> {} + } + } + + if (suggestion != null) + valueerr("array initialization value must be a range value or an array literal (suggestion: use '$suggestion' here)") + else + valueerr("array initialization value must be a range value or an array literal") + } + } + val length = decl.arraysize?.constIndex() if(length==null) err("array length must be known at compile-time") @@ -1831,14 +1851,7 @@ internal class AstChecker(private val program: Program, DataType.UWORD -> sourceDatatype == DataType.UBYTE || sourceDatatype == DataType.UWORD DataType.FLOAT -> sourceDatatype in NumericDatatypes DataType.STR -> sourceDatatype == DataType.STR - else -> { - if(targetDatatype in ArrayDatatypes && sourceValue is ArrayLiteral) - true // assigning array literal to an array variable is allowed, size and type are checked elsewhere - else { - errors.err("cannot assign this value to variable of type $targetDatatype", position) - false - } - } + else -> targetDatatype in ArrayDatatypes && sourceValue is ArrayLiteral } if(result) diff --git a/compiler/test/ast/TestAstChecks.kt b/compiler/test/ast/TestAstChecks.kt index ba5f553d1..e2847134f 100644 --- a/compiler/test/ast/TestAstChecks.kt +++ b/compiler/test/ast/TestAstChecks.kt @@ -104,7 +104,7 @@ class TestAstChecks: FunSpec({ val text = """ main { sub start() { - const ubyte[5] a = 5 + const ubyte[5] a = [1,2,3,4,5] a[2]=42 } } @@ -133,22 +133,6 @@ class TestAstChecks: FunSpec({ errors.errors[0] shouldContain "indexing requires" } - test("array decl with expression as size can be initialized with a single value") { - val text = """ - main { - sub start() { - const ubyte n = 40 - const ubyte half = n / 2 - ubyte[half] @shared a = 5 - } - } - """ - val errors = ErrorReporterForTests(keepMessagesAfterReporting = true) - compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldNotBe null - errors.errors.size shouldBe 0 - errors.warnings.size shouldBe 0 - } - test("unicode in identifier names is working") { val text = """ %import floats diff --git a/compiler/test/codegeneration/TestVariables.kt b/compiler/test/codegeneration/TestVariables.kt index 4af007a5e..6c143cbad 100644 --- a/compiler/test/codegeneration/TestVariables.kt +++ b/compiler/test/codegeneration/TestVariables.kt @@ -113,32 +113,4 @@ class TestVariables: FunSpec({ errors.errors[0] shouldContain "value has incompatible type" errors.errors[1] shouldContain "value has incompatible type" } - - test("initialization of boolean array with single value") { - val text = """ - main { - sub start() { - bool[10] sieve0 = false - bool[10] sieve1 = true - sieve0[0] = true - sieve1[0] = true - } - } - """ - compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null - } - - test("initialization of boolean array with single value of wrong type fails") { - val text = """ - main { - sub start() { - bool[10] sieve2 = 42 - } - } - """ - val errors = ErrorReporterForTests() - compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null - errors.errors.size shouldBe 1 - errors.errors[0] shouldContain "initializer value is not a boolean" - } }) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index e2fdc0b05..15f831d9b 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -290,7 +290,7 @@ always have to be constants. Here are some examples of arrays:: byte[10] array ; array of 10 bytes, initially set to 0 byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value - ubyte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...] + ubyte[99] array = [255]*99 ; initialize array with 99 times 255 [255, 255, 255, 255, ...] byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199] str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to array of uwords) uword[] others = [names, array] ; array of pointers/addresses to other arrays @@ -306,7 +306,8 @@ always have to be constants. Here are some examples of arrays:: This means byte arrays should be <= 256 elements, word arrays <= 128 elements (256 if it's a split array - see below), and float arrays <= 51 elements. -You can write out an array initializer list over several lines if you want to improve readability. +Arrays can be initialized with a range expression or an array literal value. +You can write out such an initializer value over several lines if you want to improve readability. Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.) can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte`` diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 941504f30..12ae61f64 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -363,7 +363,7 @@ Various examples:: bool flag = true byte[] values = [11, 22, 33, 44, 55] byte[5] values ; array of 5 bytes, initially set to zero - byte[5] values = 255 ; initialize with five 255 bytes + byte[5] values = [255]*5 ; initialize with five 255 bytes word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage uword @requirezp zpaddr = $3000 ; we require this variable in zeropage @@ -403,8 +403,11 @@ type identifier type storage size example var declara implicitly terminated by a 0-byte =============== ======================= ================= ========================================= -**arrays:** you can split an array initializer list over several lines if you want. When an initialization -value is given, the array size in the declaration can be omitted. +**arrays:** +Arrays can be initialized with a range expression or an array literal value. +You can write out such an initializer value over several lines if you want to improve readability. +When an initialization value is given, you are allowed to omit the array size in the declaration, +because it can be inferred from the initialization value. **numbers:** unless prefixed for hex or binary as described below, all numbers are decimal numbers. There is no octal notation. @@ -542,9 +545,10 @@ memory at the given index (and allows index values of word size). See :ref:`poin String ^^^^^^ A string literal can occur with or without an encoding prefix (encoding followed by ':' followed by the string itself). +String length is limited to 255 characters. +You can use '+' and '*' to concatenate or repeat string fragments to build up a larger string literal. When this is omitted, the string is stored in the machine's default character encoding (which is PETSCII on the CBM machines). You can choose to store the string in other encodings such as ``sc`` (screencodes) or ``iso`` (iso-8859-15). -String length is limited to 255 characters. Here are examples of the various encodings: - ``"hello"`` a string translated into the default character encoding (PETSCII on the CBM machines) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index e9fd9dbf7..9a03d3f85 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,6 +1,12 @@ TODO ==== +- remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly) +- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy. +- should we add a cleararray builtin function that can efficiently set every element in the array to the given value + + + Improve register load order in subroutine call args assignments: in certain situations, the "wrong" order of evaluation of function call arguments is done which results in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!) @@ -9,11 +15,6 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ - -- remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly) -- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy. -- should we add a cleararray builtin function that can efficiently set every element in the array to the given value - - improve detection that a variable is not read before being written so that initializing it to zero can be omitted (only happens now if a vardecl is immediately followed by a for loop for instance) - Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions - Can we support signed % (remainder) somehow? diff --git a/examples/cube3d-float.p8 b/examples/cube3d-float.p8 index 071ca8285..641cdf96c 100644 --- a/examples/cube3d-float.p8 +++ b/examples/cube3d-float.p8 @@ -12,9 +12,9 @@ main { float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ] ; storage for rotated coordinates - float[len(xcoor)] rotatedx=0.0 - float[len(ycoor)] rotatedy=0.0 - float[len(zcoor)] rotatedz=-1.0 + float[len(xcoor)] rotatedx + float[len(ycoor)] rotatedy + float[len(zcoor)] rotatedz sub start() { float time=0.0 diff --git a/examples/test.p8 b/examples/test.p8 index af54f1ce6..0622fd138 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,42 +1,63 @@ -%import monogfx +%import floats %import textio -%import math %option no_sysinit %zeropage basicsafe - main { + str name = "xyz" * 3 + bool[3] boolarray = true + ubyte[3] bytearray = 52 + uword[3] wordarray = 5544 + float[3] floatarray = 123.45 + + ubyte[3] bytearray2 = 10 to 12 + uword[3] wordarray2 = 5540 to 5542 + float[3] floatarray2 = 123 to 125 + + bool[3] boolarray3 + ubyte[3] bytearray3 + uword[3] wordarray3 + float[3] floatarray3 + sub start() { - monogfx.lores() - demofill() - } - - sub demofill() { - const uword offsetx = 0 - const uword offsety = 0 - - monogfx.circle(offsetx+160, offsety+120, 110, true) - monogfx.rect(offsetx+180, offsety+5, 25, 190, true) - monogfx.line(offsetx+100, offsety+150, offsetx+240, offsety+10, true) - monogfx.line(offsetx+101, offsety+150, offsetx+241, offsety+10, true) - monogfx.rect(offsetx+150, offsety+130, 10, 100, true) - - sys.wait(30) - - cbm.SETTIM(0,0,0) - monogfx.fill(offsetx+100,offsety+100,true) - monogfx.fill(offsetx+100,offsety+100,false) - uword duration = cbm.RDTIM16() - sys.wait(30) - - monogfx.textmode() + txt.print(name) + txt.nl() + for cx16.r1L in 0 to 2 { + txt.print_bool(boolarray[cx16.r1L]) + txt.spc() + txt.print_ub(bytearray[cx16.r1L]) + txt.spc() + txt.print_uw(wordarray[cx16.r1L]) + txt.spc() + floats.print(floatarray[cx16.r1L]) + txt.nl() + } + txt.nl() txt.nl() - txt.print_uw(duration) - txt.print(" jiffies\n") - ; before optimizations: ~166 jiffies + for cx16.r1L in 0 to 2 { + txt.print_ub(bytearray2[cx16.r1L]) + txt.spc() + txt.print_uw(wordarray2[cx16.r1L]) + txt.spc() + floats.print(floatarray2[cx16.r1L]) + txt.nl() + } + txt.nl() + txt.nl() + for cx16.r1L in 0 to 2 { + txt.print_bool(boolarray3[cx16.r1L]) + txt.spc() + txt.print_ub(bytearray3[cx16.r1L]) + txt.spc() + txt.print_uw(wordarray3[cx16.r1L]) + txt.spc() + floats.print(floatarray3[cx16.r1L]) + txt.nl() + } + txt.nl() } } diff --git a/gradle.properties b/gradle.properties index 69cfb6a64..d51a58952 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,5 +4,5 @@ org.gradle.parallel=true org.gradle.daemon=true kotlin.code.style=official javaVersion=11 -kotlinVersion=2.0.20 -version=10.4.2 +kotlinVersion=2.0.21 +version=10.5-SNAPSHOT From 66829203d8625f74a4105aec30c2757fe74f9f5e Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 13 Oct 2024 04:20:57 +0200 Subject: [PATCH 4/8] New [x]*42 syntax to create array literals with repeated values (like "abc"*10 already exists for strings) Should be used in place of array initializer expressions that contain only a single numeric value to initialize the whole array with. That isn't supported anymore. --- .../optimizer/ConstantFoldingOptimizer.kt | 23 +++ .../astprocessing/LiteralsToAutoVars.kt | 24 +-- .../test/codegeneration/TestArrayThings.kt | 139 +++++++++++++++++- docs/source/todo.rst | 3 +- examples/test.p8 | 47 ++++-- 5 files changed, 207 insertions(+), 29 deletions(-) diff --git a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt index c3136b941..0b9a68164 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt @@ -2,6 +2,7 @@ package prog8.optimizer import prog8.ast.Node import prog8.ast.Program +import prog8.ast.base.FatalAstException import prog8.ast.expressions.* import prog8.ast.maySwapOperandOrder import prog8.ast.statements.* @@ -112,6 +113,28 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors: } } + if(expr.left.inferType(program).isArray) { + if (expr.operator=="*" && rightconst!=null) { + if (expr.left is ArrayLiteral) { + // concatenate array literal. + val part = expr.left as ArrayLiteral + if(part.value.isEmpty()) + errors.warn("resulting array has length zero", part.position) + val tmp = mutableListOf() + repeat(rightconst.number.toInt()) { + tmp += part.value + } + val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position) + return listOf(IAstModification.ReplaceNode(expr, newArray, parent)) + } + else { + val leftTarget = (expr.left as? IdentifierReference)?.targetVarDecl(program) + if(leftTarget!=null && leftTarget.origin==VarDeclOrigin.ARRAYLITERAL) + throw FatalAstException("shouldn't see an array literal converted to an autovar here") + } + } + } + if(expr.operator=="==" && rightconst!=null) { val leftExpr = expr.left as? BinaryExpression // only do this shuffling when the LHS is not a constant itself (otherwise problematic nested replacements) diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index 326e80d9f..26de5ee99 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -60,17 +60,19 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro return noModifications } if(arrayDt.isKnown) { - val parentAssign = parent as? Assignment - val targetDt = parentAssign?.target?.inferType(program) ?: arrayDt - // turn the array literal it into an identifier reference - val litval2 = array.cast(targetDt.getOr(DataType.UNDEFINED)) - if(litval2!=null) { - val vardecl2 = VarDecl.createAuto(litval2, targetDt.getOr(DataType.UNDEFINED) in SplitWordArrayTypes) - val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position) - return listOf( - IAstModification.ReplaceNode(array, identifier, parent), - IAstModification.InsertFirst(vardecl2, array.definingScope) - ) + if((array.parent as? BinaryExpression)?.operator!="*") { + val parentAssign = parent as? Assignment + val targetDt = parentAssign?.target?.inferType(program) ?: arrayDt + // turn the array literal it into an identifier reference + val litval2 = array.cast(targetDt.getOr(DataType.UNDEFINED)) + if (litval2 != null) { + val vardecl2 = VarDecl.createAuto(litval2, targetDt.getOr(DataType.UNDEFINED) in SplitWordArrayTypes) + val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position) + return listOf( + IAstModification.ReplaceNode(array, identifier, parent), + IAstModification.InsertFirst(vardecl2, array.definingScope) + ) + } } } } diff --git a/compiler/test/codegeneration/TestArrayThings.kt b/compiler/test/codegeneration/TestArrayThings.kt index 72099cc11..ae1500f06 100644 --- a/compiler/test/codegeneration/TestArrayThings.kt +++ b/compiler/test/codegeneration/TestArrayThings.kt @@ -4,7 +4,10 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain -import prog8.code.ast.PtBuiltinFunctionCall +import prog8.code.StStaticVariable +import prog8.code.SymbolTableMaker +import prog8.code.ast.* +import prog8.code.core.* import prog8.code.target.C64Target import prog8.code.target.VMTarget import prog8tests.helpers.ErrorReporterForTests @@ -403,5 +406,139 @@ main { (x.children[18] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" (x.children[19] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" } + + test("array and string initializer with multiplication") { + val src=""" +%option enable_floats + +main { + sub start() { + str name = "xyz" * 3 + bool[3] boolarray = [true] * 3 + ubyte[3] bytearray = [42] * 3 + uword[3] wordarray = [5555] * 3 + float[3] floatarray = [123.45] * 3 + } +}""" + val result = compileText(C64Target(), false, src, writeAssembly = true)!! + val x = result.codegenAst!!.entrypoint()!! + x.children.size shouldBe 6 + ((x.children[0] as PtVariable).value as PtString).value shouldBe "xyzxyzxyz" + val array1 = (x.children[1] as PtVariable).value as PtArray + val array2 = (x.children[2] as PtVariable).value as PtArray + val array3 = (x.children[3] as PtVariable).value as PtArray + val array4 = (x.children[4] as PtVariable).value as PtArray + array1.children.map { (it as PtBool).value } shouldBe listOf(true, true, true) + array2.children.map { (it as PtNumber).number } shouldBe listOf(42, 42, 42) + array3.children.map { (it as PtNumber).number } shouldBe listOf(5555, 5555, 5555) + array4.children.map { (it as PtNumber).number } shouldBe listOf(123.45, 123.45, 123.45) + } + + test("array initializer with range") { + val src=""" +%option enable_floats + +main { + sub start() { + ubyte[3] bytearray2 = 10 to 12 + uword[3] wordarray2 = 5000 to 5002 + float[3] floatarray2 = 100 to 102 + } +}""" + val result = compileText(C64Target(), false, src, writeAssembly = true)!! + val x = result.codegenAst!!.entrypoint()!! + x.children.size shouldBe 4 + val array1 = (x.children[0] as PtVariable).value as PtArray + val array2 = (x.children[1] as PtVariable).value as PtArray + val array3 = (x.children[2] as PtVariable).value as PtArray + array1.children.map { (it as PtNumber).number } shouldBe listOf(10, 11, 12) + array2.children.map { (it as PtNumber).number } shouldBe listOf(5000, 5001, 5002) + array3.children.map { (it as PtNumber).number } shouldBe listOf(100, 101, 102) + } + + + fun getTestOptions(): CompilationOptions { + val target = VMTarget() + return CompilationOptions( + OutputType.RAW, + CbmPrgLauncherType.NONE, + ZeropageType.DONTUSE, + zpReserved = emptyList(), + zpAllowed = CompilationOptions.AllZeropageAllowed, + floats = true, + noSysInit = false, + compTarget = target, + loadAddress = target.machine.PROGRAM_LOAD_ADDRESS + ) + } + + + test("array assignments with ranges and multiplications") { + val src=""" +%option enable_floats + +main { + sub start() { + bool[4] boolarray3 + ubyte[4] bytearray3 + uword[4] wordarray3 + float[4] floatarray3 + + boolarray3 = [true] *4 + bytearray3 = [42]*4 + wordarray3 = [999]*4 + wordarray3 = [&bytearray3]*4 + wordarray3 = [bytearray3]*4 + floatarray3 = [99.77]*4 + + bytearray3 = 10 to 13 + wordarray3 = 5000 to 5003 + floatarray3 = 100 to 103 + } +}""" + val ast = compileText(C64Target(), false, src, writeAssembly = true)!!.codegenAst!! + val x = ast.entrypoint()!! + x.children.size shouldBe 23 + val assign1value = (x.children[13] as PtBuiltinFunctionCall).args[1] + val assign2value = (x.children[14] as PtBuiltinFunctionCall).args[1] + val assign3value = (x.children[15] as PtBuiltinFunctionCall).args[1] + val assign4value = (x.children[16] as PtBuiltinFunctionCall).args[1] + val assign5value = (x.children[17] as PtBuiltinFunctionCall).args[1] + val assign6value = (x.children[18] as PtBuiltinFunctionCall).args[1] + val assign7value = (x.children[19] as PtBuiltinFunctionCall).args[1] + val assign8value = (x.children[20] as PtBuiltinFunctionCall).args[1] + val assign9value = (x.children[21] as PtBuiltinFunctionCall).args[1] + val options = getTestOptions() + val st = SymbolTableMaker(ast, options).make() + + val heapvar1 = st.lookup((assign1value as PtIdentifier).name) as StStaticVariable + val heapvar2 = st.lookup((assign2value as PtIdentifier).name) as StStaticVariable + val heapvar3 = st.lookup((assign3value as PtIdentifier).name) as StStaticVariable + val heapvar4 = st.lookup((assign4value as PtIdentifier).name) as StStaticVariable + val heapvar5 = st.lookup((assign5value as PtIdentifier).name) as StStaticVariable + val heapvar6 = st.lookup((assign6value as PtIdentifier).name) as StStaticVariable + val heapvar7 = st.lookup((assign7value as PtIdentifier).name) as StStaticVariable + val heapvar8 = st.lookup((assign8value as PtIdentifier).name) as StStaticVariable + val heapvar9 = st.lookup((assign9value as PtIdentifier).name) as StStaticVariable + heapvar1.length shouldBe 4 + heapvar2.length shouldBe 4 + heapvar3.length shouldBe 4 + heapvar4.length shouldBe 4 + heapvar5.length shouldBe 4 + heapvar6.length shouldBe 4 + heapvar7.length shouldBe 4 + heapvar8.length shouldBe 4 + heapvar9.length shouldBe 4 + heapvar1.dt shouldBe DataType.ARRAY_BOOL + heapvar2.dt shouldBe DataType.ARRAY_UB + heapvar3.dt shouldBe DataType.ARRAY_UW + heapvar4.dt shouldBe DataType.ARRAY_UW + heapvar5.dt shouldBe DataType.ARRAY_UW + heapvar6.dt shouldBe DataType.ARRAY_F + heapvar7.dt shouldBe DataType.ARRAY_UB + heapvar8.dt shouldBe DataType.ARRAY_UW + heapvar9.dt shouldBe DataType.ARRAY_F + } + }) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 9a03d3f85..7b274be0f 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,12 +1,11 @@ TODO ==== -- remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly) +- word arrays (after ast processing) should no longer contain identifiers, these should have been replaced by &identifier. - should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy. - should we add a cleararray builtin function that can efficiently set every element in the array to the given value - Improve register load order in subroutine call args assignments: in certain situations, the "wrong" order of evaluation of function call arguments is done which results in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!) diff --git a/examples/test.p8 b/examples/test.p8 index 0622fd138..d64713dbc 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -6,22 +6,13 @@ main { - str name = "xyz" * 3 - bool[3] boolarray = true - ubyte[3] bytearray = 52 - uword[3] wordarray = 5544 - float[3] floatarray = 123.45 + sub arrayinit_with_multiplier() { + str name = "xyz" * 3 + bool[3] boolarray = [true] * 3 + ubyte[3] bytearray = [42] * 3 + uword[3] wordarray = [5555] * 3 + float[3] floatarray = [123.45] * 3 - ubyte[3] bytearray2 = 10 to 12 - uword[3] wordarray2 = 5540 to 5542 - float[3] floatarray2 = 123 to 125 - - bool[3] boolarray3 - ubyte[3] bytearray3 - uword[3] wordarray3 - float[3] floatarray3 - - sub start() { txt.print(name) txt.nl() for cx16.r1L in 0 to 2 { @@ -36,6 +27,12 @@ main { } txt.nl() txt.nl() + } + + sub arrayinit_with_range() { + ubyte[3] bytearray2 = 10 to 12 + uword[3] wordarray2 = 5000 to 5002 + float[3] floatarray2 = 100 to 102 for cx16.r1L in 0 to 2 { txt.print_ub(bytearray2[cx16.r1L]) @@ -47,6 +44,20 @@ main { } txt.nl() txt.nl() + } + + sub arrayassign() { + bool[4] boolarray3 + ubyte[4] bytearray3 + uword[4] wordarray3 + float[4] floatarray3 + + boolarray3 = [true] *4 + bytearray3 = [42]*4 + wordarray3 = [999]*4 + wordarray3 = [&bytearray3]*4 + wordarray3 = [bytearray3]*4 + floatarray3 = [99.77]*4 for cx16.r1L in 0 to 2 { txt.print_bool(boolarray3[cx16.r1L]) @@ -60,4 +71,10 @@ main { } txt.nl() } + + sub start() { + arrayinit_with_multiplier() + arrayinit_with_range() + arrayassign() + } } From aef211e5f3ab7fa6d8aa5bd419f6e68e5e15a55c Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 13 Oct 2024 17:46:41 +0200 Subject: [PATCH 5/8] stricter array literal element type handling (number,bool,address-of). More consistent implicit address-of handling if array literals contain by-ref identifiers (such as subroutine names) --- codeCore/src/prog8/code/SymbolTableMaker.kt | 1 - codeCore/src/prog8/code/ast/AstExpressions.kt | 1 + codeCore/src/prog8/code/ast/AstPrinter.kt | 1 + .../src/prog8/codegen/cpu6502/AsmGen.kt | 1 - .../optimizer/ConstantFoldingOptimizer.kt | 2 +- .../compiler/astprocessing/AstChecker.kt | 14 +--- .../astprocessing/IntermediateAstMaker.kt | 7 +- .../astprocessing/LiteralsToAutoVars.kt | 2 + .../compiler/astprocessing/TypecastsAdder.kt | 20 +++++ .../test/codegeneration/TestArrayThings.kt | 41 +++++++++ docs/source/todo.rst | 2 +- examples/test.p8 | 83 ++----------------- 12 files changed, 81 insertions(+), 94 deletions(-) diff --git a/codeCore/src/prog8/code/SymbolTableMaker.kt b/codeCore/src/prog8/code/SymbolTableMaker.kt index 6462276ff..1b3b44a7a 100644 --- a/codeCore/src/prog8/code/SymbolTableMaker.kt +++ b/codeCore/src/prog8/code/SymbolTableMaker.kt @@ -131,7 +131,6 @@ class SymbolTableMaker(private val program: PtProgram, private val options: Comp TODO("address-of array element $it in initial array value") StArrayElement(null, it.identifier.name, null) } - is PtIdentifier -> StArrayElement(null, it.name, null) is PtNumber -> StArrayElement(it.number, null, null) is PtBool -> StArrayElement(null, null, it.value) else -> throw AssemblyError("invalid array element $it") diff --git a/codeCore/src/prog8/code/ast/AstExpressions.kt b/codeCore/src/prog8/code/ast/AstExpressions.kt index f5ee093a5..b07c59d66 100644 --- a/codeCore/src/prog8/code/ast/AstExpressions.kt +++ b/codeCore/src/prog8/code/ast/AstExpressions.kt @@ -163,6 +163,7 @@ class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(el class PtArray(type: DataType, position: Position): PtExpression(type, position) { + // children are always one of 3 types: PtBool, PtNumber or PtAddressOf. override fun hashCode(): Int = Objects.hash(children, type) override fun equals(other: Any?): Boolean { if(other==null || other !is PtArray) diff --git a/codeCore/src/prog8/code/ast/AstPrinter.kt b/codeCore/src/prog8/code/ast/AstPrinter.kt index 47d47eff3..aee95db21 100644 --- a/codeCore/src/prog8/code/ast/AstPrinter.kt +++ b/codeCore/src/prog8/code/ast/AstPrinter.kt @@ -24,6 +24,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni is PtArray -> { val valuelist = node.children.map { when (it) { + is PtBool -> it.toString() is PtNumber -> it.number.toString() is PtIdentifier -> it.name else -> "?" diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index a099bf80c..461530c40 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -145,7 +145,6 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable { val newValue = PtArray(arrayValue.type, arrayValue.position) arrayValue.children.forEach { elt -> when(elt) { - is PtIdentifier -> newValue.add(elt.prefix(arrayValue, st)) is PtBool -> newValue.add(elt) is PtNumber -> newValue.add(elt) is PtAddressOf -> { diff --git a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt index 0b9a68164..a2aa74c6c 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt @@ -122,7 +122,7 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors: errors.warn("resulting array has length zero", part.position) val tmp = mutableListOf() repeat(rightconst.number.toInt()) { - tmp += part.value + part.value.forEach { tmp += it.copy() } } val newArray = ArrayLiteral(part.type, tmp.toTypedArray(), part.position) return listOf(IAstModification.ReplaceNode(expr, newArray, parent)) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 463b2b465..fbe4695b7 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1010,19 +1010,8 @@ internal class AstChecker(private val program: Program, checkValueTypeAndRangeArray(array.type.getOr(DataType.UNDEFINED), arrayspec, array) } - fun isPassByReferenceElement(e: Expression): Boolean { - if(e is IdentifierReference) { - val decl = e.targetVarDecl(program) - return if(decl!=null) - decl.datatype in PassByReferenceDatatypes - else - true // is probably a symbol that needs addr-of - } - return e is StringLiteral - } - if(array.parent is VarDecl) { - if (!array.value.all { it is NumericLiteral || it is AddressOf || isPassByReferenceElement(it) }) + if (!array.value.all { it is NumericLiteral || it is AddressOf }) errors.err("array literal for variable initialization contains non-constant elements", array.position) } else if(array.parent is ForLoop) { if (!array.value.all { it.constValue(program) != null }) @@ -1036,6 +1025,7 @@ internal class AstChecker(private val program: Program, if(arraydt!=targetDt) errors.err("value has incompatible type ($arraydt) for the variable ($targetDt)", array.position) } + super.visit(array) } diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt index 2d023ef09..61e7ceb52 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstMaker.kt @@ -566,8 +566,11 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr private fun transform(srcArr: ArrayLiteral): PtArray { val arr = PtArray(srcArr.inferType(program).getOrElse { throw FatalAstException("array must know its type") }, srcArr.position) - for (elt in srcArr.value) - arr.add(transformExpression(elt)) + for (elt in srcArr.value) { + val child = transformExpression(elt) + require(child is PtAddressOf || child is PtBool || child is PtNumber) { "array element invalid type $child" } + arr.add(child) + } return arr } diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index 26de5ee99..fb5d469db 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -49,6 +49,8 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro } } else { val arrayDt = array.guessDatatype(program) + if(arrayDt.isUnknown) + return noModifications val elementDt = ArrayToElementTypes.getValue(arrayDt.getOr(DataType.UNDEFINED)) val maxSize = when(elementDt) { in ByteDatatypesWithBoolean -> PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE diff --git a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt index 9788e6f6a..f51e8f605 100644 --- a/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt +++ b/compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt @@ -1,6 +1,7 @@ package prog8.compiler.astprocessing import prog8.ast.IFunctionCall +import prog8.ast.INameScope import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.FatalAstException @@ -363,6 +364,25 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val return adjustRangeDts(range, fromConst, fromDt, toConst, toDt, varDt.getOr(DataType.UNDEFINED), parent) } + override fun after(array: ArrayLiteral, parent: Node): Iterable { + // Arrays can contain booleans, numbers, or address-ofs. + // if there is an identifier here (that is of a pass-by-reference type), take its address explicitly. + + for((index, elt) in array.value.withIndex()) { + if (elt is IdentifierReference) { + val eltType = elt.inferType(program) + val tgt = elt.targetStatement(program) + if(eltType.isPassByReference || tgt is Subroutine || tgt is Label || tgt is Block) { + val addressof = AddressOf(elt, null, elt.position) + addressof.linkParents(array) + array.value[index] = addressof + } + } + } + + return noModifications + } + private fun adjustRangeDts( range: RangeExpression, fromConst: NumericLiteral?, diff --git a/compiler/test/codegeneration/TestArrayThings.kt b/compiler/test/codegeneration/TestArrayThings.kt index ae1500f06..f9fd7ba8a 100644 --- a/compiler/test/codegeneration/TestArrayThings.kt +++ b/compiler/test/codegeneration/TestArrayThings.kt @@ -4,6 +4,7 @@ import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain +import io.kotest.matchers.types.instanceOf import prog8.code.StStaticVariable import prog8.code.SymbolTableMaker import prog8.code.ast.* @@ -456,6 +457,46 @@ main { array3.children.map { (it as PtNumber).number } shouldBe listOf(100, 101, 102) } + test("identifiers in array literals getting implicit address-of") { + val src=""" +main { + sub start() { +label: + str @shared name = "name" + uword[] @shared array1 = [name, label, start, main] + uword[] @shared array2 = [&name, &label, &start, &main] + } +}""" + val result = compileText(C64Target(), false, src, writeAssembly = true)!! + val x = result.codegenAst!!.entrypoint()!! + x.children.size shouldBe 5 + val array1 = (x.children[1] as PtVariable).value as PtArray + val array2 = (x.children[2] as PtVariable).value as PtArray + array1.children.forEach { + it shouldBe instanceOf() + } + array2.children.forEach { + it shouldBe instanceOf() + } + } + + test("variable identifiers in array literals not getting implicit address-of") { + val src=""" +main { + sub start() { +label: + str @shared name = "name" + ubyte @shared bytevar + uword[] @shared array1 = [cx16.r0] ; error, is variables + uword[] @shared array2 = [bytevar] ; error, is variables + } +}""" + val errors = ErrorReporterForTests() + compileText(C64Target(), false, src, writeAssembly = true, errors=errors) shouldBe null + errors.errors.size shouldBe 2 + errors.errors[0] shouldContain "contains non-constant" + errors.errors[1] shouldContain "contains non-constant" + } fun getTestOptions(): CompilationOptions { val target = VMTarget() diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 7b274be0f..3febf0695 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,7 +1,6 @@ TODO ==== -- word arrays (after ast processing) should no longer contain identifiers, these should have been replaced by &identifier. - should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy. - should we add a cleararray builtin function that can efficiently set every element in the array to the given value @@ -14,6 +13,7 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ +- keep boolean array intact in IR so that it might be represented as a bitmask in the resulting code (8 times storage improvement) - improve detection that a variable is not read before being written so that initializing it to zero can be omitted (only happens now if a vardecl is immediately followed by a for loop for instance) - Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions - Can we support signed % (remainder) somehow? diff --git a/examples/test.p8 b/examples/test.p8 index d64713dbc..d4bd6a495 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,80 +1,11 @@ -%import floats -%import textio - -%option no_sysinit -%zeropage basicsafe - main { - - sub arrayinit_with_multiplier() { - str name = "xyz" * 3 - bool[3] boolarray = [true] * 3 - ubyte[3] bytearray = [42] * 3 - uword[3] wordarray = [5555] * 3 - float[3] floatarray = [123.45] * 3 - - txt.print(name) - txt.nl() - for cx16.r1L in 0 to 2 { - txt.print_bool(boolarray[cx16.r1L]) - txt.spc() - txt.print_ub(bytearray[cx16.r1L]) - txt.spc() - txt.print_uw(wordarray[cx16.r1L]) - txt.spc() - floats.print(floatarray[cx16.r1L]) - txt.nl() - } - txt.nl() - txt.nl() - } - - sub arrayinit_with_range() { - ubyte[3] bytearray2 = 10 to 12 - uword[3] wordarray2 = 5000 to 5002 - float[3] floatarray2 = 100 to 102 - - for cx16.r1L in 0 to 2 { - txt.print_ub(bytearray2[cx16.r1L]) - txt.spc() - txt.print_uw(wordarray2[cx16.r1L]) - txt.spc() - floats.print(floatarray2[cx16.r1L]) - txt.nl() - } - txt.nl() - txt.nl() - } - - sub arrayassign() { - bool[4] boolarray3 - ubyte[4] bytearray3 - uword[4] wordarray3 - float[4] floatarray3 - - boolarray3 = [true] *4 - bytearray3 = [42]*4 - wordarray3 = [999]*4 - wordarray3 = [&bytearray3]*4 - wordarray3 = [bytearray3]*4 - floatarray3 = [99.77]*4 - - for cx16.r1L in 0 to 2 { - txt.print_bool(boolarray3[cx16.r1L]) - txt.spc() - txt.print_ub(bytearray3[cx16.r1L]) - txt.spc() - txt.print_uw(wordarray3[cx16.r1L]) - txt.spc() - floats.print(floatarray3[cx16.r1L]) - txt.nl() - } - txt.nl() - } - sub start() { - arrayinit_with_multiplier() - arrayinit_with_range() - arrayassign() +label: + str @shared name = "name" + ubyte @shared bytevar + uword[] @shared array = [name, label, start, main, 9999] + uword[] @shared array2 = [&name, &label, &start, &main, 9999] + uword[] @shared array3 = [cx16.r0] ; error, is variables + uword[] @shared array4 = [bytevar] ; error, is variables } } From 0dd1c17ff42e64eaf0b13c3b5208ade9de89a928 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 13 Oct 2024 17:51:14 +0200 Subject: [PATCH 6/8] avoid possible crash --- compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index 326e80d9f..82a4c0970 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -49,6 +49,8 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro } } else { val arrayDt = array.guessDatatype(program) + if(arrayDt.isUnknown) + return noModifications val elementDt = ArrayToElementTypes.getValue(arrayDt.getOr(DataType.UNDEFINED)) val maxSize = when(elementDt) { in ByteDatatypesWithBoolean -> PtContainmentCheck.MAX_SIZE_FOR_INLINE_CHECKS_BYTE From e9edffa9f03d9658af80c15934a780ecd2f10c86 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 13 Oct 2024 19:37:25 +0200 Subject: [PATCH 7/8] remove support for array-to-array assignments (other than initialization of variable declaration) Just use an explicit sys.memcopy(src, dest, sizeof(dest)) or assign array members individually. --- .../src/prog8/code/core/BuiltinFunctions.kt | 1 - .../codegen/cpu6502/BuiltinFunctionsAsmGen.kt | 107 ----------- .../codegen/intermediate/BuiltinFuncGen.kt | 62 ------ .../compiler/astprocessing/AstChecker.kt | 13 +- .../compiler/astprocessing/CodeDesugarer.kt | 16 -- .../test/codegeneration/TestArrayThings.kt | 181 ------------------ compiler/test/codegeneration/TestVariables.kt | 18 -- docs/source/programming.rst | 15 +- docs/source/syntaxreference.rst | 2 - docs/source/todo.rst | 4 +- examples/cx16/automatons.p8 | 2 +- examples/test.p8 | 33 +++- .../src/prog8/intermediate/IMSyscall.kt | 4 +- virtualmachine/src/prog8/vm/SysCalls.kt | 34 +--- .../src/prog8/vm/VmProgramLoader.kt | 2 - 15 files changed, 48 insertions(+), 446 deletions(-) diff --git a/codeCore/src/prog8/code/core/BuiltinFunctions.kt b/codeCore/src/prog8/code/core/BuiltinFunctions.kt index 926b82046..7e2553c1c 100644 --- a/codeCore/src/prog8/code/core/BuiltinFunctions.kt +++ b/codeCore/src/prog8/code/core/BuiltinFunctions.kt @@ -79,7 +79,6 @@ val BuiltinFunctions: Map = mapOf( // cmp returns a status in the carry flag, but not a proper return value "cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null), "prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE), - "prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null), "prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE), "prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD), "prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL), diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index a924993bd..0a582b8f1 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -1,7 +1,5 @@ package prog8.codegen.cpu6502 -import prog8.code.StMemVar -import prog8.code.StStaticVariable import prog8.code.ast.* import prog8.code.core.* import prog8.codegen.cpu6502.assignment.* @@ -71,101 +69,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, "prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister) "prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister) "prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister) - "prog8_lib_arraycopy" -> funcArrayCopy(fcall) else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}") } return BuiltinFunctions.getValue(fcall.name).returnType } - private fun funcArrayCopy(fcall: PtBuiltinFunctionCall) { - val source = fcall.args[0] as PtIdentifier - val target = fcall.args[1] as PtIdentifier - - val numElements = when(val sourceSymbol = asmgen.symbolTable.lookup(source.name)) { - is StStaticVariable -> sourceSymbol.length!! - is StMemVar -> sourceSymbol.length!! - else -> 0 - } - val sourceAsm = asmgen.asmVariableName(source) - val targetAsm = asmgen.asmVariableName(target) - - if(source.type in SplitWordArrayTypes && target.type in SplitWordArrayTypes) { - // split -> split words (copy lsb and msb arrays separately) - asmgen.out(""" - lda #<${sourceAsm}_lsb - ldy #>${sourceAsm}_lsb - sta P8ZP_SCRATCH_W1 - sty P8ZP_SCRATCH_W1+1 - lda #<${targetAsm}_lsb - ldy #>${targetAsm}_lsb - sta P8ZP_SCRATCH_W2 - sty P8ZP_SCRATCH_W2+1 - ldy #${numElements and 255} - jsr prog8_lib.memcopy_small - lda #<${sourceAsm}_msb - ldy #>${sourceAsm}_msb - sta P8ZP_SCRATCH_W1 - sty P8ZP_SCRATCH_W1+1 - lda #<${targetAsm}_msb - ldy #>${targetAsm}_msb - sta P8ZP_SCRATCH_W2 - sty P8ZP_SCRATCH_W2+1 - ldy #${numElements and 255} - jsr prog8_lib.memcopy_small""") - } - else if(source.type in SplitWordArrayTypes) { - // split word array to normal word array (copy lsb and msb arrays separately) - require(target.type==DataType.ARRAY_UW || target.type==DataType.ARRAY_W) - asmgen.out(""" - lda #<${sourceAsm}_lsb - ldy #>${sourceAsm}_lsb - sta P8ZP_SCRATCH_W1 - sty P8ZP_SCRATCH_W1+1 - lda #<${sourceAsm}_msb - ldy #>${sourceAsm}_msb - sta P8ZP_SCRATCH_W2 - sty P8ZP_SCRATCH_W2+1 - lda #<${targetAsm} - ldy #>${targetAsm} - ldx #${numElements and 255} - jsr prog8_lib.arraycopy_split_to_normal_words""") - } - else if(target.type in SplitWordArrayTypes) { - // normal word array to split array - require(source.type==DataType.ARRAY_UW || source.type==DataType.ARRAY_W) - asmgen.out(""" - lda #<${targetAsm}_lsb - ldy #>${targetAsm}_lsb - sta P8ZP_SCRATCH_W1 - sty P8ZP_SCRATCH_W1+1 - lda #<${targetAsm}_msb - ldy #>${targetAsm}_msb - sta P8ZP_SCRATCH_W2 - sty P8ZP_SCRATCH_W2+1 - lda #<${sourceAsm} - ldy #>${sourceAsm} - ldx #${numElements and 255} - jsr prog8_lib.arraycopy_normal_to_split_words""") - } - else { - // normal array to array copy, various element types - val eltsize = asmgen.options.compTarget.memorySize(ArrayToElementTypes.getValue(source.type)) - val numBytes = numElements * eltsize - asmgen.out(""" - lda #<${sourceAsm} - ldy #>${sourceAsm} - sta P8ZP_SCRATCH_W1 - sty P8ZP_SCRATCH_W1+1 - lda #<${targetAsm} - ldy #>${targetAsm} - sta P8ZP_SCRATCH_W2 - sty P8ZP_SCRATCH_W2+1 - ldy #${numBytes and 255} - jsr prog8_lib.memcopy_small""") - } - } - private fun funcSquare(fcall: PtBuiltinFunctionCall, resultType: DataType, resultRegister: RegisterOrPair?) { // square of word value is faster with dedicated routine, square of byte just use the regular multiplication routine. when (resultType) { @@ -1357,22 +1266,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram, } } - private fun outputAddressAndLengthOfArray(arg: PtIdentifier) { - // address goes in P8ZP_SCRATCH_W1, number of elements in A - val numElements = when(val symbol = asmgen.symbolTable.lookup(arg.name)) { - is StStaticVariable -> symbol.length!! - is StMemVar -> symbol.length!! - else -> 0 - } - val identifierName = asmgen.asmVariableName(arg) - asmgen.out(""" - lda #<$identifierName - ldy #>$identifierName - sta P8ZP_SCRATCH_W1 - sty P8ZP_SCRATCH_W1+1 - lda #${numElements and 255}""") - } - private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) { val signature = BuiltinFunctions.getValue(call.name) val callConv = signature.callConvention(call.args.map { it.type}) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index 4b9c9fcc1..979eb853a 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -44,72 +44,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe "prog8_lib_stringcompare" -> funcStringCompare(call) "prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE) "prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD) - "prog8_lib_arraycopy" -> funcArrayCopy(call) else -> throw AssemblyError("missing builtinfunc for ${call.name}") } } - private fun funcArrayCopy(call: PtBuiltinFunctionCall): ExpressionCodeResult { - val source = call.args[0] as PtIdentifier - val target = call.args[1] as PtIdentifier - val sourceLength = codeGen.symbolTable.getLength(source.name)!! - val targetLength = codeGen.symbolTable.getLength(target.name)!! - require(sourceLength==targetLength) - val result = mutableListOf() - val fromReg = codeGen.registers.nextFree() - val toReg = codeGen.registers.nextFree() - val countReg = codeGen.registers.nextFree() - if(source.type in SplitWordArrayTypes && target.type in SplitWordArrayTypes) { - // split words -> split words, copy lsb and msb arrays separately - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb") - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb") - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength) - it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null) - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_msb") - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_msb") - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength) - it += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null) - } - } - else if(source.type in SplitWordArrayTypes) { - // split -> normal words - require(target.type==DataType.ARRAY_UW || target.type==DataType.ARRAY_W) - val fromRegMsb = codeGen.registers.nextFree() - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name+"_lsb") - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromRegMsb, labelSymbol = source.name+"_msb") - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name) - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength) - } - result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_SPLITW_TO_NORMAL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to fromRegMsb, IRDataType.WORD to toReg, IRDataType.BYTE to countReg), returns = null) - } - else if(target.type in SplitWordArrayTypes) { - // normal -> split words - require(source.type==DataType.ARRAY_UW || source.type==DataType.ARRAY_W) - val toRegMsb = codeGen.registers.nextFree() - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name) - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name+"_lsb") - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toRegMsb, labelSymbol = target.name+"_msb") - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=countReg, immediate = sourceLength) - } - result += codeGen.makeSyscall(IMSyscall.ARRAYCOPY_NORMAL_TO_SPLITW, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.WORD to toRegMsb, IRDataType.BYTE to countReg), returns = null) - } - else { - // normal array to array copy (various element types) - val eltsize = codeGen.options.compTarget.memorySize(ArrayToElementTypes.getValue(source.type)) - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=fromReg, labelSymbol = source.name) - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=toReg, labelSymbol = target.name) - it += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=countReg, immediate = sourceLength * eltsize) - } - result += codeGen.makeSyscall(IMSyscall.MEMCOPY_SMALL, listOf(IRDataType.WORD to fromReg, IRDataType.WORD to toReg, IRDataType.BYTE to (countReg and 255)), returns = null) - } - - return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1) - } - private fun funcSquare(call: PtBuiltinFunctionCall, resultType: IRDataType): ExpressionCodeResult { val result = mutableListOf() val valueTr = exprGen.translateExpression(call.args[0]) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index fbe4695b7..0d0dc945f 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1813,14 +1813,9 @@ internal class AstChecker(private val program: Program, sourceValue: Expression) : Boolean { val position = sourceValue.position - if(sourceValue is ArrayLiteral && targetDatatype in ArrayDatatypes) { - val vardecl=target.identifier?.targetVarDecl(program) - val targetSize = vardecl?.arraysize?.constIndex() - if(targetSize!=null) { - if(sourceValue.value.size != targetSize) { - errors.err("array size mismatch (expecting $targetSize, got ${sourceValue.value.size})", sourceValue.position) - } - } + if(sourceValue is ArrayLiteral || targetDatatype in ArrayDatatypes) { + errors.err("cannot assign arrays directly. Maybe use sys.memcopy(src, tgt, sizeof(tgt)) instead.", target.position) + return false } if(sourceValue is RangeExpression) { @@ -1841,7 +1836,7 @@ internal class AstChecker(private val program: Program, DataType.UWORD -> sourceDatatype == DataType.UBYTE || sourceDatatype == DataType.UWORD DataType.FLOAT -> sourceDatatype in NumericDatatypes DataType.STR -> sourceDatatype == DataType.STR - else -> targetDatatype in ArrayDatatypes && sourceValue is ArrayLiteral + else -> false } if(result) diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt index 29620e9ce..07b4f2ed3 100644 --- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt +++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt @@ -23,22 +23,6 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep // - pointer[word] replaced by @(pointer+word) // - @(&var) and @(&var+1) replaced by lsb(var) and msb(var) if var is a word // - flatten chained assignments - // - replace array assignments by a call to the builtin function that does this: prog8_lib_arraycopy - - override fun after(assignment: Assignment, parent: Node): Iterable { - val targetArray = assignment.target.identifier?.targetVarDecl(program) - val sourceArray = (assignment.value as? IdentifierReference)?.targetVarDecl(program) - if(targetArray?.isArray==true && sourceArray?.isArray==true) { - val copy = FunctionCallStatement( - IdentifierReference(listOf("prog8_lib_arraycopy"), assignment.position), - mutableListOf( - IdentifierReference(sourceArray.scopedName, assignment.position), - IdentifierReference(targetArray.scopedName, assignment.position) - ), false, assignment.position) - return listOf(IAstModification.ReplaceNode(assignment, copy, parent)) - } - return noModifications - } override fun before(breakStmt: Break, parent: Node): Iterable { fun jumpAfter(stmt: Statement): Iterable { diff --git a/compiler/test/codegeneration/TestArrayThings.kt b/compiler/test/codegeneration/TestArrayThings.kt index f9fd7ba8a..a09cde735 100644 --- a/compiler/test/codegeneration/TestArrayThings.kt +++ b/compiler/test/codegeneration/TestArrayThings.kt @@ -5,10 +5,7 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.instanceOf -import prog8.code.StStaticVariable -import prog8.code.SymbolTableMaker import prog8.code.ast.* -import prog8.code.core.* import prog8.code.target.C64Target import prog8.code.target.VMTarget import prog8tests.helpers.ErrorReporterForTests @@ -156,25 +153,6 @@ main { compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null } - test("split array assignments") { - val text = """ -main { - sub start() { - str name1 = "name1" - str name2 = "name2" - uword[] @split names = [name1, name2, "name3"] - uword[] @split names2 = [name1, name2, "name3"] - uword[] addresses = [0,0,0] - names = [1111,2222,3333] - addresses = names - names = addresses - names2 = names - } -}""" - compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null - compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null - } - test("array target with expression for index") { val text = """ main { @@ -333,81 +311,6 @@ main { errors.errors[2] shouldContain "out of bounds" } - test("array assignments should check for number of elements and element type correctness") { - val src=""" -%option enable_floats - -main { - sub start() { - ubyte[] array = 1 to 4 - ubyte[] array2 = [1,2,3,4] - str[] names = ["apple", "banana", "tomato"] - - array = [10,11,12,13] ; ok! - array = 20 to 23 ; ok! - names = ["x1", "x2", "x3"] ; ok! - - ubyte[] array3 = [1,2,3,4000] ; error: element type - array = 10 to 15 ; error: array size - array = 1000 to 1003 ; error: element type - names = ["x1", "x2", "x3", "x4"] ; error: array size - names = [1.1, 2.2, 3.3, 4.4] ; error: array size AND element type - names = [1.1, 2.2, 999999.9] ; error: element type - names = [1.1, 2.2, 9.9] ; error: element type - } -}""" - val errors = ErrorReporterForTests() - compileText(C64Target(), false, src, writeAssembly = true, errors = errors) shouldBe null - errors.errors.size shouldBe 8 - errors.errors[0] shouldContain "incompatible type" - errors.errors[1] shouldContain "array size mismatch" - errors.errors[2] shouldContain "array element out of range" - errors.errors[3] shouldContain "array size mismatch" - errors.errors[4] shouldContain "array size mismatch" - errors.errors[5] shouldContain "value has incompatible type" - errors.errors[6] shouldContain "value has incompatible type" - errors.errors[7] shouldContain "value has incompatible type" - } - - test("array assignments should work via array copy call") { - val src=""" -%option enable_floats - -main { - sub start() { - ubyte[] array = [1,2,3] - ubyte[3] array2 - float[] flarray = [1.1, 2.2, 3.3] - float[3] flarray2 - word[] warray = [-2222,42,3333] - word[3] warray2 - str[] names = ["apple", "banana", "tomato"] - str[3] names2 - - ; 8 array assignments -> 8 arraycopies: - array = [8,7,6] - array = array2 - flarray = [99.9, 88.8, 77.7] - flarray = flarray2 - warray = [4444,5555,6666] - warray = warray2 - names = ["x1", "x2", "x3"] - names = names2 - } -}""" - compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null - val result = compileText(C64Target(), false, src, writeAssembly = true)!! - val x = result.codegenAst!!.entrypoint()!! - (x.children[12] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" - (x.children[13] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" - (x.children[14] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" - (x.children[15] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" - (x.children[16] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" - (x.children[17] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" - (x.children[18] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" - (x.children[19] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy" - } - test("array and string initializer with multiplication") { val src=""" %option enable_floats @@ -497,89 +400,5 @@ label: errors.errors[0] shouldContain "contains non-constant" errors.errors[1] shouldContain "contains non-constant" } - - fun getTestOptions(): CompilationOptions { - val target = VMTarget() - return CompilationOptions( - OutputType.RAW, - CbmPrgLauncherType.NONE, - ZeropageType.DONTUSE, - zpReserved = emptyList(), - zpAllowed = CompilationOptions.AllZeropageAllowed, - floats = true, - noSysInit = false, - compTarget = target, - loadAddress = target.machine.PROGRAM_LOAD_ADDRESS - ) - } - - - test("array assignments with ranges and multiplications") { - val src=""" -%option enable_floats - -main { - sub start() { - bool[4] boolarray3 - ubyte[4] bytearray3 - uword[4] wordarray3 - float[4] floatarray3 - - boolarray3 = [true] *4 - bytearray3 = [42]*4 - wordarray3 = [999]*4 - wordarray3 = [&bytearray3]*4 - wordarray3 = [bytearray3]*4 - floatarray3 = [99.77]*4 - - bytearray3 = 10 to 13 - wordarray3 = 5000 to 5003 - floatarray3 = 100 to 103 - } -}""" - val ast = compileText(C64Target(), false, src, writeAssembly = true)!!.codegenAst!! - val x = ast.entrypoint()!! - x.children.size shouldBe 23 - val assign1value = (x.children[13] as PtBuiltinFunctionCall).args[1] - val assign2value = (x.children[14] as PtBuiltinFunctionCall).args[1] - val assign3value = (x.children[15] as PtBuiltinFunctionCall).args[1] - val assign4value = (x.children[16] as PtBuiltinFunctionCall).args[1] - val assign5value = (x.children[17] as PtBuiltinFunctionCall).args[1] - val assign6value = (x.children[18] as PtBuiltinFunctionCall).args[1] - val assign7value = (x.children[19] as PtBuiltinFunctionCall).args[1] - val assign8value = (x.children[20] as PtBuiltinFunctionCall).args[1] - val assign9value = (x.children[21] as PtBuiltinFunctionCall).args[1] - val options = getTestOptions() - val st = SymbolTableMaker(ast, options).make() - - val heapvar1 = st.lookup((assign1value as PtIdentifier).name) as StStaticVariable - val heapvar2 = st.lookup((assign2value as PtIdentifier).name) as StStaticVariable - val heapvar3 = st.lookup((assign3value as PtIdentifier).name) as StStaticVariable - val heapvar4 = st.lookup((assign4value as PtIdentifier).name) as StStaticVariable - val heapvar5 = st.lookup((assign5value as PtIdentifier).name) as StStaticVariable - val heapvar6 = st.lookup((assign6value as PtIdentifier).name) as StStaticVariable - val heapvar7 = st.lookup((assign7value as PtIdentifier).name) as StStaticVariable - val heapvar8 = st.lookup((assign8value as PtIdentifier).name) as StStaticVariable - val heapvar9 = st.lookup((assign9value as PtIdentifier).name) as StStaticVariable - heapvar1.length shouldBe 4 - heapvar2.length shouldBe 4 - heapvar3.length shouldBe 4 - heapvar4.length shouldBe 4 - heapvar5.length shouldBe 4 - heapvar6.length shouldBe 4 - heapvar7.length shouldBe 4 - heapvar8.length shouldBe 4 - heapvar9.length shouldBe 4 - heapvar1.dt shouldBe DataType.ARRAY_BOOL - heapvar2.dt shouldBe DataType.ARRAY_UB - heapvar3.dt shouldBe DataType.ARRAY_UW - heapvar4.dt shouldBe DataType.ARRAY_UW - heapvar5.dt shouldBe DataType.ARRAY_UW - heapvar6.dt shouldBe DataType.ARRAY_F - heapvar7.dt shouldBe DataType.ARRAY_UB - heapvar8.dt shouldBe DataType.ARRAY_UW - heapvar9.dt shouldBe DataType.ARRAY_F - } - }) diff --git a/compiler/test/codegeneration/TestVariables.kt b/compiler/test/codegeneration/TestVariables.kt index 6c143cbad..cb65c6575 100644 --- a/compiler/test/codegeneration/TestVariables.kt +++ b/compiler/test/codegeneration/TestVariables.kt @@ -5,7 +5,6 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain import prog8.code.target.C64Target -import prog8.code.target.VMTarget import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.compileText @@ -42,23 +41,6 @@ class TestVariables: FunSpec({ compileText(C64Target(), true, text, writeAssembly = true) shouldNotBe null } - test("array initialization with array var assignment") { - val text = """ - main { - sub start() { - ubyte[3] @shared arrayvar=main.values1 - arrayvar = main.values2 - } - - ubyte[] values1 = [1,2,3] - ubyte[] values2 = [1,2,3] - } - """ - compileText(VMTarget(), false, text, writeAssembly = true) shouldNotBe null - compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null - } - - test("pipe character in string literal") { val text = """ main { diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 15f831d9b..d55d97c93 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -299,7 +299,6 @@ always have to be constants. Here are some examples of arrays:: value = array[3] ; the fourth value in the array (index is 0-based) char = string[4] ; the fifth character (=byte) in the string char = string[-2] ; the second-to-last character in the string (Python-style indexing from the end) - flags = [false, true] ; reset all flags in the array .. note:: Right now, the array should be small enough to be indexable by a single byte index. @@ -309,14 +308,16 @@ always have to be constants. Here are some examples of arrays:: Arrays can be initialized with a range expression or an array literal value. You can write out such an initializer value over several lines if you want to improve readability. +You can assign a new value to an element in the array, but you can't assign a whole +new array to another array at once. This is usually a costly operation. If you really +need this you have to write it out depending on the use case: you can copy the memory using +``sys.memcopy(sourcearray, targetarray, sizeof(targetarray))``. Or perhaps use ``sys.memset`` instead to +set it all to the same value, or maybe even simply assign the individual elements. + Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.) can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte`` for instance. -It is possible to assign an array (variable or array literal) to another array; this will overwrite all elements in the target -array with those in the source array. The number of elements in the arrays and the data types have to match. -For large arrays this is a slow operation because all values are copied over. - Using the ``in`` operator you can easily check if a value is present in an array, example: ``if choice in [1,2,3,4] {....}`` @@ -378,8 +379,8 @@ You can concatenate two string literals using '+', which can be useful to split long strings over separate lines. But remember that the length of the total string still cannot exceed 255 characters. A string literal can also be repeated a given number of times using '*', where the repeat number must be a constant value. -And a new string value can be assigned to another string, but no bounds check is done -so be sure the destination string is large enough to contain the new value (it is overwritten in memory):: +And a new string value can be assigned to another string, but no bounds check is done! +So be sure the destination string is large enough to contain the new value (it is overwritten in memory):: str string1 = "first part" + "second part" str string2 = "hello!" * 10 diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 12ae61f64..853dabb39 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -511,8 +511,6 @@ the downto variant to avoid having to specify the step as well:: xx = 10 aa to xx ; range of 5, 6, 7, 8, 9, 10 - byte[] array = 10 to 13 ; sets the array to [10, 11, 12, 13] - for i in 0 to 127 { ; i loops 0, 1, 2, ... 127 } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 3febf0695..cd8555edb 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,9 +1,7 @@ TODO ==== -- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy. -- should we add a cleararray builtin function that can efficiently set every element in the array to the given value - +- fixup syscall list UNUSED_SYSCALL_1 and 2 (numbers shift!) Improve register load order in subroutine call args assignments: in certain situations, the "wrong" order of evaluation of function call arguments is done which results diff --git a/examples/cx16/automatons.p8 b/examples/cx16/automatons.p8 index 179fe7b48..181ead3ca 100644 --- a/examples/cx16/automatons.p8 +++ b/examples/cx16/automatons.p8 @@ -25,7 +25,7 @@ main { for y in 32 to 199+32 { cx16.FB_cursor_position((320-len(cells))/2,y) cx16.FB_set_pixels(cells, len(cells)) - cells_previous = cells + sys.memcopy(cells, cells_previous, sizeof(cells)) ubyte @zp x for x in 0 to len(cells)-1 { cells[x] = generate(x) ; next generation diff --git a/examples/test.p8 b/examples/test.p8 index d4bd6a495..cf1349a12 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,11 +1,30 @@ +%import floats +%import textio +%option no_sysinit +%zeropage basicsafe + main { + sub start() { -label: - str @shared name = "name" - ubyte @shared bytevar - uword[] @shared array = [name, label, start, main, 9999] - uword[] @shared array2 = [&name, &label, &start, &main, 9999] - uword[] @shared array3 = [cx16.r0] ; error, is variables - uword[] @shared array4 = [bytevar] ; error, is variables + uword[4] words1 = [1,2,3,4] + uword[4] words2 = [99,88,77,66] + + for cx16.r0 in words1 { + txt.print_uw(cx16.r0) + txt.spc() + } + txt.nl() + sys.memcopy(words2, words1, sizeof(words1)) + for cx16.r0 in words1 { + txt.print_uw(cx16.r0) + txt.spc() + } + txt.nl() + sys.memcopy([2222,3333,4444,5555], words1, sizeof(words1)) + for cx16.r0 in words1 { + txt.print_uw(cx16.r0) + txt.spc() + } + txt.nl() } } diff --git a/intermediate/src/prog8/intermediate/IMSyscall.kt b/intermediate/src/prog8/intermediate/IMSyscall.kt index e3548e542..4b33373e2 100644 --- a/intermediate/src/prog8/intermediate/IMSyscall.kt +++ b/intermediate/src/prog8/intermediate/IMSyscall.kt @@ -18,7 +18,5 @@ enum class IMSyscall(val number: Int) { CALLFAR(0x1017), CALLFAR2(0x1018), MEMCOPY(0x1019), - MEMCOPY_SMALL(0x101a), - ARRAYCOPY_SPLITW_TO_NORMAL(0x101b), - ARRAYCOPY_NORMAL_TO_SPLITW(0x101c), + MEMCOPY_SMALL(0x101a) } diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index 0747d35bb..9c9ad1e9e 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -50,8 +50,8 @@ SYSCALLS: 37 = memset 38 = memsetw 39 = stringcopy -40 = ARRAYCOPY_SPLITW_TO_NORMAL -41 = ARRAYCOPY_NORMAL_TO_SPLITW +40 = ...unused... +41 = ...unused... 42 = memcopy_small 43 = load 44 = load_raw @@ -103,8 +103,8 @@ enum class Syscall { MEMSET, MEMSETW, STRINGCOPY, - ARRAYCOPY_SPLITW_TO_NORMAL, - ARRAYCOPY_NORMAL_TO_SPLITW, + UNUSED_SYSCALL_1, // TODO fixup + UNUSED_SYSCALL_2, // TODO fixup MEMCOPY_SMALL, LOAD, LOAD_RAW, @@ -444,29 +444,6 @@ object SysCalls { vm.memory.setString(target, string, true) returnValue(callspec.returns.single(), string.length, vm) } - Syscall.ARRAYCOPY_SPLITW_TO_NORMAL -> { - val (fromLsbA, fromMsbA, targetA, bytecountA) = getArgValues(callspec.arguments, vm) - val fromLsb = (fromLsbA as UShort).toInt() - val fromMsb = (fromMsbA as UShort).toInt() - val target = (targetA as UShort).toInt() - val bytecount = (bytecountA as UByte).toInt() - for(offset in 0.. { - val (fromA, targetLsbA, targetMsbA, bytecountA) = getArgValues(callspec.arguments, vm) - val from = (fromA as UShort).toInt() - val targetLsb = (targetLsbA as UShort).toInt() - val targetMsb = (targetMsbA as UShort).toInt() - val bytecount = (bytecountA as UByte).toInt() - for(offset in 0.. { val (filenameA, addrA) = getArgValues(callspec.arguments, vm) val filename = vm.memory.getString((filenameA as UShort).toInt()) @@ -572,6 +549,9 @@ object SysCalls { } return returnValue(callspec.returns.single(), 30*256 + 80, vm) // just return some defaults in this case 80*30 } + + Syscall.UNUSED_SYSCALL_1 -> TODO("remove this") + Syscall.UNUSED_SYSCALL_2 -> TODO("remove this") } } } diff --git a/virtualmachine/src/prog8/vm/VmProgramLoader.kt b/virtualmachine/src/prog8/vm/VmProgramLoader.kt index 60d3363fc..300c0e136 100644 --- a/virtualmachine/src/prog8/vm/VmProgramLoader.kt +++ b/virtualmachine/src/prog8/vm/VmProgramLoader.kt @@ -117,8 +117,6 @@ class VmProgramLoader { IMSyscall.CALLFAR2.number -> throw IRParseException("vm doesn't support the callfar2() syscall") IMSyscall.MEMCOPY.number -> Syscall.MEMCOPY IMSyscall.MEMCOPY_SMALL.number -> Syscall.MEMCOPY_SMALL - IMSyscall.ARRAYCOPY_SPLITW_TO_NORMAL.number -> Syscall.ARRAYCOPY_SPLITW_TO_NORMAL - IMSyscall.ARRAYCOPY_NORMAL_TO_SPLITW.number -> Syscall.ARRAYCOPY_NORMAL_TO_SPLITW else -> null } From 5280e1b44928c424eb8d0c83a8d101b16955f821 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 13 Oct 2024 21:33:13 +0200 Subject: [PATCH 8/8] err msgs --- .../src/prog8/compiler/astprocessing/AstChecker.kt | 13 +++++++++---- examples/test.p8 | 3 ++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 0d0dc945f..ee85d9cd6 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1813,16 +1813,21 @@ internal class AstChecker(private val program: Program, sourceValue: Expression) : Boolean { val position = sourceValue.position - if(sourceValue is ArrayLiteral || targetDatatype in ArrayDatatypes) { - errors.err("cannot assign arrays directly. Maybe use sys.memcopy(src, tgt, sizeof(tgt)) instead.", target.position) + if (targetDatatype in ArrayDatatypes) { + if(sourceValue.inferType(program).isArray) + errors.err("cannot assign arrays directly. Maybe use sys.memcopy instead.", target.position) + else + errors.err("cannot assign value to array. Maybe use sys.memset/memsetw instead.", target.position) + return false + } + if (sourceValue is ArrayLiteral) { + errors.err("cannot assign array", target.position) return false } - if(sourceValue is RangeExpression) { errors.err("can't assign a range value to something else", position) return false } - if(sourceDatatype==DataType.UNDEFINED) { errors.err("assignment right hand side doesn't result in a value", position) return false diff --git a/examples/test.p8 b/examples/test.p8 index cf1349a12..423eb6014 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -14,7 +14,8 @@ main { txt.spc() } txt.nl() - sys.memcopy(words2, words1, sizeof(words1)) + cx16.r0L = words2 + sys.memsetw(words1, len(words1), 99) for cx16.r0 in words1 { txt.print_uw(cx16.r0) txt.spc()