From 66479b5d6ea7716981fb2c9eecd1d755b5f0ad26 Mon Sep 17 00:00:00 2001 From: Eremey Valetov Date: Tue, 10 Feb 2026 12:11:25 -0500 Subject: [PATCH] Phase 4: CHAIN, RUN "file", random-access I/O, MBF conversions Add CHAIN statement for loading and running chained programs with optional ALL flag to preserve variables. Extend RUN to accept a filename string argument. Implement random-access file I/O with FIELD, LSET, RSET, PUT#, GET# and the MBF conversion functions CVI/CVS/CVD/MKI$/MKS$/MKD$. Add COMMON statement (parse and skip). Five new test programs covering all new features (27 total). --- include/gwbasic.h | 21 ++- include/types.h | 11 +- src/eval.c | 51 ++++- src/fileio.c | 312 ++++++++++++++++++++++++++++++- src/interp.c | 146 ++++++++++++++- src/program_io.c | 8 +- tests/programs/chain_target.bas | 2 + tests/programs/chain_test.bas | 4 + tests/programs/mkicvi.bas | 7 + tests/programs/random_access.bas | 16 ++ tests/programs/run_file.bas | 4 + 11 files changed, 571 insertions(+), 11 deletions(-) create mode 100644 tests/programs/chain_target.bas create mode 100644 tests/programs/chain_test.bas create mode 100644 tests/programs/mkicvi.bas create mode 100644 tests/programs/random_access.bas create mode 100644 tests/programs/run_file.bas diff --git a/include/gwbasic.h b/include/gwbasic.h index 708e0ce..8507e0b 100644 --- a/include/gwbasic.h +++ b/include/gwbasic.h @@ -10,7 +10,7 @@ #include "gw_math.h" #include "strings.h" -#define GW_VERSION "0.3.0" +#define GW_VERSION "0.4.0" #define GW_BANNER "GW-BASIC " GW_VERSION /* Tokenizer */ @@ -59,6 +59,7 @@ int gw_file_eof(int num); void gw_stmt_save(void); void gw_stmt_load(void); void gw_stmt_merge(void); +void gw_stmt_load_internal(const char *filename, bool clear); /* PRINT USING (print_using.c) */ void gw_print_using(FILE *fp); @@ -66,6 +67,24 @@ void gw_print_using(FILE *fp); /* MID$ assignment (interp.c) */ void gw_stmt_mid_assign(void); +/* Random-access file I/O (fileio.c) */ +void gw_stmt_field(void); +void gw_stmt_lset(void); +void gw_stmt_rset(void); +void gw_stmt_put(void); +void gw_stmt_get(void); + +/* MBF conversion functions (fileio.c) */ +gw_value_t gw_fn_cvi(gw_value_t *s); +gw_value_t gw_fn_cvs(gw_value_t *s); +gw_value_t gw_fn_cvd(gw_value_t *s); +gw_value_t gw_fn_mki(int16_t n); +gw_value_t gw_fn_mks(float f); +gw_value_t gw_fn_mkd(double d); + +/* CHAIN (interp.c) */ +void gw_stmt_chain(void); + /* Error recovery for run loop */ extern jmp_buf gw_run_jmp; diff --git a/include/types.h b/include/types.h index 0be0016..ef69516 100644 --- a/include/types.h +++ b/include/types.h @@ -90,9 +90,18 @@ typedef struct { /* File entry for OPEN/CLOSE file table */ typedef struct { FILE *fp; - int mode; /* 0=closed, 'I'=input, 'O'=output, 'A'=append */ + int mode; /* 0=closed, 'I'=input, 'O'=output, 'A'=append, 'R'=random */ int file_num; bool eof_flag; + int record_len; /* record length for random access (default 128) */ + uint8_t *field_buf; /* FIELD buffer (malloc'd, record_len bytes) */ + int field_count; /* number of FIELDed variables */ + struct field_var { + char name[2]; + gw_valtype_t type; + int offset; + int width; + } fields[32]; } file_entry_t; /* MBF (Microsoft Binary Format) types for file compatibility */ diff --git a/src/eval.c b/src/eval.c index d9d084b..e047580 100644 --- a/src/eval.c +++ b/src/eval.c @@ -727,12 +727,59 @@ static gw_value_t eval_atom(void) return eval_function(TOK_PREFIX_FF, func); } - /* Extended function (0xFD prefix) */ + /* Extended function (0xFD prefix): CVI, CVS, CVD, MKI$, MKS$, MKD$ */ if (tok == TOK_PREFIX_FD) { gw_chrget(); uint8_t func = gw_chrgot(); gw_chrget(); - return eval_function(TOK_PREFIX_FD, func); + + switch (func) { + case XFUNC_CVI: + { + gw_expect('('); + gw_value_t arg = gw_eval_str(); + gw_expect_rparen(); + return gw_fn_cvi(&arg); + } + case XFUNC_CVS: + { + gw_expect('('); + gw_value_t arg = gw_eval_str(); + gw_expect_rparen(); + return gw_fn_cvs(&arg); + } + case XFUNC_CVD: + { + gw_expect('('); + gw_value_t arg = gw_eval_str(); + gw_expect_rparen(); + return gw_fn_cvd(&arg); + } + case XFUNC_MKI: + { + gw_expect('('); + int16_t n = gw_eval_int(); + gw_expect_rparen(); + return gw_fn_mki(n); + } + case XFUNC_MKS: + { + gw_expect('('); + gw_value_t arg = gw_eval_num(); + gw_expect_rparen(); + return gw_fn_mks(gw_to_sng(&arg)); + } + case XFUNC_MKD: + { + gw_expect('('); + gw_value_t arg = gw_eval_num(); + gw_expect_rparen(); + return gw_fn_mkd(gw_to_dbl(&arg)); + } + default: + gw_error(ERR_SN); + break; + } } /* STRING$ function (single-byte token but acts like function) */ diff --git a/src/fileio.c b/src/fileio.c index b5b0cc8..de4d8f0 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -22,6 +22,10 @@ void gw_file_close_all(void) gw.files[i].mode = 0; gw.files[i].eof_flag = false; } + free(gw.files[i].field_buf); + gw.files[i].field_buf = NULL; + gw.files[i].field_count = 0; + gw.files[i].record_len = 0; } } @@ -172,12 +176,31 @@ void gw_stmt_open(void) gw.files[file_num].mode = mode; gw.files[file_num].file_num = file_num; gw.files[file_num].eof_flag = false; + gw.files[file_num].field_buf = NULL; + gw.files[file_num].field_count = 0; - /* Skip optional record length: , reclen */ + /* Parse optional record length */ + int reclen = 128; gw_skip_spaces(); - if (gw_chrgot() == ',') { + /* Modern syntax: LEN = n */ + if (gw_is_letter(gw_chrgot()) && toupper(gw_chrgot()) == 'L') { + /* Skip LEN */ + while (gw_is_letter(gw_chrgot())) gw_chrget(); + gw_skip_spaces(); + if (gw_chrgot() == TOK_EQ) { + gw_chrget(); + reclen = gw_eval_int(); + } + } else if (gw_chrgot() == ',') { + /* Compact syntax: , reclen */ gw_chrget(); - gw_eval_int(); /* consume and ignore record length */ + reclen = gw_eval_int(); + } + + gw.files[file_num].record_len = reclen; + if (mode == 'R') { + gw.files[file_num].field_buf = calloc(1, reclen); + if (!gw.files[file_num].field_buf) gw_error(ERR_OM); } } @@ -202,6 +225,10 @@ void gw_stmt_close(void) gw.files[num].fp = NULL; gw.files[num].mode = 0; gw.files[num].eof_flag = false; + free(gw.files[num].field_buf); + gw.files[num].field_buf = NULL; + gw.files[num].field_count = 0; + gw.files[num].record_len = 0; } gw_skip_spaces(); if (gw_chrgot() != ',') @@ -472,3 +499,282 @@ void gw_stmt_line_input_file(void) if (feof(f->fp)) f->eof_flag = true; } + +/* ================================================================ + * Random-access file I/O: FIELD, LSET, RSET, PUT, GET + * ================================================================ */ + +static void field_buf_to_vars(file_entry_t *f); +static void vars_to_field_buf(file_entry_t *f); + +/* FIELD #n, width AS var$ [, width AS var$ ...] */ +void gw_stmt_field(void) +{ + gw_skip_spaces(); + if (gw_chrgot() == '#') + gw_chrget(); + int num = gw_eval_int(); + file_entry_t *f = gw_file_get(num); + if (f->mode != 'R') + gw_error(ERR_BM); + + gw_skip_spaces(); + if (gw_chrgot() == ',') + gw_chrget(); + + f->field_count = 0; + int offset = 0; + + for (;;) { + gw_skip_spaces(); + if (gw_chrgot() == 0 || gw_chrgot() == ':') + break; + + int width = gw_eval_int(); + if (width < 0) gw_error(ERR_FC); + if (offset + width > f->record_len) gw_error(ERR_FO); + + gw_skip_spaces(); + /* Skip AS keyword */ + if (gw_is_letter(gw_chrgot()) && toupper(gw_chrgot()) == 'A') { + gw_chrget(); + if (gw_is_letter(gw_chrgot()) && toupper(gw_chrgot()) == 'S') + gw_chrget(); + } + + gw_skip_spaces(); + char name[2]; + gw_valtype_t type = gw_parse_varname(name); + if (type != VT_STR) gw_error(ERR_TM); + + if (f->field_count >= 32) gw_error(ERR_FO); + f->fields[f->field_count].name[0] = name[0]; + f->fields[f->field_count].name[1] = name[1]; + f->fields[f->field_count].type = type; + f->fields[f->field_count].offset = offset; + f->fields[f->field_count].width = width; + f->field_count++; + + offset += width; + + gw_skip_spaces(); + if (gw_chrgot() == ',') { + gw_chrget(); + continue; + } + break; + } + + /* Initialize FIELD variables with proper widths (space-filled) */ + field_buf_to_vars(f); +} + +/* Copy field buffer data into the FIELD variables */ +static void field_buf_to_vars(file_entry_t *f) +{ + for (int i = 0; i < f->field_count; i++) { + var_entry_t *var = gw_var_find_or_create(f->fields[i].name, + f->fields[i].type); + gw_str_free(&var->val.sval); + var->val.type = VT_STR; + var->val.sval = gw_str_alloc(f->fields[i].width); + memcpy(var->val.sval.data, + f->field_buf + f->fields[i].offset, + f->fields[i].width); + } +} + +/* Copy FIELD variables into the field buffer */ +static void vars_to_field_buf(file_entry_t *f) +{ + for (int i = 0; i < f->field_count; i++) { + var_entry_t *var = gw_var_find_or_create(f->fields[i].name, + f->fields[i].type); + int width = f->fields[i].width; + int len = (var->val.type == VT_STR) ? var->val.sval.len : 0; + int copy = (len < width) ? len : width; + if (copy > 0) + memcpy(f->field_buf + f->fields[i].offset, + var->val.sval.data, copy); + if (copy < width) + memset(f->field_buf + f->fields[i].offset + copy, ' ', + width - copy); + } +} + +/* LSET var$ = expr$ - left-justify into field variable */ +void gw_stmt_lset(void) +{ + gw_skip_spaces(); + char name[2]; + gw_valtype_t type = gw_parse_varname(name); + if (type != VT_STR) gw_error(ERR_TM); + var_entry_t *var = gw_var_find_or_create(name, type); + + gw_skip_spaces(); + gw_expect(TOK_EQ); + gw_value_t rhs = gw_eval_str(); + + int target_len = var->val.sval.len; + if (target_len == 0) { + gw_str_free(&rhs.sval); + return; + } + + int copy = (rhs.sval.len < target_len) ? rhs.sval.len : target_len; + memcpy(var->val.sval.data, rhs.sval.data, copy); + if (copy < target_len) + memset(var->val.sval.data + copy, ' ', target_len - copy); + gw_str_free(&rhs.sval); +} + +/* RSET var$ = expr$ - right-justify into field variable */ +void gw_stmt_rset(void) +{ + gw_skip_spaces(); + char name[2]; + gw_valtype_t type = gw_parse_varname(name); + if (type != VT_STR) gw_error(ERR_TM); + var_entry_t *var = gw_var_find_or_create(name, type); + + gw_skip_spaces(); + gw_expect(TOK_EQ); + gw_value_t rhs = gw_eval_str(); + + int target_len = var->val.sval.len; + if (target_len == 0) { + gw_str_free(&rhs.sval); + return; + } + + int copy = (rhs.sval.len < target_len) ? rhs.sval.len : target_len; + int pad = target_len - copy; + if (pad > 0) + memset(var->val.sval.data, ' ', pad); + memcpy(var->val.sval.data + pad, rhs.sval.data, copy); + gw_str_free(&rhs.sval); +} + +/* PUT #n [, record] - write field buffer to file */ +void gw_stmt_put(void) +{ + gw_skip_spaces(); + if (gw_chrgot() == '#') + gw_chrget(); + int num = gw_eval_int(); + file_entry_t *f = gw_file_get(num); + if (f->mode != 'R') + gw_error(ERR_BM); + + long record = -1; + gw_skip_spaces(); + if (gw_chrgot() == ',') { + gw_chrget(); + record = gw_eval_int(); + if (record < 1) gw_error(ERR_RN); + } + + vars_to_field_buf(f); + + if (record > 0) + fseek(f->fp, (long)(record - 1) * f->record_len, SEEK_SET); + + fwrite(f->field_buf, 1, f->record_len, f->fp); + fflush(f->fp); +} + +/* GET #n [, record] - read record from file into field buffer */ +void gw_stmt_get(void) +{ + gw_skip_spaces(); + if (gw_chrgot() == '#') + gw_chrget(); + int num = gw_eval_int(); + file_entry_t *f = gw_file_get(num); + if (f->mode != 'R') + gw_error(ERR_BM); + + long record = -1; + gw_skip_spaces(); + if (gw_chrgot() == ',') { + gw_chrget(); + record = gw_eval_int(); + if (record < 1) gw_error(ERR_RN); + } + + if (record > 0) + fseek(f->fp, (long)(record - 1) * f->record_len, SEEK_SET); + + memset(f->field_buf, 0, f->record_len); + size_t got = fread(f->field_buf, 1, f->record_len, f->fp); + if (got == 0) + f->eof_flag = true; + + field_buf_to_vars(f); +} + +/* ================================================================ + * MBF Conversion Functions: CVI, CVS, CVD, MKI$, MKS$, MKD$ + * ================================================================ */ + +gw_value_t gw_fn_cvi(gw_value_t *s) +{ + if (s->type != VT_STR) gw_error(ERR_TM); + if (s->sval.len < 2) gw_error(ERR_FC); + gw_value_t v; + v.type = VT_INT; + v.ival = (int16_t)((uint8_t)s->sval.data[0] | + ((uint8_t)s->sval.data[1] << 8)); + gw_str_free(&s->sval); + return v; +} + +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); + gw_value_t v; + v.type = VT_SNG; + memcpy(&v.fval, s->sval.data, 4); + gw_str_free(&s->sval); + return v; +} + +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); + gw_value_t v; + v.type = VT_DBL; + memcpy(&v.dval, s->sval.data, 8); + gw_str_free(&s->sval); + return v; +} + +gw_value_t gw_fn_mki(int16_t n) +{ + gw_value_t v; + v.type = VT_STR; + v.sval = gw_str_alloc(2); + v.sval.data[0] = (char)(n & 0xFF); + v.sval.data[1] = (char)((n >> 8) & 0xFF); + return v; +} + +gw_value_t gw_fn_mks(float f) +{ + gw_value_t v; + v.type = VT_STR; + v.sval = gw_str_alloc(4); + memcpy(v.sval.data, &f, 4); + return v; +} + +gw_value_t gw_fn_mkd(double d) +{ + gw_value_t v; + v.type = VT_STR; + v.sval = gw_str_alloc(8); + memcpy(v.sval.data, &d, 8); + return v; +} diff --git a/src/interp.c b/src/interp.c index 8eec43c..9a55957 100644 --- a/src/interp.c +++ b/src/interp.c @@ -542,6 +542,97 @@ void gw_stmt_mid_assign(void) gw_str_free(&rhs.sval); } +/* ================================================================ + * CHAIN "filename" [, linenum] [, ALL] + * ================================================================ */ + +void gw_stmt_chain(void) +{ + gw_skip_spaces(); + + /* Skip optional MERGE keyword */ + bool merge = false; + if (gw_is_letter(gw_chrgot()) && toupper(gw_chrgot()) == 'M') { + /* Could be MERGE - skip the word */ + uint8_t *save = gw.text_ptr; + while (gw_is_letter(gw_chrgot())) + gw_chrget(); + gw_skip_spaces(); + merge = true; + /* If no string follows, this wasn't MERGE */ + if (gw_chrgot() != '"') { + gw.text_ptr = save; + merge = false; + } + } + + gw_value_t fname_val = gw_eval_str(); + char *filename = gw_str_to_cstr(&fname_val.sval); + gw_str_free(&fname_val.sval); + + uint16_t start_line = 0; + bool has_start = false; + bool keep_all = false; + + gw_skip_spaces(); + if (gw_chrgot() == ',') { + gw_chrget(); + gw_skip_spaces(); + /* Optional line number */ + uint8_t ch = gw_chrgot(); + if ((ch >= 0x11 && ch <= 0x1A) || ch == TOK_INT1 || ch == TOK_INT2 + || ch == TOK_CONST_SNG || ch == TOK_CONST_DBL) { + start_line = gw_eval_uint16(); + has_start = true; + } + gw_skip_spaces(); + if (gw_chrgot() == ',') { + gw_chrget(); + gw_skip_spaces(); + /* ALL keyword - keep all variables */ + if (gw_is_letter(gw_chrgot()) && toupper(gw_chrgot()) == 'A') { + keep_all = true; + while (gw_is_letter(gw_chrgot())) + gw_chrget(); + } + } + } + + /* Load the new program */ + gw_stmt_load_internal(filename, !merge); + free(filename); + + if (!keep_all && !merge) { + /* Clear variables unless ALL specified */ + gw_vars_clear(); + gw_arrays_clear(); + } + + /* Start execution */ + program_line_t *start = gw.prog_head; + if (has_start) { + start = gw_find_line(start_line); + if (!start) gw_error(ERR_UL); + } + + if (start) { + gw.for_sp = 0; + gw.gosub_sp = 0; + gw.while_sp = 0; + gw.data_ptr = NULL; + gw.data_line_ptr = NULL; + gw.cont_text = NULL; + gw.cont_line = NULL; + gw.on_error_line = 0; + gw.in_error_handler = false; + gw.cur_line = start; + gw.text_ptr = start->tokens; + gw.cur_line_num = start->num; + gw.running = true; + gw_run_loop(); + } +} + /* ================================================================ * Statement Dispatcher * ================================================================ */ @@ -604,6 +695,43 @@ void gw_exec_stmt(void) if (gw_hal) gw_hal->shutdown(); exit(0); } + if (xstmt == XSTMT_CHAIN) { + gw_chrget(); + gw_stmt_chain(); + return; + } + if (xstmt == XSTMT_COMMON) { + /* COMMON var, var... - just skip, variables already in table */ + gw_chrget(); + while (gw_chrgot() && gw_chrgot() != ':' && gw_chrgot() != TOK_ELSE) + gw.text_ptr++; + return; + } + if (xstmt == XSTMT_FIELD) { + gw_chrget(); + gw_stmt_field(); + return; + } + if (xstmt == XSTMT_LSET) { + gw_chrget(); + gw_stmt_lset(); + return; + } + if (xstmt == XSTMT_RSET) { + gw_chrget(); + gw_stmt_rset(); + return; + } + if (xstmt == XSTMT_PUT) { + gw_chrget(); + gw_stmt_put(); + return; + } + if (xstmt == XSTMT_GET) { + gw_chrget(); + gw_stmt_get(); + return; + } /* Graphics/sound stubs - parse and discard arguments */ if (xstmt == XSTMT_CIRCLE || xstmt == XSTMT_DRAW || xstmt == XSTMT_PAINT || xstmt == XSTMT_PLAY || @@ -690,10 +818,26 @@ void gw_exec_stmt(void) gw_chrget(); gw_skip_spaces(); + /* RUN "filename" - load and run a file */ + if (gw_chrgot() == '"') { + gw_value_t fname_val = gw_eval_str(); + char *filename = gw_str_to_cstr(&fname_val.sval); + gw_str_free(&fname_val.sval); + gw_stmt_load_internal(filename, true); + free(filename); + if (gw.prog_head) { + gw.cur_line = gw.prog_head; + gw.text_ptr = gw.prog_head->tokens; + gw.cur_line_num = gw.prog_head->num; + gw.running = true; + gw_run_loop(); + } + return; + } + /* RUN with line number */ program_line_t *start = gw.prog_head; if (gw_chrgot() >= TOK_INT2 && gw_chrgot() <= TOK_CONST_DBL) { - /* has a line number arg */ uint16_t num = gw_eval_uint16(); start = gw_find_line(num); if (!start) gw_error(ERR_UL); diff --git a/src/program_io.c b/src/program_io.c index 734ab7f..7e24ce8 100644 --- a/src/program_io.c +++ b/src/program_io.c @@ -39,7 +39,9 @@ void gw_stmt_save(void) } /* Helper: load lines from a file into the program, optionally clearing first */ -static void load_from_file(const char *filename, bool clear) +void gw_stmt_load_internal(const char *filename, bool clear); + +void gw_stmt_load_internal(const char *filename, bool clear) { FILE *fp = fopen(filename, "r"); if (!fp) @@ -120,7 +122,7 @@ void gw_stmt_load(void) } } - load_from_file(filename, true); + gw_stmt_load_internal(filename, true); free(filename); if (run_after && gw.prog_head) { @@ -140,6 +142,6 @@ void gw_stmt_merge(void) char *filename = gw_str_to_cstr(&fname_val.sval); gw_str_free(&fname_val.sval); - load_from_file(filename, false); + gw_stmt_load_internal(filename, false); free(filename); } diff --git a/tests/programs/chain_target.bas b/tests/programs/chain_target.bas new file mode 100644 index 0000000..ee94672 --- /dev/null +++ b/tests/programs/chain_target.bas @@ -0,0 +1,2 @@ +10 REM Target program for CHAIN test +20 PRINT "Chained OK" diff --git a/tests/programs/chain_test.bas b/tests/programs/chain_test.bas new file mode 100644 index 0000000..13d5760 --- /dev/null +++ b/tests/programs/chain_test.bas @@ -0,0 +1,4 @@ +10 REM CHAIN test +20 PRINT "Before CHAIN" +30 CHAIN "tests/programs/chain_target.bas" +40 PRINT "Should not reach here" diff --git a/tests/programs/mkicvi.bas b/tests/programs/mkicvi.bas new file mode 100644 index 0000000..1e495ea --- /dev/null +++ b/tests/programs/mkicvi.bas @@ -0,0 +1,7 @@ +10 REM MKI$/CVI, MKS$/CVS, MKD$/CVD test +20 A$ = MKI$(12345) +30 PRINT CVI(A$) +40 B$ = MKS$(3.14) +50 PRINT CVS(B$) +60 C$ = MKD$(2.718281828) +70 PRINT CVD(C$) diff --git a/tests/programs/random_access.bas b/tests/programs/random_access.bas new file mode 100644 index 0000000..11d8e2f --- /dev/null +++ b/tests/programs/random_access.bas @@ -0,0 +1,16 @@ +10 REM Random-access file I/O test +20 OPEN "R", #1, "/tmp/gwbasic_random.dat", 32 +30 FIELD #1, 20 AS N$, 4 AS A$, 8 AS S$ +40 LSET N$ = "Alice" +50 LSET A$ = MKI$(25) + " " +60 LSET S$ = MKD$(50000.5) +70 PUT #1, 1 +80 LSET N$ = "Bob" +90 LSET A$ = MKI$(30) + " " +100 LSET S$ = MKD$(75000.25) +110 PUT #1, 2 +120 GET #1, 1 +130 PRINT LEFT$(N$, 5); CVI(A$); +140 GET #1, 2 +150 PRINT LEFT$(N$, 3); CVI(A$) +160 CLOSE #1 diff --git a/tests/programs/run_file.bas b/tests/programs/run_file.bas new file mode 100644 index 0000000..a0c452f --- /dev/null +++ b/tests/programs/run_file.bas @@ -0,0 +1,4 @@ +10 REM RUN "filename" test +20 PRINT "Before RUN" +30 RUN "tests/programs/chain_target.bas" +40 PRINT "Should not reach here"