Hacking in BASIC
Steve Drain (222) 1620 posts |
It is. I program for pleasure, mostly, and enjoy the challenge, rather like doing crosswords. ;-)
It was never about fixing bugs, although it does implement safe Here’s another for your collection: DO [WHILE|UNTIL <cond>] ... IF <cond> EXIT ... LOOP [WHILE|UNTIL <cond>] |
nemo (145) 2546 posts |
It’s a Basalt thing I suspect. It’ll be dimming in the stack or summat. |
Steve Drain (222) 1620 posts |
DEFFN_ERROR LOCAL k%:DIM k% LOCAL TRUE k%!-8=!_ERROR REM more magic =TRUE
Oh no it’s not! It is standard BASIC, but only in the Castle derived versions – thanks Steve. Here is a break down.
This DIMs a local block on the stack of zero extent, because TRUE=-1. The DIMmed variable The offset at So at This has not been tested very hard, so no guarantees. |
Clive Semmens (2335) 3276 posts |
I think I’ll stick to using BASIC in straightforward rather than devious ways…E&OE… |
nemo (145) 2546 posts |
I’ve been getting more enthusiastic about Steve’s TRY idea, but with BASIC syntax: CASE ERROR OF REM This is the TRY block PROCall_kinds_of_errors_might_happen WHENerrornumber REM deal with that error OTHERWISE REM deal with all other errors, REM or if no OTHERWISE then throw the ERROR ENDCASE However, it requires WHEN, OTHERWISE and ENDCASE to behave as an implicit code block terminator for the TRY code, and that requires context. That means something on the stack. And this means that plain old CASE/ENDCASE would no longer be able to stack anything, where currently it can: DEFPROCweird_but_legal(global%) CASEglobal%OF WHENFALSE:LOCALa%,b%,c% OTHERWISE:REM global vars will be used ENDCASE a%=b%+c% ENDPROC I can’t think of a way around this that doesn’t break for any embedded CASE within the TRY block. Adding a stacked context where previously there wasn’t one changes behaviour. This is a shame, as I really can see the utility of this construction. |
nemo (145) 2546 posts |
Steve. Steve. Steve… Steve. I love the TRY construction. It’s fab. I want it. |
nemo (145) 2546 posts |
Here’s my test program: REM Test the two kinds of CASE/ENDCASE construction IF0^0:PRINT"This doesn't look like nemoBasic" PRINT"Normal CASE:" FORZ%=1TO5:PRINT;Z%":"; CASEZ%OF WHEN2:PRINT"Two" WHEN1,3,5:PRINT"Odd" OTHERWISE:PRINT"Four" ENDCASE NEXT PRINT PRINT"Successful TRY:" CASE ERROR OF PRINT"This is the TRY block" PRINT"No errors here" OTHERWISE PRINT"Error ";ERR" happened" ENDCASE PRINT ONERROR:ONERROROFF:PRINT"Error ";ERR" was not handled. Stopping.":END PRINT"Errors in TRY" FORZ%=1TO7:PRINT;Z%":"; CASE ERROR OF REM This is the TRY block IFZ%=1:Z%=nosuchvar IFZ%=2:Z%=Z%/0 IFZ%=3:Z%=(3 IFZ%=4:Z%=FNnothere IFZ%=5:WHEN:REM Effective BREAK IFZ%=6:mistake IFZ%=7:"syntax" REM That was it, if we get to here, success! WHEN26:PRINT"No such var" WHEN18:PRINT"Div by 0" WHEN27:PRINT"Missing )" WHEN29:PRINT"No such FN" WHEN4:PRINT"Mistake" ENDCASE IFZ%=5:PRINT"OK!" NEXT Note that in a (My earlier idea of OTHERWISE as a success clause was silly – that’s what the end of the TRY block is, after all) |
Steve Drain (222) 1620 posts |
Welcome to the club. ;-) Although it would be nice to have in BASIC itself it is not necessary, because you can construct it from a normal
IF <cond1> THEN ... ELSEIF <cond2> THEN ... ELSEIF <cond3> THEN ... ENDIF Pretty easy to get the same effect with Edited to include the missing |
Alan Adams (2486) 1149 posts |
While we’re fiddling around in BASIC, is there any way that an error handler can display the call stack that lead to the error? I frequently have to resort to putting a *reportstack just before places where errors might be expected. |
nemo (145) 2546 posts |
Sore point. BBC Basic’s error handling is dumb, and this is caused by a hang-over from the translation to ARM from 6502. On the venerable Beeb, Basic’s memory usage was broadly the same – first Basic’s workspace, then the program, then the variables, then some free space, and then “the stack”. The “stack” being where Basic sticks temporary stuff, loop and flow control structures, LOCAL variables (in a manner of speaking) and so on. However, the 6502 stack was elsewhere. So Basic’s internal machine code routine calling didn’t effect “the stack”, only the CPU stack. On ARM it was decided to mix the two stacks, so “the stack” contains both flow-control structures (which are well defined) and whatever some routine has decided to STMFD while it does stuff. This includes exposed interfaces like SWI and CALL… and in the case of CALL and USR, the machine code then uses the same stack too. So when an error occurs, the state of “the stack” is indeterminate. There’s junk on it. There’s also valuable flow-control structures, the PROC call stack and so on, but there’s junk there too and it can easily look like legitimate structures. When a This has horrific consequences which cause real problems. It is by far the worst design decision in BBC Basic. The most obvious is that all LOCAL variables since that ONERROR suddenly become GLOBAL. This is mind-bogglingly stupid and isn’t widely-enough understood. Say you have a Wimp program whose wimp block is called !q%=handle%:SYS"Wimp_GetWindowState",,q% PROCdo_something_with_window(q%+4) ... DEFPROCdo_something_with_window(q%) blah blah blah ENDPROC I have seen distributed programs that do this kind of thing. If no error occurs then it works fine. But if the PROC (or anything it calls) raises an error, the global q% has now got 4 bigger, permanently.
It did not have to be like this. Basic could use STMFD in such a way that it could step over the junk from its own internal calls, and hence unwind the stack back to the desired level, restoring LOCALs as it went. But that isn’t how the original BBC Basic worked with a 3KB my-first-program, so it isn’t how ARM Basic works with 1MB desktop applications. TL;DR The short version is: It could be arranged, but it would be a significant change in behaviour. Ideally I’d want a way of signalling the required ONERROR behaviour. |
nemo (145) 2546 posts |
Steve said
Not in standard Basic (without poking about in workspace) and of course you do not get the encapsulation that this affords… for example: REPEAT:REM Oh this is going to be good CASE ERROR OF UNTIL FALSE WHEN43:REM Not in a REPEAT PRINT"Ha ha!" ENDCASE UNTIL TRUE PRINT"Phew!" This is what I was talking about when I mentioned having to give CASE a stack presence – it changes how CASE/ENDCASE interacts with other loops. In particular, in standard Basic this minimal program is legal: REM Short boring program ENDCASE REM The end Whereas in nemoBasic that produces an error (“OS missing from CASE statement” at the moment – more accurate wording will be required!). And conversely, whereas the single line program |
nemo (145) 2546 posts |
Steve also suggested ELSEIF ELSIF perhaps. New keywords are easy, but getting existing editors to work with them is less so, which is why I stick to the existing keywords arranged in new ways never before attempted. That’s not chutzpah, it’s a requirement. I had already planned to use CASE OF WHENflags%AND1:REM b0 set WHENflags%AND2:REM b1 set WHENflags%AND12:REM b2 and/or b3 set ENDCASE Naturally I’m also wanting to do CASE X% OF WHEN<0:PRINT"Negative" WHEN0TO9:PRINT"One digit" WHEN11TO99STEP2:PRINT"Two digits and odd" WHENlist%():PRINT"One of these values" ENDCASE |
nemo (145) 2546 posts |
The |
Clive Semmens (2335) 3276 posts |
I’m trying to work out how that’s any different from exactly the behaviour you’d expect from that code… rhetorical non-question… |
Alan Adams (2486) 1149 posts |
Re Nemo on error handling:
If I read your post correctly, making it possible for the stack to be unwound after an error would also require fixing a little known bug relating to LOCAL ERROR handling. This would seem to me to be a perfectly acceptable change. |
nemo (145) 2546 posts |
It’s a breaking change. Consider this which specifically uses the broken context$="" ONERRORPROCerror ... DEFPROCerror:LOCALA$ A$=REPORT$:IFcontext$>"":A$+=" during "+context$ context$="" PRINT"ERROR: "A$ ENDPROC ... DEFPROCmay_go_wrong:LOCALcontext$ context$="may_go_wrong" x=x/0 ENDPROC “Fixing” the |
nemo (145) 2546 posts |
Clive asked, rhetorically
The aim is to provide the multiclause CASE TRUE OF WHEN K%>64 AND K%<91:PRINT"Uppercase" WHEN K%>96 AND K%<123:PRINT"Lowercase" ENDCASE This works because each of the comparisons evaluates only to X%=5 IF X%AND1 PRINT"Odd number" CASE TRUE OF WHEN X%AND1 :PRINT"Odd number, hello? What?!" ENDCASE The My X%=5 CASE OF WHEN X%AND1 :PRINT"Odd!" ENDCASE This is therefore a direct replacement for |
Clive Semmens (2335) 3276 posts |
Ah, I see what you’re getting at. I’m not so familiar with other languages as I am with BBC BASIC, so I just know about anything other than 0 being sort of TRUE without even thinking about it. And I’ve never used CASE TRUE OF – I’ve only ever used CASE variable OF… |
Steve Drain (222) 1620 posts |
But poking around in workspace is fun, and effective. ;-) CASE ERROR OF UNTIL FALSE Why on earth would any one do that? It is of the class of abusing loops that exists without your |
nemo (145) 2546 posts |
Truthy is a JavaScript term, but is useful for describing the behaviour of Basic’s |
nemo (145) 2546 posts |
It’s merely an illustration of the tight encapsulation, much like calling a PROC. I was demonstrating that this implementation is not the same as your example. Also see the contrived example of contextually doing a LOCAL inside a CASE – no longer possible. |
Steve Drain (222) 1620 posts |
I am with you there. Hence
Its actual meaning is just what is required. The example I posted is just a continuing series of nested multi-line
Yes. That is why the |
Steve Drain (222) 1620 posts |
You present programmers with a problem. |
Steve Drain (222) 1620 posts |
I see your CASE X% OF and raise you: CASE OF <value>[,type%] WHEN <range>[,<range>]^ ... OTHERWISE ENDCASE
|
Alan Adams (2486) 1149 posts |
While we’re thinking of adapting BASIC, think about Unicode. (Or would you rather not?) Once the number of bytes in a string is not equal to the number of characters, LEN, INSTR, LEFT$, MID$, RIGHT$, CHR$, ASC() all become ambiguous. |