Implement VIEW/WINDOW/PALETTE, PMAP, fix MBF float format, update to v0.13.0

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).
This commit is contained in:
Eremey Valetov
2026-03-10 22:20:58 -04:00
parent 4551c88a50
commit 0bacfcef6c
15 changed files with 478 additions and 47 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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 (015). 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
```

View File

@@ -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

View File

@@ -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);

View File

@@ -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 */

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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.
*

View File

@@ -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();

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"