diff --git a/manual.md b/manual.md index 8b91017..c88ff39 100644 --- a/manual.md +++ b/manual.md @@ -1414,6 +1414,50 @@ Control Statements appear any place in the program. +**Return From A Subroutine** +**RETURN [_expression_] / RET.** + + Ends the current `PROC`. With an + _expression_, returns its integer value + as the function result. Without an + expression, returns immediately with no + result; this is useful as an early exit + from nested loops. + + `RETURN` is only valid inside a `PROC`. + Using it outside a `PROC` gives the `bad + RETURN` compiler error. + + Subroutines that return a value can + be called in integer expressions with + `@name(...)`: + + PROC Square X + RETURN X*X + ENDPROC + + ? @Square(5) + A(1) = @Square(4) + + Function arguments use the same syntax + as `EXEC` arguments, but the call must + use parentheses in an expression, so it + is distinct from the cross-compiler + `@symbol` address syntax. The function + result is an integer. A function should + contain at least one value-returning + `RETURN`; a bare `RETURN` does not + count. The cross compiler reports an + error if it does not. It does not + require every control-flow path to + return. + + `RETURN` inside an `IF` is allowed. + `RETURN` inside a `FOR` loop is also + allowed; any active `FOR` state is + removed before returning. + + **Loop Until Condition Is True** **REPEAT / R.** **UNTIL _condition_ / U.** diff --git a/src/actions.asm b/src/actions.asm index 33e0d1b..e311b5a 100644 --- a/src/actions.asm +++ b/src/actions.asm @@ -23,6 +23,7 @@ .export E_PUSH_LT, E_POP_LOOP, E_POP_REPEAT .export E_POP_IF, E_ELSEIF, E_EXIT_LOOP .export E_POP_WHILE, E_POP_FOR, E_POP_PROC_DATA, E_POP_PROC_2 + .export E_RETURN .export E_CONST_STRING .export E_VAR_CREATE, E_VAR_WORD, E_VAR_SEARCH .export E_VAR_SET_TYPE, E_LABEL_SET_TYPE @@ -45,6 +46,9 @@ .import parser_error, parser_skipws, parser_emit_byte, parser_inc_opos ; From error.asm .importzp ERR_LOOP, parse_err + .importzp ERR_RETURN + ; From for_exit.asm + .importzp TOK_FOR_EXIT .ifdef FASTBASIC_FP ; Exported only in Floating Point version @@ -104,14 +108,14 @@ read_fp = AFP ;---------------------------------------------------------- ; Use cassette buffer for loop stack, max 128 bytes loop_stk = $400 -; And at $480 store the "variable" stack, used procedure parameters +; At $480 store the "variable" stack, used for variable IDs and the current +; function label around nested function calls. var_stk = $480 ;---------------------------------------------------------- .segment "IDEZP": zeropage ; Relocation amount reloc_addr: .res 2 -last_label_num: .res 1 laddr_search_num: .res 1 loop_exit_comp = laddr_search_num @@ -505,7 +509,10 @@ xit: rts jsr push_codep ; Search in the label list - ldx last_label_num + dec var_sp + ldx var_sp + lda var_stk, x + tax jsr laddr_search_start bcs nfound @@ -554,7 +561,10 @@ xit: rts ::E_DO_EXEC: ; Emits a label, searching the label address in the label list - ldx last_label_num + dec var_sp + ldx var_sp + lda var_stk, x + tax start_searching: jsr laddr_search_start bcs nfound @@ -579,7 +589,10 @@ ret: rts ; Label search and create if not exists .proc E_LABEL_CREATE jsr label_search - stx last_label_num + ldy var_sp + txa + sta var_stk, y + inc var_sp ; Already defined, just store index bcc ret @@ -693,6 +706,28 @@ rtsclc: clc rts ; C is cleared on exit! .endproc +.proc E_RETURN + ; Find the enclosing PROC, removing any active FOR state. + ldx loop_sp + beq bad_return +loop: + lda loop_stk-3, x + cmp #LT_PROC_2 + beq pop_codep::rtsclc + cmp #LT_FOR_1 + bne next + lda #TOK_FOR_EXIT + jsr parser_emit_byte +next: + dex + dex + dex + bne loop +bad_return: + ldy #ERR_RETURN + jmp parser_error +.endproc + .proc E_POP_PROC_2 ; Pop saved position, store lda #LT_PROC_2 @@ -725,7 +760,6 @@ retry: dey bmi retry ; FOR(2)/WHILE(2)/IF/ELSE/ELIF are > 127 cpx #LT_PROC_DATA+1 ; PROC(1)/DATA bcc loop_error -ok: ; Store slot sty loop_exit_comp ; Check if enough stack diff --git a/src/compiler/compile.cc b/src/compiler/compile.cc index ded58d0..8a627ba 100644 --- a/src/compiler/compile.cc +++ b/src/compiler/compile.cc @@ -261,6 +261,14 @@ int compiler::compile_file(std::string iname, std::string output_filename, return 1; } + // Check functions used in expressions return a value somewhere. + auto function_error = s.validate_functions(); + if(function_error.size()) + { + std::cerr << iname << ": " << function_error << "\n"; + return 1; + } + s.emit_tok("TOK_END"); // Optimize if(optimize) diff --git a/src/compiler/parser-actions.cc b/src/compiler/parser-actions.cc index 3ef274d..e8f8afe 100644 --- a/src/compiler/parser-actions.cc +++ b/src/compiler/parser-actions.cc @@ -127,6 +127,15 @@ static bool get_asm_word_constant(parse &s) // Reads ASM constant if(s.get_ident(name)) { + // A following '(' means a user function call, not an ASM symbol. + auto ident_end = s.pos; + s.skipws(); + if(s.pos < s.str.size() && s.str[s.pos] == '(') + { + s.pos = start; + return false; + } + s.pos = ident_end; s.add_text("@" + name); s.add_s_var("@" + name); s.emit_word(name); @@ -399,6 +408,28 @@ static bool SMB_E_POP_PROC_DATA(parse &s) return true; } +static bool SMB_E_RETURN(parse &s) +{ + s.debug("E_RETURN"); + int proc = -1; + for(int i = (int)s.jumps.size() - 1; i >= 0; i--) + { + if(s.jumps[i].type == LT_PROC_2) + { + proc = i; + break; + } + } + if(proc < 0) + return s.loop_error("return outside proc"); + for(int i = (int)s.jumps.size() - 1; i > proc; i--) + { + if(s.jumps[i].type == LT_FOR_1) + s.emit_tok("TOK_FOR_EXIT"); + } + return true; +} + static bool SMB_E_POP_PROC_2(parse &s) { // nothing to do! @@ -597,10 +628,17 @@ static bool SMB_E_LABEL_DEF(parse &s) s.debug("E_LABEL_DEF"); auto &v = s.labels; auto name = s.last_label; + s.proc_names[l] = name; if(v[name].is_defined()) return s.loop_error("new label, got label already defined '" + name + "'"); s.current_params = 0; s.emit_label(s.label_prefix + name); + // The label remains current for E_PROC_CHECK / E_LABEL_SET_TYPE; discard + // the saved outer context. + if(!s.label_stack.empty()) + s.label_stack.pop_back(); + if(!s.param_stack.empty()) + s.param_stack.pop_back(); return true; } @@ -632,6 +670,25 @@ static bool SMB_E_COUNT_PARAM(parse &s) return false; } +static bool SMB_E_MARK_FUNCTION(parse &s) +{ + s.debug("E_MARK_FUNCTION"); + s.labels[s.last_label].mark_function_call(); + return true; +} + +static bool SMB_E_MARK_RETURN(parse &s) +{ + s.debug("E_MARK_RETURN"); + if(!s.proc_stack.empty()) + { + auto name = s.proc_names.find(s.proc_stack.back()); + if(name != s.proc_names.end()) + s.labels[name->second].mark_return(); + } + return true; +} + // Called in EXEC, creates a label if not exists, if already exists checks // that it is a PROC. static bool SMB_E_LABEL_CREATE(parse &s) @@ -648,6 +705,8 @@ static bool SMB_E_LABEL_CREATE(parse &s) // Store variable name s.add_text(name); s.add_s_var(name); + s.label_stack.push_back(s.last_label); + s.param_stack.push_back(s.current_params); s.last_label = name; s.current_params = 0; return true; @@ -664,6 +723,12 @@ static bool SMB_E_DO_EXEC(parse &s) std::to_string(pnum), s.pos); s.emit_word(s.label_prefix + s.last_label); + if(s.label_stack.empty() || s.param_stack.empty()) + return false; + s.last_label = s.label_stack.back(); + s.label_stack.pop_back(); + s.current_params = s.param_stack.back(); + s.param_stack.pop_back(); return true; } @@ -743,6 +808,8 @@ static bool SMB_E_DATA_FILE(parse &s) static std::map actions = { {"E_CONST_STRING", SMB_E_CONST_STRING}, {"E_COUNT_PARAM", SMB_E_COUNT_PARAM}, + {"E_MARK_FUNCTION", SMB_E_MARK_FUNCTION}, + {"E_MARK_RETURN", SMB_E_MARK_RETURN}, {"E_DATA_FILE", SMB_E_DATA_FILE}, {"E_DO_EXEC", SMB_E_DO_EXEC}, {"E_ELSEIF", SMB_E_ELSEIF}, @@ -769,6 +836,7 @@ static std::map actions = { {"E_PUSH_LT", SMB_E_PUSH_LT}, {"E_PUSH_VAR", SMB_E_PUSH_VAR}, {"E_REM", SMB_E_REM}, + {"E_RETURN", SMB_E_RETURN}, {"E_VAR_CREATE", SMB_E_VAR_CREATE}, {"E_VAR_SEARCH", SMB_E_VAR_SEARCH}, {"E_VAR_SET_TYPE", SMB_E_VAR_SET_TYPE}, diff --git a/src/compiler/parser.h b/src/compiler/parser.h index 6117b89..628f910 100644 --- a/src/compiler/parser.h +++ b/src/compiler/parser.h @@ -159,7 +159,10 @@ class parse std::vector proc_stack; std::map vars; std::map labels; + std::map proc_names; // internal loop label -> name std::vector jumps; + std::vector param_stack; // saved current_params around label contexts + std::vector label_stack; // saved last_label around label contexts int label_num; bool finalized; std::vector *code; @@ -241,6 +244,19 @@ class parse return "EXIT without loop"; } + std::string validate_functions() + { + // Cross-compiler diagnostic: a procedure used as a function must + // contain at least one RETURN. We deliberately do not try to prove + // that every control-flow path returns. + for(auto &l : labels) + { + if(l.second.is_function_call() && !l.second.has_return()) + return "function '" + l.first + "' does not return a value"; + } + return std::string(); + } + void set_input_file(std::string fn) { in_fname = fn; } void new_line(std::string l, int ln) diff --git a/src/compiler/peephole.cc b/src/compiler/peephole.cc index b0c12a8..84c1659 100644 --- a/src/compiler/peephole.cc +++ b/src/compiler/peephole.cc @@ -389,6 +389,60 @@ class peephole } } } + // Remove SADDR save/restore pairs around calls when no lvalue address is + // pending. A push starts a region where SADDR is already saved, so nested + // calls inside its arguments do not need their own save/restore. + void remove_saddr_saves() + { + struct saved + { + size_t pos; + bool live; + }; + std::vector stack; + std::vector remove(code.size(), false); + bool live = false; + for(size_t i = 0; i < code.size(); i++) + { + if(code[i].is_label()) + { + live = false; + continue; + } + if(!code[i].is_tok()) + continue; + auto tok = code[i].get_tok(); + if(tok == "TOK_SADDR" || tok == "TOK_VAR_SADDR") + live = true; + else if(tok == "TOK_POKE" || tok == "TOK_DPOKE" || + tok == "TOK_NUM_POKE" || tok == "TOK_BYTE_POKE" || + tok == "TOK_FP_STORE" || tok == "TOK_COPY_STR" || + tok == "TOK_INC" || tok == "TOK_DEC" || tok == "TOK_END") + live = false; + else if(tok == "TOK_PUSH_SADDR") + { + stack.push_back({i, live}); + if(!live) + remove[i] = true; + // Inside the saved region, nested calls do not need to save it. + live = false; + } + else if(tok == "TOK_POP_SADDR") + { + if(stack.empty()) + continue; + auto saved = stack.back(); + stack.pop_back(); + if(!saved.live) + remove[i] = true; + live = saved.live; + } + } + for(size_t i = code.size(); i-- > 0;) + if(remove[i]) + code.erase(code.begin() + i); + } + // Unused labels removal void remove_unused_labels() { @@ -1455,6 +1509,23 @@ class peephole del(0); continue; } + // Bypass CJUMP/CNJUMP over a RETURN 0 + // TOK_CJUMP / x / TOK_NUM / 0 / TOK_RET / LABEL x + // -> TOK_CNRET / LABEL x + // TOK_CNJUMP / x / TOK_NUM / 0 / TOK_RET / LABEL x + // -> TOK_CRET / LABEL x + if((mtok(0, "TOK_CJUMP") || mtok(0, "TOK_CNJUMP")) && + mtok(2, "TOK_NUM") && mword(3) && val(3) == 0 && + mtok(4, "TOK_RET") && mlabel(5) && lbl(5) == wlbl(1)) + { + bool cjump = mtok(0, "TOK_CJUMP"); + set_tok(0, cjump ? "TOK_CNRET" : "TOK_CRET"); + del(4); + del(3); + del(2); + del(1); + continue; + } // Bypass CJUMP over another JUMP // TOK_CJUMP / x / TOK_JUMP / y / LABEL x // -> TOK_CNJUMP / y / LABEL x @@ -1589,6 +1660,7 @@ class peephole shorten_numbers(); fold_push(); fold_saddr(); + remove_saddr_saves(); } }; diff --git a/src/compiler/vartype.h b/src/compiler/vartype.h index 5863fcd..f810b0b 100644 --- a/src/compiler/vartype.h +++ b/src/compiler/vartype.h @@ -58,9 +58,15 @@ class labelType void set_type(std::string); void set_segment(std::string); std::string get_segment(); + void mark_function_call() { function_call = true; } + bool is_function_call() const { return function_call; } + void mark_return() { returns_value = true; } + bool has_return() const { return returns_value; } bool operator!=(const labelType &l) const { return type != l.type; } private: std::string segment; - int type; + int type = 0; + bool function_call = false; + bool returns_value = false; }; diff --git a/src/editor.bas b/src/editor.bas index f0220e5..6075581 100644 --- a/src/editor.bas +++ b/src/editor.bas @@ -100,6 +100,7 @@ ENDPROC '------------------------------------- ' Edit top line for input +' Returns 1 if the line was accepted, 0 if cancelled. ' PROC InputLine do @@ -111,27 +112,29 @@ PROC InputLine elif key = 155 pos. 6, 0 poke @CH, 12: ' Force ENTER - key = 0 - exit + return 1 else ' Otherwise simply exit exec ShowInfo exit endif loop + return 0 ENDPROC '------------------------------------- ' Gets a filename with minimal line editing +' Returns 1 if a filename was accepted, 0 if cancelled. ' PROC InputFilename ' Show current filename: ? "? "; FileName$; - exec InputLine - if not key + if @InputLine() input ; FileName$ exec ShowInfo + return 1 endif + return 0 ENDPROC '------------------------------------- @@ -322,8 +325,7 @@ ENDPROC PROC AskSaveFile exec ClrTopLine ? "Save"; - exec InputFileName - if key + if not @InputFileName() ' Don't save exit endif @@ -372,22 +374,21 @@ PROC ShowInfo ENDPROC '------------------------------------- -' Ask to save a file if it is changed -' from last save. -PROC AskSaveFileChanged - key = 0 +' Ask to save a file if it is changed from last save. +' Returns 1 if the caller may continue, 0 to cancel the operation. +PROC CheckUnsavedChanges while not fileSaved exec AskSaveFile ' ESC means "don't save, cancel operation" if key = 27 - exit + return 0 endif ' CONTROL-C means "don't save, lose changes" if key = 3 - key = 0 exit endif wend + return 1 ENDPROC '------------------------------------- @@ -975,8 +976,7 @@ PROC ProcessKeys ' '--------- Control-Q (exit) ----- elif key = $11 - exec AskSaveFileChanged - if not key + if @CheckUnsavedChanges() cls poke @@LMARGN, OldMargin end @@ -996,19 +996,16 @@ PROC ProcessKeys ' '--------- Control-N (new) ----- elif key = $0E - exec AskSaveFileChanged - if not key + if @CheckUnsavedChanges() exec NewFile endif ' '--------- Control-L (load) ----- elif key = $0C - exec AskSaveFileChanged - if not key + if @CheckUnsavedChanges() exec ClrTopLine ? "Load"; - exec InputFileName - if not key + if @InputFileName() exec LoadFile endif endif @@ -1017,8 +1014,7 @@ PROC ProcessKeys elif key = $07 exec ClrTopLine ? "Line? "; - exec InputLine - if not key + if @InputLine() input ; topLine topLine = abs(topLine - 1) scrLine = 0 @@ -1055,8 +1051,7 @@ PROC SaveCompiledFile exec DefaultFileName ? "Name"; - exec InputFileName - if key + if not @InputFileName() ' Don't save exit endif diff --git a/src/errors.asm b/src/errors.asm index 4ec7f88..edd517b 100644 --- a/src/errors.asm +++ b/src/errors.asm @@ -37,6 +37,7 @@ error_msg_list = * - 1 def_error ERR_LOOP, "bad loop" def_error ERR_PARSE, "parse error" def_error ERR_NO_ELOOP, "no end loop/proc/if" + def_error ERR_RETURN, "bad RETURN" .if (* - error_msg_list) > 255 .error "Error, too many error messages" diff --git a/src/interp/return.asm b/src/interp/return.asm index d9dbbfd..efc4267 100644 --- a/src/interp/return.asm +++ b/src/interp/return.asm @@ -41,10 +41,13 @@ .endproc ; Fall through .proc EXE_RET + ; Preserve the return value in A, popping the address clobbers it. + tay pla sta cptr+1 pla sta cptr + tya ::skip: jmp next_instruction .endproc diff --git a/src/interp/saddr.asm b/src/interp/saddr.asm index d4e8bc1..b704b8d 100644 --- a/src/interp/saddr.asm +++ b/src/interp/saddr.asm @@ -44,8 +44,31 @@ EXE_VAR_SADDR: ; SADDR = VAR address jmp next_instruction .endproc +; Save and restore the SADDR pointer around calls used as expressions. +; POP_SADDR must preserve A and X, as they hold the function result. Y is +; clobbered, but next_instruction reloads it from sptr. +.proc EXE_PUSH_SADDR + lda saddr + pha + lda saddr+1 + pha + jmp next_instruction +.endproc + +.proc EXE_POP_SADDR + tay + pla + sta saddr+1 + pla + sta saddr + tya + jmp next_instruction +.endproc + .include "deftok.inc" deftoken "SADDR" deftoken "VAR_SADDR" + deftoken "PUSH_SADDR" + deftoken "POP_SADDR" ; vi:syntax=asm_ca65 diff --git a/src/syntax/basic.syn b/src/syntax/basic.syn index c54113c..98ead00 100644 --- a/src/syntax/basic.syn +++ b/src/syntax/basic.syn @@ -28,6 +28,7 @@ TOKENS { # PUSH plus composed PUSH ops TOK_PUSH, TOK_PUSH_VAR_LOAD TOK_PUSH_NUM, TOK_PUSH_BYTE, TOK_PUSH_0, TOK_PUSH_1, TOK_POP + TOK_PUSH_SADDR, TOK_POP_SADDR # Numeric operators TOK_NEG, TOK_ABS, TOK_SGN, TOK_ADD, TOK_SUB, TOK_MUL, TOK_DIV, TOK_MOD # Operators on VAR @@ -72,6 +73,7 @@ EXTERN { E_PUSH_LT, E_POP_LOOP, E_POP_REPEAT E_POP_IF, E_ELSEIF, E_EXIT_LOOP E_POP_WHILE, E_POP_FOR, E_POP_PROC_DATA, E_POP_PROC_2 + E_RETURN E_CONST_STRING E_VAR_CREATE, E_VAR_SET_TYPE, E_LABEL_SET_TYPE E_VAR_WORD, E_VAR_SEARCH @@ -157,6 +159,7 @@ T_EXPR: integer constant, variable or function # This is an integer terminal that can't be parsed as a floating point: INT_FUNCTIONS: integer variable or function + "@" E_LABEL_CREATE FUNC_MARK emit TOK_PUSH_SADDR "(" FUNC_ARG_LIST ")" emit { TOK_CALL } E_DO_EXEC emit TOK_POP_SADDR "Not" NOT_EXPR emit TOK_L_NOT # Special (predefined) variables "Time" emit TOK_TIME @@ -186,6 +189,20 @@ INT_FUNCTIONS: integer variable or function "Val" STR_EXPR emit TOK_VAL "ASc" STR_EXPR emit { TOK_PUSH_1, TOK_ADD, TOK_PEEK } # TODO: does not check for empty strings. +FUNC_MARK: + pass + +FUNC_ARG_OK: + pass + +FUNC_ARG_LIST: + EXPR emit TOK_PUSH FUNC_ARG_OK FUNC_ARG_MORE + pass + +FUNC_ARG_MORE: + "," EXPR emit TOK_PUSH FUNC_ARG_OK FUNC_ARG_MORE + pass + # Used to handle PADDLE/STICK/PTRIG/STRIG RD_PORT: emit TOK_PUSH T_EXPR emit { TOK_BIT_AND, TOK_ADD, TOK_PEEK } @@ -466,6 +483,14 @@ DATA_VAR: new data array name DATA_TYPE E_LABEL_CREATE "()" emit { TOK_JUMP } E_LABEL_DEF DATA_TYPE +# Optional value returned by RETURN +RETURN_VALUE: + INT_EXPR MARK_RETURN + pass + +MARK_RETURN: + pass + # Optional number after PAUSE PAUSE_OPT: EXPR @@ -526,6 +551,7 @@ STATEMENT: statement "DEc" VAR_WORD_LVALUE_SADDR emit TOK_DEC "PRoc" E_LABEL_CREATE emit { TOK_JUMP } E_LABEL_DEF emit LT_PROC_2 E_PUSH_LT OPT_PROC_VAR "ENdproc" E_POP_PROC_2 emit TOK_RET E_POP_PROC_DATA + "Return" RETURN_VALUE E_RETURN emit TOK_RET EXEC_CALL E_LABEL_CREATE OPT_EXEC_PARAM emit { TOK_CALL } E_DO_EXEC "DAta" DATA_VAR DATA_END "END" emit TOK_END diff --git a/src/syntax/extended.syn b/src/syntax/extended.syn index 7ce0411..c2cccc8 100644 --- a/src/syntax/extended.syn +++ b/src/syntax/extended.syn @@ -24,6 +24,8 @@ EXTERN { E_DATA_SET_SEGMENT E_COUNT_PARAM E_PROC_CHECK + E_MARK_FUNCTION + E_MARK_RETURN } # Reda DATA statements from a file: @@ -53,4 +55,16 @@ OPT_PROC_VAR:< OPT_PROC_VAR: E_PROC_CHECK +# Mark labels used as functions in expressions +FUNC_MARK:< + E_MARK_FUNCTION + +# Mark only value-returning RETURN statements +MARK_RETURN:< + E_MARK_RETURN + +# Count function arguments, without adding an action to the native parser +FUNC_ARG_OK:< + E_COUNT_PARAM + # vi:syntax=perl diff --git a/testsuite/tests/func-array-saddr.bas b/testsuite/tests/func-array-saddr.bas new file mode 100644 index 0000000..cc35e8b --- /dev/null +++ b/testsuite/tests/func-array-saddr.bas @@ -0,0 +1,18 @@ +DIM B(3) BYTE +DIM W(3) + +PROC Plus X + RETURN X + 10 +ENDPROC + +PROC Twice X + RETURN X + X +ENDPROC + +B(1) = @Plus(5) +W(2) = @Plus(7) +B(2) = @Twice(@Plus(1)) + +? B(1) +? B(2) +? W(2) diff --git a/testsuite/tests/func-array-saddr.chk b/testsuite/tests/func-array-saddr.chk new file mode 100644 index 0000000..798d17a --- /dev/null +++ b/testsuite/tests/func-array-saddr.chk @@ -0,0 +1,6 @@ +Name: Function calls preserve SADDR for array assignment +Test: run +Output: +15 +22 +17 diff --git a/testsuite/tests/func-bare-return-error.bas b/testsuite/tests/func-bare-return-error.bas new file mode 100644 index 0000000..ed844cc --- /dev/null +++ b/testsuite/tests/func-bare-return-error.bas @@ -0,0 +1,5 @@ +PROC Bare + RETURN +ENDPROC + +? @Bare() diff --git a/testsuite/tests/func-bare-return-error.chk b/testsuite/tests/func-bare-return-error.chk new file mode 100644 index 0000000..3e168b9 --- /dev/null +++ b/testsuite/tests/func-bare-return-error.chk @@ -0,0 +1,3 @@ +Name: Bare RETURN does not make a function +Test: compile-error cross +Error: function 'Bare' does not return a value diff --git a/testsuite/tests/func-bare-return.bas b/testsuite/tests/func-bare-return.bas new file mode 100644 index 0000000..6a9b9d1 --- /dev/null +++ b/testsuite/tests/func-bare-return.bas @@ -0,0 +1,20 @@ +' Bare RETURN exits the current PROC and unwinds nested FOR loops. +PROC Early + ? "early" + RETURN + ? "after" +ENDPROC + +PROC Nested + FOR I = 1 TO 2 + FOR J = 1 TO 2 + ? "loop" + RETURN + NEXT J + NEXT I + ? "after" +ENDPROC + +@Early +@Nested +? "done" diff --git a/testsuite/tests/func-bare-return.chk b/testsuite/tests/func-bare-return.chk new file mode 100644 index 0000000..d531b96 --- /dev/null +++ b/testsuite/tests/func-bare-return.chk @@ -0,0 +1,6 @@ +Name: Bare RETURN exits PROC and unwinds FOR +Test: run +Output: +early +loop +done diff --git a/testsuite/tests/func-basic.bas b/testsuite/tests/func-basic.bas new file mode 100644 index 0000000..5f9e267 --- /dev/null +++ b/testsuite/tests/func-basic.bas @@ -0,0 +1,18 @@ +PROC Add X + RETURN X + 1 +ENDPROC + +PROC Mul X Y + RETURN X * Y +ENDPROC + +PROC Forty + RETURN 42 +ENDPROC + +? @Add(4) +? @Add(5) +? @Mul(3,5) +? @Add(@Mul(2,3)) +? @Add(1) + @Add(2) +? @Forty() diff --git a/testsuite/tests/func-basic.chk b/testsuite/tests/func-basic.chk new file mode 100644 index 0000000..426e569 --- /dev/null +++ b/testsuite/tests/func-basic.chk @@ -0,0 +1,9 @@ +Name: Test integer functions +Test: run +Output: +5 +6 +15 +7 +5 +42 diff --git a/testsuite/tests/func-cond-return.bas b/testsuite/tests/func-cond-return.bas new file mode 100644 index 0000000..4fb4028 --- /dev/null +++ b/testsuite/tests/func-cond-return.bas @@ -0,0 +1,6 @@ +' A function only needs at least one RETURN; full path coverage is not checked. +PROC Maybe X + IF X THEN RETURN 1 +ENDPROC + +? @Maybe(1) diff --git a/testsuite/tests/func-cond-return.chk b/testsuite/tests/func-cond-return.chk new file mode 100644 index 0000000..494394a --- /dev/null +++ b/testsuite/tests/func-cond-return.chk @@ -0,0 +1,4 @@ +Name: Function with conditional RETURN +Test: run +Output: +1 diff --git a/testsuite/tests/func-missing-return.bas b/testsuite/tests/func-missing-return.bas new file mode 100644 index 0000000..9dfea84 --- /dev/null +++ b/testsuite/tests/func-missing-return.bas @@ -0,0 +1,4 @@ +PROC Bad +ENDPROC + +? @Bad() diff --git a/testsuite/tests/func-missing-return.chk b/testsuite/tests/func-missing-return.chk new file mode 100644 index 0000000..ff3de75 --- /dev/null +++ b/testsuite/tests/func-missing-return.chk @@ -0,0 +1,3 @@ +Name: Function call without RETURN (cross) +Test: compile-error cross +Error: function 'Bad' does not return a value diff --git a/testsuite/tests/func-poke-saddr.bas b/testsuite/tests/func-poke-saddr.bas new file mode 100644 index 0000000..e833b2b --- /dev/null +++ b/testsuite/tests/func-poke-saddr.bas @@ -0,0 +1,45 @@ +PROC F1 + POKE $600, 1 + RETURN 2 +ENDPROC + +PROC F2 + RETURN $603 +ENDPROC + +PROC F3 + POKE $604, 5 + RETURN 6 +ENDPROC + +PROC F4 + POKE $607, 1 + RETURN 1 +ENDPROC + +' Function in POKE value: its body clobbers SADDR, so the wrapper must +' preserve the address for $601. +POKE $601, @F1() +? PEEK($600) +? PEEK($601) + +' Function in POKE address: no SADDR is pending before the call. +POKE @F2(), 4 +? PEEK($603) + +' Expression value with a pending SADDR. +POKE $602, 10 + @F1() +? PEEK($602) + +' Both address and value are function calls. +POKE @F2(), @F1() +? PEEK($603) + +' DPOKE value needs SADDR preservation too. +DPOKE $605, @F3() +? PEEK($604) +? DPEEK($605) + +' No pending SADDR: the wrapper can be removed. +IF @F4() THEN ? "YES" +? PEEK($607) diff --git a/testsuite/tests/func-poke-saddr.chk b/testsuite/tests/func-poke-saddr.chk new file mode 100644 index 0000000..3393d42 --- /dev/null +++ b/testsuite/tests/func-poke-saddr.chk @@ -0,0 +1,12 @@ +Name: Function calls preserve SADDR for POKE and DPOKE +Test: run +Output: +1 +2 +4 +12 +2 +5 +6 +YES +1 diff --git a/testsuite/tests/func-recursion.bas b/testsuite/tests/func-recursion.bas new file mode 100644 index 0000000..66acb87 --- /dev/null +++ b/testsuite/tests/func-recursion.bas @@ -0,0 +1,6 @@ +PROC Fact N + IF N <= 1 THEN RETURN 1 + RETURN N * @Fact(N-1) +ENDPROC + +? @Fact(5) diff --git a/testsuite/tests/func-recursion.chk b/testsuite/tests/func-recursion.chk new file mode 100644 index 0000000..2f9e2bc --- /dev/null +++ b/testsuite/tests/func-recursion.chk @@ -0,0 +1,4 @@ +Name: Recursive integer function +Test: run +Output: +120 diff --git a/testsuite/tests/func-return-error-cross.bas b/testsuite/tests/func-return-error-cross.bas new file mode 100644 index 0000000..9edc6ca --- /dev/null +++ b/testsuite/tests/func-return-error-cross.bas @@ -0,0 +1,2 @@ +? 1 +RETURN 2 diff --git a/testsuite/tests/func-return-error-cross.chk b/testsuite/tests/func-return-error-cross.chk new file mode 100644 index 0000000..1ac0098 --- /dev/null +++ b/testsuite/tests/func-return-error-cross.chk @@ -0,0 +1,4 @@ +Name: RETURN outside PROC (cross) +Test: compile-error cross +Error: return outside proc +Error-pos: 2:8 diff --git a/testsuite/tests/func-return-error.bas b/testsuite/tests/func-return-error.bas new file mode 100644 index 0000000..9edc6ca --- /dev/null +++ b/testsuite/tests/func-return-error.bas @@ -0,0 +1,2 @@ +? 1 +RETURN 2 diff --git a/testsuite/tests/func-return-error.chk b/testsuite/tests/func-return-error.chk new file mode 100644 index 0000000..35041b3 --- /dev/null +++ b/testsuite/tests/func-return-error.chk @@ -0,0 +1,4 @@ +Name: RETURN outside PROC (native) +Test: compile-error native +Error: bad RETURN +Error-pos: 2:8 diff --git a/testsuite/tests/func-return-for.bas b/testsuite/tests/func-return-for.bas new file mode 100644 index 0000000..07b25e9 --- /dev/null +++ b/testsuite/tests/func-return-for.bas @@ -0,0 +1,20 @@ +' RETURN inside FOR must remove the active FOR state before leaving. +PROC First N + FOR I = 1 TO N + RETURN N + 10 + NEXT I + RETURN 0 +ENDPROC + +PROC Nested + FOR I = 1 TO 2 + FOR J = 1 TO 2 + RETURN I * 10 + J + NEXT J + NEXT I + RETURN 0 +ENDPROC + +? @First(3) +? @First(2) +? @Nested() diff --git a/testsuite/tests/func-return-for.chk b/testsuite/tests/func-return-for.chk new file mode 100644 index 0000000..41b8272 --- /dev/null +++ b/testsuite/tests/func-return-for.chk @@ -0,0 +1,6 @@ +Name: RETURN inside FOR +Test: run +Output: +13 +12 +11 diff --git a/testsuite/tests/func-return-zero.bas b/testsuite/tests/func-return-zero.bas new file mode 100644 index 0000000..064f6bd --- /dev/null +++ b/testsuite/tests/func-return-zero.bas @@ -0,0 +1,42 @@ +' Check conditional RETURN 0 optimization with both branch directions. +' F returns a global so the same test covers zero, odd, even and 16-bit +' non-zero values. The optimizer must keep COMP_0: a raw CALL/CRET would +' incorrectly return for values such as 2 or 256. + +' Create the variable before the functions; DIM is the documented way +' to define a variable that is used as an rvalue in a PROC. +DIM V + +PROC F + RETURN V +ENDPROC + +PROC GN + IF NOT @F() : RETURN 0 : ENDIF + RETURN 1 +ENDPROC + +PROC GP + IF @F() : RETURN 0 : ENDIF + RETURN 1 +ENDPROC + +V = 0 +? @GN() +? @GP() + +V = 1 +? @GN() +? @GP() + +V = 2 +? @GN() +? @GP() + +V = 256 +? @GN() +? @GP() + +V = -2 +? @GN() +? @GP() diff --git a/testsuite/tests/func-return-zero.chk b/testsuite/tests/func-return-zero.chk new file mode 100644 index 0000000..dbc1c13 --- /dev/null +++ b/testsuite/tests/func-return-zero.chk @@ -0,0 +1,13 @@ +Name: Conditional RETURN 0 optimization +Test: run +Output: +0 +1 +1 +0 +1 +0 +1 +0 +1 +0 diff --git a/testsuite/tests/func-stack.bas b/testsuite/tests/func-stack.bas new file mode 100644 index 0000000..32b6311 --- /dev/null +++ b/testsuite/tests/func-stack.bas @@ -0,0 +1,15 @@ +' Function calls inside a DIM expression use both the variable stack and +' the function-label stack. They must share the same stack correctly. +PROC Three + RETURN 3 +ENDPROC + +DIM A(@Three()) BYTE +A(2) = 7 +? A(2) + +PROC Show X + ? X +ENDPROC + +@Show @Three() diff --git a/testsuite/tests/func-stack.chk b/testsuite/tests/func-stack.chk new file mode 100644 index 0000000..2405162 --- /dev/null +++ b/testsuite/tests/func-stack.chk @@ -0,0 +1,5 @@ +Name: Function call inside DIM uses stack correctly +Test: run +Output: +7 +3