From 0bacfcef6c195868d693ccd471150dfd2cba7b3f Mon Sep 17 00:00:00 2001 From: Eremey Valetov Date: Tue, 10 Mar 2026 22:20:58 -0400 Subject: [PATCH] Implement VIEW/WINDOW/PALETTE, PMAP, fix MBF float format, update to v0.13.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Graphics viewport and coordinate mapping: - VIEW [[SCREEN] (x1,y1)-(x2,y2) [,[fill][,border]]] with clipping - WINDOW [[SCREEN] (x1,y1)-(x2,y2)] with Cartesian/screen modes - PALETTE [attribute, color] with CGA 16-color remapping - PMAP(coord, func) for logical/physical coordinate conversion - All graphics statements (PSET, LINE, CIRCLE, PAINT, GET/PUT) respect viewport clipping and WINDOW coordinate mapping MBF (Microsoft Binary Format) float support: - CVS/CVD now interpret bytes as MBF format (compatible with real GW-BASIC) - MKS$/MKD$ now produce MBF-encoded bytes - Fixed shift errors in MBF↔IEEE conversion routines (single: 1→0, double: 4→3) - Random-access file I/O now byte-compatible with original GWBASIC.EXE 66 tests (2 new), 61 compat matches (up from 58). --- README.md | 6 +- docs/development.md | 5 +- docs/language-reference.md | 16 +- docs/roadmap.md | 4 - include/graphics.h | 15 ++ include/gwbasic.h | 2 +- src/eval.c | 12 ++ src/fileio.c | 18 ++- src/graphics.c | 219 +++++++++++++++++++++++++++- src/interp.c | 150 ++++++++++++++++--- src/math_float.c | 13 +- tests/expected/mbf_format.expected | 15 ++ tests/expected/view_window.expected | 7 + tests/programs/mbf_format.bas | 22 +++ tests/programs/view_window.bas | 21 +++ 15 files changed, 478 insertions(+), 47 deletions(-) create mode 100644 tests/expected/mbf_format.expected create mode 100644 tests/expected/view_window.expected create mode 100644 tests/programs/mbf_format.bas create mode 100644 tests/programs/view_window.bas diff --git a/README.md b/README.md index 1d52da7..b7a8361 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Interactive mode launches the authentic GW-BASIC full-screen editor: ``` $ ./gwbasic -GW-BASIC 2026 0.12.0 +GW-BASIC 2026 0.13.0 (C) Eremey Valetov 2026. MIT License. Based on Microsoft GW-BASIC assembly source. Ok @@ -77,7 +77,7 @@ SPACE$, STRING$, HEX$, OCT$, INSTR, INPUT$ | File management | KILL, NAME, FILES, MKDIR, RMDIR, CHDIR, SHELL | | Date/time | DATE$, TIME$, TIMER | | Screen | LOCATE, COLOR, WIDTH, SCREEN, KEY ON/OFF/LIST | -| Graphics | PSET, PRESET, LINE, CIRCLE, DRAW, PAINT, GET/PUT (sprites), BSAVE, BLOAD | +| Graphics | PSET, PRESET, LINE, CIRCLE, DRAW, PAINT, GET/PUT (sprites), BSAVE, BLOAD, VIEW, WINDOW, PALETTE, PMAP | | Sound | SOUND, BEEP, PLAY (MML) | | Memory | DEF SEG, PEEK, POKE | @@ -206,7 +206,7 @@ Source text → Tokenizer (CRUNCH) → Token stream | Platform | hal_posix.c | OEM*.ASM | Key design differences from the original: -- IEEE 754 floating point (MBF conversion only for CVI/CVS/CVD compatibility) +- IEEE 754 floating point (MBF conversion for CVI/CVS/CVD/MKI$/MKS$/MKD$ file compatibility) - Dynamic memory allocation instead of 64KB segment layout - malloc'd strings instead of compacting garbage collector - setjmp/longjmp for error recovery diff --git a/docs/development.md b/docs/development.md index 6533fa4..e4e4d68 100644 --- a/docs/development.md +++ b/docs/development.md @@ -16,10 +16,11 @@ | 0.10.0 | | Binary tokenized SAVE/LOAD, INKEY$ extended key sequences, golden-file regression tests, classic BASIC programs | | 0.11.0 | | DEF SEG / PEEK / POKE virtual memory, GET/PUT graphics sprites, PRINT USING fixes | | 0.12.0 | | BSAVE/BLOAD, TUI ANSI 16-color rendering, CGA graphics framebuffer PEEK/POKE, keyboard shift flags | +| 0.13.0 | | VIEW/WINDOW/PALETTE graphics, PMAP function, MBF float format for CVS/CVD/MKS$/MKD$ | ## Tests -64 test programs in `tests/programs/`, plus 4 classic interactive programs in +66 test programs in `tests/programs/`, plus 4 classic interactive programs in `tests/classic/` (Hamurabi, Lunar Lander, Gunner, Diamond from David Ahl's *BASIC Computer Games*). @@ -29,7 +30,7 @@ Run the full automated suite: bash tests/run_tests.sh ``` -Each test has a 5-second timeout. 60 tests have `.expected` golden files +Each test has a 5-second timeout. 62 tests have `.expected` golden files for output regression detection. Three timing-dependent tests (datetime, on_timer, timer_stop) and one visual test (color_test) run without golden comparison. diff --git a/docs/language-reference.md b/docs/language-reference.md index 65a2b9a..bf848ed 100644 --- a/docs/language-reference.md +++ b/docs/language-reference.md @@ -57,7 +57,7 @@ type suffixes (`%`, `!`, `#`) | File management | `KILL`, `NAME`, `FILES`, `MKDIR`, `RMDIR`, `CHDIR`, `SHELL` | | Date/time | `DATE$`, `TIME$`, `TIMER` | | Screen | `LOCATE`, `COLOR`, `WIDTH`, `SCREEN`, `KEY ON`/`OFF`/`LIST`, `KEY n,"string"` | -| Graphics | `PSET`, `PRESET`, `LINE`, `CIRCLE`, `DRAW`, `PAINT`, `GET`, `PUT` | +| Graphics | `PSET`, `PRESET`, `LINE`, `CIRCLE`, `DRAW`, `PAINT`, `GET`, `PUT`, `VIEW`, `WINDOW`, `PALETTE`, `PMAP` | | Sound | `SOUND`, `BEEP`, `PLAY` (MML parser, PulseAudio backend) | | Memory | `DEF SEG`, `PEEK`, `POKE`, `BSAVE`, `BLOAD` | | Misc | `KEY`, `TRON`/`TROFF`, `OPTION BASE`, `MID$` assignment | @@ -158,6 +158,20 @@ Sprite data is stored in CGA-compatible packed format: word 0 is the width in bits, word 1 is the height, and remaining words contain packed pixel data matching the original GW-BASIC representation. +### VIEW / WINDOW / PALETTE + +- `VIEW [[SCREEN] (x1,y1)-(x2,y2) [,[fill][,border]]]` — define a graphics + viewport. Without `SCREEN`, drawing coordinates are relative to the + viewport origin. With `SCREEN`, coordinates remain absolute. Without + arguments, resets to full screen. +- `WINDOW [[SCREEN] (x1,y1)-(x2,y2)]` — map logical coordinates onto the + viewport. Without `SCREEN`, Y increases upward (Cartesian); with `SCREEN`, + Y increases downward. Without arguments, resets to physical coordinates. +- `PALETTE [attribute, color]` — remap a color attribute to a different + physical color (0–15). Without arguments, resets to the default CGA palette. +- `PMAP(coordinate, function)` — convert between logical and physical + coordinates. Function 0/1 = logical→physical X/Y; 2/3 = physical→logical X/Y. + ### Example ``` diff --git a/docs/roadmap.md b/docs/roadmap.md index 27422d9..b5640f9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -15,8 +15,6 @@ bus so retro programs that talk to the speaker, joystick port, or parallel interface can do *something* useful instead of silently no-oping. Think of it as a tiny museum exhibit for vintage BASIC hardware hacking. -- **VIEW / WINDOW / PALETTE** — graphics viewport, coordinate mapping, and - palette remapping - **MBF format support for binary LOAD** — convert Microsoft Binary Format float constants when loading files saved by the original GWBASIC.EXE @@ -36,8 +34,6 @@ ## Known Limitations -- Binary files use IEEE 754 floats, not MBF — files saved here are not - byte-compatible with original GWBASIC.EXE binary format - String garbage collection not implemented (uses `malloc`/`free` instead) - Maximum 256 variables, 64 arrays, 16 FOR nesting, 24 GOSUB nesting, 16 WHILE nesting diff --git a/include/graphics.h b/include/graphics.h index 5cd7a1b..cbb6aae 100644 --- a/include/graphics.h +++ b/include/graphics.h @@ -50,6 +50,21 @@ void gfx_sprite_put(int x, int y, const int16_t *buf, int bufsize, int action); /* Calculate required array element count for a sprite rectangle. */ int gfx_sprite_size(int x1, int y1, int x2, int y2); +/* VIEW / WINDOW / PALETTE */ +void gfx_view(bool screen_flag, int x1, int y1, int x2, int y2, + int fill, bool has_fill, int border, bool has_border); +void gfx_view_reset(void); +void gfx_window(bool screen_flag, double x1, double y1, double x2, double y2); +void gfx_window_reset(void); +void gfx_palette_set(int attr, int color); +void gfx_palette_reset(void); + +/* Coordinate mapping (logical ↔ physical) */ +int gfx_map_x(double x); +int gfx_map_y(double y); +double gfx_pmap(double coord, int func); +bool gfx_has_window(void); + /* CGA framebuffer PEEK/POKE (segment 0xB800 in graphics mode) */ uint8_t gfx_cga_peek(uint16_t offset); void gfx_cga_poke(uint16_t offset, uint8_t val); diff --git a/include/gwbasic.h b/include/gwbasic.h index b8bdef2..802e8fd 100644 --- a/include/gwbasic.h +++ b/include/gwbasic.h @@ -10,7 +10,7 @@ #include "gw_math.h" #include "strings.h" -#define GW_VERSION "0.12.0" +#define GW_VERSION "0.13.0" #define GW_BANNER "GW-BASIC " GW_VERSION /* Tokenizer */ diff --git a/src/eval.c b/src/eval.c index ce88f86..7e6d847 100644 --- a/src/eval.c +++ b/src/eval.c @@ -987,6 +987,18 @@ static gw_value_t eval_atom(void) v.fval = (float)(tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec); return v; } + if (xtok == XSTMT_PMAP) { + gw_chrget(); + gw_expect('('); + gw_value_t arg = gw_eval_num(); + gw_expect(','); + int func = gw_eval_int(); + gw_expect_rparen(); + gw_value_t v; + v.type = VT_SNG; + v.fval = (float)gfx_pmap(gw_to_dbl(&arg), func); + return v; + } gw.text_ptr = save; } diff --git a/src/fileio.c b/src/fileio.c index de4d8f0..3ab4c04 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -733,10 +733,12 @@ gw_value_t gw_fn_cvs(gw_value_t *s) { if (s->type != VT_STR) gw_error(ERR_TM); if (s->sval.len < 4) gw_error(ERR_FC); + mbf_single_t mbf; + memcpy(&mbf, s->sval.data, 4); + gw_str_free(&s->sval); gw_value_t v; v.type = VT_SNG; - memcpy(&v.fval, s->sval.data, 4); - gw_str_free(&s->sval); + v.fval = gw_mbf_to_ieee_single(mbf); return v; } @@ -744,10 +746,12 @@ gw_value_t gw_fn_cvd(gw_value_t *s) { if (s->type != VT_STR) gw_error(ERR_TM); if (s->sval.len < 8) gw_error(ERR_FC); + mbf_double_t mbf; + memcpy(&mbf, s->sval.data, 8); + gw_str_free(&s->sval); gw_value_t v; v.type = VT_DBL; - memcpy(&v.dval, s->sval.data, 8); - gw_str_free(&s->sval); + v.dval = gw_mbf_to_ieee_double(mbf); return v; } @@ -763,18 +767,20 @@ gw_value_t gw_fn_mki(int16_t n) gw_value_t gw_fn_mks(float f) { + mbf_single_t mbf = gw_ieee_to_mbf_single(f); gw_value_t v; v.type = VT_STR; v.sval = gw_str_alloc(4); - memcpy(v.sval.data, &f, 4); + memcpy(v.sval.data, &mbf, 4); return v; } gw_value_t gw_fn_mkd(double d) { + mbf_double_t mbf = gw_ieee_to_mbf_double(d); gw_value_t v; v.type = VT_STR; v.sval = gw_str_alloc(8); - memcpy(v.sval.data, &d, 8); + memcpy(v.sval.data, &mbf, 8); return v; } diff --git a/src/graphics.c b/src/graphics.c index 29d25f6..1e01507 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -13,13 +13,35 @@ static int current_color = 1; static int last_x, last_y; /* CGA default palette (RGBI) */ -static uint32_t palette[16] = { +static const uint32_t default_palette[16] = { 0x000000, 0x0000AA, 0x00AA00, 0x00AAAA, 0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA, 0x555555, 0x5555FF, 0x55FF55, 0x55FFFF, 0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF, }; +/* Active palette: palette_map[attr] gives the physical color index */ +static int palette_map[16]; +static uint32_t palette[16]; + +/* Viewport state (VIEW) */ +static bool vp_active; +static bool vp_screen; /* VIEW SCREEN flag */ +static int vp_x1, vp_y1, vp_x2, vp_y2; + +/* Window state (WINDOW) */ +static bool win_active; +static bool win_screen; /* WINDOW SCREEN flag */ +static double win_x1, win_y1, win_x2, win_y2; + +static void reset_palette(void) +{ + for (int i = 0; i < 16; i++) { + palette_map[i] = i; + palette[i] = default_palette[i]; + } +} + bool gfx_active(void) { return framebuf != NULL; } int gfx_get_mode(void) { return screen_mode; } void gfx_set_color(int c) { current_color = c; } @@ -42,6 +64,11 @@ void gfx_init(int mode) current_color = (mode == 2) ? 1 : 3; last_x = 0; last_y = 0; + vp_active = false; + vp_x1 = 0; vp_y1 = 0; + vp_x2 = fb_width - 1; vp_y2 = fb_height - 1; + win_active = false; + reset_palette(); } void gfx_shutdown(void) @@ -51,17 +78,40 @@ void gfx_shutdown(void) fb_width = 0; fb_height = 0; screen_mode = 0; + vp_active = false; + win_active = false; + reset_palette(); } void gfx_cls(void) { - if (framebuf) + if (!framebuf) return; + if (vp_active) { + for (int y = vp_y1; y <= vp_y2; y++) + for (int x = vp_x1; x <= vp_x2; x++) + framebuf[y * fb_width + x] = 0; + } else { memset(framebuf, 0, fb_width * fb_height); + } +} + +/* Clip bounds: viewport when active, otherwise full framebuffer */ +static inline void clip_bounds(int *cx1, int *cy1, int *cx2, int *cy2) +{ + if (vp_active) { + *cx1 = vp_x1; *cy1 = vp_y1; + *cx2 = vp_x2; *cy2 = vp_y2; + } else { + *cx1 = 0; *cy1 = 0; + *cx2 = fb_width - 1; *cy2 = fb_height - 1; + } } static inline void set_pixel(int x, int y, int color) { - if (x >= 0 && x < fb_width && y >= 0 && y < fb_height) + int cx1, cy1, cx2, cy2; + clip_bounds(&cx1, &cy1, &cx2, &cy2); + if (x >= cx1 && x <= cx2 && y >= cy1 && y <= cy2) framebuf[y * fb_width + x] = color; } @@ -350,10 +400,10 @@ void gfx_flush(void) /* DCS q - enter Sixel mode */ EMIT("\033Pq"); - /* Define palette */ + /* Define palette (using palette mapping) */ for (int c = 0; c < 16; c++) { if (!color_used[c]) continue; - uint32_t rgb = palette[c]; + uint32_t rgb = default_palette[palette_map[c]]; int r = ((rgb >> 16) & 0xFF) * 100 / 255; int g = ((rgb >> 8) & 0xFF) * 100 / 255; int b = (rgb & 0xFF) * 100 / 255; @@ -462,6 +512,165 @@ void gfx_flush(void) free(buf); } +/* ================================================================ + * VIEW / WINDOW / PALETTE + * ================================================================ */ + +void gfx_view(bool screen_flag, int x1, int y1, int x2, int y2, + int fill, bool has_fill, int border, bool has_border) +{ + if (!framebuf) return; + + /* Normalize */ + if (x1 > x2) { int t = x1; x1 = x2; x2 = t; } + if (y1 > y2) { int t = y1; y1 = y2; y2 = t; } + + /* Clamp to screen */ + if (x1 < 0) x1 = 0; + if (y1 < 0) y1 = 0; + if (x2 >= fb_width) x2 = fb_width - 1; + if (y2 >= fb_height) y2 = fb_height - 1; + + vp_active = true; + vp_screen = screen_flag; + vp_x1 = x1; vp_y1 = y1; + vp_x2 = x2; vp_y2 = y2; + + /* Draw border (outside viewport, on full framebuffer) */ + if (has_border) { + bool save = vp_active; + vp_active = false; + if (x1 > 0) draw_line(x1 - 1, y1 > 0 ? y1 - 1 : y1, x1 - 1, y2 < fb_height - 1 ? y2 + 1 : y2, border); + if (x2 < fb_width - 1) draw_line(x2 + 1, y1 > 0 ? y1 - 1 : y1, x2 + 1, y2 < fb_height - 1 ? y2 + 1 : y2, border); + if (y1 > 0) draw_line(x1 > 0 ? x1 - 1 : x1, y1 - 1, x2 < fb_width - 1 ? x2 + 1 : x2, y1 - 1, border); + if (y2 < fb_height - 1) draw_line(x1 > 0 ? x1 - 1 : x1, y2 + 1, x2 < fb_width - 1 ? x2 + 1 : x2, y2 + 1, border); + vp_active = save; + } + + /* Fill viewport */ + if (has_fill) { + for (int y = vp_y1; y <= vp_y2; y++) + for (int x = vp_x1; x <= vp_x2; x++) + framebuf[y * fb_width + x] = fill; + } + + /* Reset last position to viewport origin */ + last_x = vp_x1; + last_y = vp_y1; + + /* Reset WINDOW when VIEW changes */ + win_active = false; +} + +void gfx_view_reset(void) +{ + vp_active = false; + if (framebuf) { + vp_x1 = 0; vp_y1 = 0; + vp_x2 = fb_width - 1; vp_y2 = fb_height - 1; + } + win_active = false; + last_x = 0; + last_y = 0; +} + +void gfx_window(bool screen_flag, double x1, double y1, double x2, double y2) +{ + win_active = true; + win_screen = screen_flag; + win_x1 = x1; win_y1 = y1; + win_x2 = x2; win_y2 = y2; +} + +void gfx_window_reset(void) +{ + win_active = false; +} + +bool gfx_has_window(void) +{ + return win_active; +} + +int gfx_map_x(double x) +{ + int left = vp_active ? vp_x1 : 0; + int right = vp_active ? vp_x2 : (fb_width - 1); + + if (win_active) { + if (win_x2 == win_x1) return left; + return left + (int)((x - win_x1) / (win_x2 - win_x1) * (right - left) + 0.5); + } + if (vp_active && !vp_screen) + return left + (int)x; + return (int)x; +} + +int gfx_map_y(double y) +{ + int top = vp_active ? vp_y1 : 0; + int bottom = vp_active ? vp_y2 : (fb_height - 1); + + if (win_active) { + if (win_y2 == win_y1) return top; + if (win_screen) { + /* WINDOW SCREEN: Y increases downward */ + return top + (int)((y - win_y1) / (win_y2 - win_y1) * (bottom - top) + 0.5); + } else { + /* WINDOW (Cartesian): Y increases upward */ + return bottom - (int)((y - win_y1) / (win_y2 - win_y1) * (bottom - top) + 0.5); + } + } + if (vp_active && !vp_screen) + return top + (int)y; + return (int)y; +} + +double gfx_pmap(double coord, int func) +{ + int left = vp_active ? vp_x1 : 0; + int right = vp_active ? vp_x2 : (fb_width - 1); + int top = vp_active ? vp_y1 : 0; + int bottom = vp_active ? vp_y2 : (fb_height - 1); + + if (!win_active) return coord; + + switch (func) { + case 0: /* logical X → physical X */ + if (win_x2 == win_x1) return left; + return left + (coord - win_x1) / (win_x2 - win_x1) * (right - left); + case 1: /* logical Y → physical Y */ + if (win_y2 == win_y1) return top; + if (win_screen) + return top + (coord - win_y1) / (win_y2 - win_y1) * (bottom - top); + else + return bottom - (coord - win_y1) / (win_y2 - win_y1) * (bottom - top); + case 2: /* physical X → logical X */ + if (right == left) return win_x1; + return win_x1 + (coord - left) / (double)(right - left) * (win_x2 - win_x1); + case 3: /* physical Y → logical Y */ + if (bottom == top) return win_y1; + if (win_screen) + return win_y1 + (coord - top) / (double)(bottom - top) * (win_y2 - win_y1); + else + return win_y1 + (bottom - coord) / (double)(bottom - top) * (win_y2 - win_y1); + default: + return coord; + } +} + +void gfx_palette_set(int attr, int color) +{ + if (attr < 0 || attr > 15 || color < 0 || color > 15) return; + palette_map[attr] = color; + palette[attr] = default_palette[color]; +} + +void gfx_palette_reset(void) +{ + reset_palette(); +} + /* * CGA framebuffer PEEK/POKE. * diff --git a/src/interp.c b/src/interp.c index 8af219a..34916b8 100644 --- a/src/interp.c +++ b/src/interp.c @@ -723,22 +723,41 @@ void gw_stmt_chain(void) } } +/* Evaluate a graphics coordinate, mapping through WINDOW/VIEW */ +static int eval_gfx_x(void) +{ + if (gfx_has_window()) { + gw_value_t v = gw_eval_num(); + return gfx_map_x(gw_to_dbl(&v)); + } + return gfx_map_x((double)gw_eval_int()); +} + +static int eval_gfx_y(void) +{ + if (gfx_has_window()) { + gw_value_t v = gw_eval_num(); + return gfx_map_y(gw_to_dbl(&v)); + } + return gfx_map_y((double)gw_eval_int()); +} + /* Graphics GET (x1,y1)-(x2,y2), array */ static void stmt_gfx_get(void) { gw_expect('('); - int x1 = gw_eval_int(); + int x1 = eval_gfx_x(); gw_skip_spaces(); gw_expect(','); - int y1 = gw_eval_int(); + int y1 = eval_gfx_y(); gw_expect_rparen(); gw_skip_spaces(); gw_expect(TOK_MINUS); gw_expect('('); - int x2 = gw_eval_int(); + int x2 = eval_gfx_x(); gw_skip_spaces(); gw_expect(','); - int y2 = gw_eval_int(); + int y2 = eval_gfx_y(); gw_expect_rparen(); gw_skip_spaces(); gw_expect(','); @@ -781,10 +800,10 @@ static void stmt_gfx_get(void) static void stmt_gfx_put(void) { gw_expect('('); - int x = gw_eval_int(); + int x = eval_gfx_x(); gw_skip_spaces(); gw_expect(','); - int y = gw_eval_int(); + int y = eval_gfx_y(); gw_expect_rparen(); gw_skip_spaces(); gw_expect(','); @@ -1002,9 +1021,9 @@ void gw_exec_stmt(void) gw_chrget(); gw_skip_spaces(); gw_expect('('); - int cx = gw_eval_int(); + int cx = eval_gfx_x(); gw_expect(','); - int cy = gw_eval_int(); + int cy = eval_gfx_y(); gw_expect_rparen(); gw_expect(','); int radius = gw_eval_int(); @@ -1059,9 +1078,9 @@ void gw_exec_stmt(void) gw_chrget(); gw_skip_spaces(); gw_expect('('); - int px = gw_eval_int(); + int px = eval_gfx_x(); gw_expect(','); - int py = gw_eval_int(); + int py = eval_gfx_y(); gw_expect_rparen(); int fill_c = gfx_get_color(), border_c = 0; gw_skip_spaces(); @@ -1228,12 +1247,101 @@ void gw_exec_stmt(void) } gw_error(ERR_SN); } - /* Stubs: VIEW, WINDOW, PALETTE */ - if (xstmt == XSTMT_VIEW || - xstmt == XSTMT_WINDOW || xstmt == XSTMT_PALETTE) { + /* VIEW [[SCREEN] (x1,y1)-(x2,y2) [,[fill][,border]]] */ + if (xstmt == XSTMT_VIEW) { gw_chrget(); - while (gw_chrgot() && gw_chrgot() != ':' && gw_chrgot() != TOK_ELSE) - gw.text_ptr++; + gw_skip_spaces(); + if (gw_chrgot() == 0 || gw_chrgot() == ':' || gw_chrgot() == TOK_ELSE) { + gfx_view_reset(); + return; + } + bool scr = false; + if (gw_chrgot() == TOK_SCREEN) { + scr = true; + gw_chrget(); + gw_skip_spaces(); + } + /* Check for PRINT after VIEW to handle VIEW PRINT (ignore) */ + if (gw_chrgot() == TOK_PRINT) { + while (gw_chrgot() && gw_chrgot() != ':' && gw_chrgot() != TOK_ELSE) + gw.text_ptr++; + return; + } + gw_expect('('); + int x1 = gw_eval_int(); + gw_expect(','); + int y1 = gw_eval_int(); + gw_expect_rparen(); + gw_skip_spaces(); + gw_expect(TOK_MINUS); + gw_expect('('); + int x2 = gw_eval_int(); + gw_expect(','); + int y2 = gw_eval_int(); + gw_expect_rparen(); + int fill = 0, border = 0; + bool has_fill = false, has_border = false; + gw_skip_spaces(); + if (gw_chrgot() == ',') { + gw_chrget(); + gw_skip_spaces(); + if (gw_chrgot() != ',' && gw_chrgot() != 0 && gw_chrgot() != ':' && + gw_chrgot() != TOK_ELSE) { + fill = gw_eval_int(); + has_fill = true; + } + gw_skip_spaces(); + if (gw_chrgot() == ',') { + gw_chrget(); + border = gw_eval_int(); + has_border = true; + } + } + gfx_view(scr, x1, y1, x2, y2, fill, has_fill, border, has_border); + return; + } + /* WINDOW [[SCREEN] (x1,y1)-(x2,y2)] */ + if (xstmt == XSTMT_WINDOW) { + gw_chrget(); + gw_skip_spaces(); + if (gw_chrgot() == 0 || gw_chrgot() == ':' || gw_chrgot() == TOK_ELSE) { + gfx_window_reset(); + return; + } + bool scr = false; + if (gw_chrgot() == TOK_SCREEN) { + scr = true; + gw_chrget(); + gw_skip_spaces(); + } + gw_expect('('); + gw_value_t vx1 = gw_eval_num(); + gw_expect(','); + gw_value_t vy1 = gw_eval_num(); + gw_expect_rparen(); + gw_skip_spaces(); + gw_expect(TOK_MINUS); + gw_expect('('); + gw_value_t vx2 = gw_eval_num(); + gw_expect(','); + gw_value_t vy2 = gw_eval_num(); + gw_expect_rparen(); + gfx_window(scr, gw_to_dbl(&vx1), gw_to_dbl(&vy1), + gw_to_dbl(&vx2), gw_to_dbl(&vy2)); + return; + } + /* PALETTE [attribute, color] */ + if (xstmt == XSTMT_PALETTE) { + gw_chrget(); + gw_skip_spaces(); + if (gw_chrgot() == 0 || gw_chrgot() == ':' || gw_chrgot() == TOK_ELSE) { + gfx_palette_reset(); + return; + } + int attr = gw_eval_int(); + gw_expect(','); + int color = gw_eval_int(); + gfx_palette_set(attr, color); return; } gw.text_ptr = save; @@ -2188,9 +2296,9 @@ void gw_exec_stmt(void) gw_skip_spaces(); if (gw_chrgot() == '(') { gw_chrget(); - x1 = gw_eval_int(); + x1 = eval_gfx_x(); gw_expect(','); - y1 = gw_eval_int(); + y1 = eval_gfx_y(); gw_expect_rparen(); } else { gfx_get_last(&x1, &y1); @@ -2198,9 +2306,9 @@ void gw_exec_stmt(void) gw_skip_spaces(); gw_expect(TOK_MINUS); gw_expect('('); - x2 = gw_eval_int(); + x2 = eval_gfx_x(); gw_expect(','); - y2 = gw_eval_int(); + y2 = eval_gfx_y(); gw_expect_rparen(); int color = gfx_get_color(); int style = GFX_LINE; @@ -2484,9 +2592,9 @@ void gw_exec_stmt(void) gw_chrget(); gw_skip_spaces(); gw_expect('('); - int px = gw_eval_int(); + int px = eval_gfx_x(); gw_expect(','); - int py = gw_eval_int(); + int py = eval_gfx_y(); gw_expect_rparen(); int color = is_preset ? 0 : gfx_get_color(); gw_skip_spaces(); diff --git a/src/math_float.c b/src/math_float.c index 561a3e6..6cd0cc2 100644 --- a/src/math_float.c +++ b/src/math_float.c @@ -256,7 +256,8 @@ float gw_mbf_to_ieee_single(mbf_single_t mbf) int exp = (int)mbf.exponent - 128 - 1 + 127; /* MBF bias 128, IEEE bias 127 */ if (exp < 0 || exp > 254) return 0.0f; - ieee = ((uint32_t)sign << 24) | ((uint32_t)exp << 23) | ((mantissa >> 1) & 0x7FFFFF); + /* Hidden 1 at bit 23 of mantissa, IEEE stores bits 0-22 (fraction only) */ + ieee = ((uint32_t)sign << 24) | ((uint32_t)exp << 23) | (mantissa & 0x7FFFFF); float result; memcpy(&result, &ieee, 4); return result; @@ -275,7 +276,9 @@ double gw_mbf_to_ieee_double(mbf_double_t mbf) int exp = (int)mbf.exponent - 128 - 1 + 1023; if (exp < 0 || exp > 2046) return 0.0; - ieee = ((uint64_t)sign << 56) | ((uint64_t)exp << 52) | ((mantissa >> 4) & 0x000FFFFFFFFFFFFF); + /* Hidden 1 at bit 55 of mantissa, IEEE stores bits 0-51 (fraction only). + * MBF has 55 fraction bits (3 more than IEEE), shift right 3 to align. */ + ieee = ((uint64_t)sign << 56) | ((uint64_t)exp << 52) | ((mantissa >> 3) & 0x000FFFFFFFFFFFFF); double result; memcpy(&result, &ieee, 8); return result; @@ -297,7 +300,7 @@ mbf_single_t gw_ieee_to_mbf_single(float f) if (exp > 255) exp = 255; mbf.exponent = exp; - mantissa <<= 1; + /* Hidden 1 at bit 23, sign replaces it in byte 2 bit 7 */ mbf.mantissa[0] = mantissa & 0xFF; mbf.mantissa[1] = (mantissa >> 8) & 0xFF; mbf.mantissa[2] = ((mantissa >> 16) & 0x7F) | (sign << 7); @@ -321,7 +324,9 @@ mbf_double_t gw_ieee_to_mbf_double(double d) if (exp > 255) exp = 255; mbf.exponent = exp; - mantissa <<= 4; + /* Hidden 1 at bit 52, MBF stores 55 fraction bits + sign. + * Shift left 3 to place hidden 1 at bit 55 (byte 6 bit 7 = sign). */ + mantissa <<= 3; for (int i = 0; i < 7; i++) { if (i == 6) mbf.mantissa[i] = ((mantissa >> (i * 8)) & 0x7F) | (sign << 7); diff --git a/tests/expected/mbf_format.expected b/tests/expected/mbf_format.expected new file mode 100644 index 0000000..a66eda9 --- /dev/null +++ b/tests/expected/mbf_format.expected @@ -0,0 +1,15 @@ + 0 + 1 +-1 + 3.14 + 100.5 + 0 + 1 +-1 + 3.14 + 0 + 32767 +-32768 +-1 + 0 0 0 129 +DONE diff --git a/tests/expected/view_window.expected b/tests/expected/view_window.expected new file mode 100644 index 0000000..06e68a5 --- /dev/null +++ b/tests/expected/view_window.expected @@ -0,0 +1,7 @@ +PMAP(0,0)= 0 +PMAP(0,1)= 199 +PMAP(100,0)= 639 +PMAP(100,1)= 0 +PMAP(0,2)= 0 +PMAP(199,3)= 0 +VIEW/WINDOW/PALETTE OK diff --git a/tests/programs/mbf_format.bas b/tests/programs/mbf_format.bas new file mode 100644 index 0000000..c340c13 --- /dev/null +++ b/tests/programs/mbf_format.bas @@ -0,0 +1,22 @@ +10 REM MBF format conversion test +20 REM Test MKS$/CVS roundtrip +30 PRINT CVS(MKS$(0)) +40 PRINT CVS(MKS$(1)) +50 PRINT CVS(MKS$(-1)) +60 PRINT CVS(MKS$(3.14)) +70 PRINT CVS(MKS$(100.5)) +80 REM Test MKD$/CVD roundtrip +90 PRINT CVD(MKD$(0#)) +100 PRINT CVD(MKD$(1#)) +110 PRINT CVD(MKD$(-1#)) +120 PRINT CVD(MKD$(3.14#)) +130 REM Test MKI$/CVI roundtrip +140 PRINT CVI(MKI$(0)) +150 PRINT CVI(MKI$(32767)) +160 PRINT CVI(MKI$(-32768)) +170 PRINT CVI(MKI$(-1)) +180 REM Test MBF byte encoding +190 REM MKS$(1) in MBF should be: 00 00 00 81 (exponent=129, mantissa=0.5) +200 A$ = MKS$(1) +210 PRINT ASC(MID$(A$,1,1)); ASC(MID$(A$,2,1)); ASC(MID$(A$,3,1)); ASC(MID$(A$,4,1)) +220 PRINT "DONE" diff --git a/tests/programs/view_window.bas b/tests/programs/view_window.bas new file mode 100644 index 0000000..7687e50 --- /dev/null +++ b/tests/programs/view_window.bas @@ -0,0 +1,21 @@ +10 REM VIEW / WINDOW / PALETTE test +20 SCREEN 2 +30 REM Test WINDOW coordinate mapping +40 WINDOW (0,0)-(100,100) +50 REM PMAP: logical to physical +60 PRINT "PMAP(0,0)="; PMAP(0,0) +70 PRINT "PMAP(0,1)="; PMAP(0,1) +80 PRINT "PMAP(100,0)="; PMAP(100,0) +90 PRINT "PMAP(100,1)="; PMAP(100,1) +100 REM PMAP: physical to logical +110 PRINT "PMAP(0,2)="; PMAP(0,2) +120 PRINT "PMAP(199,3)="; PMAP(199,3) +130 WINDOW +140 REM Test VIEW +150 VIEW (10,10)-(100,50) +160 REM After VIEW, drawing clips to viewport +170 VIEW +180 REM Test PALETTE (just reset, no visual check) +190 PALETTE +200 SCREEN 0 +210 PRINT "VIEW/WINDOW/PALETTE OK"