RFO-BASIC / Basic

The Repository for the files the Basic project that creates the BASIC! APK for Google Play
62 stars 42 forks source link

Calling user-defined functions twice #79

Open jMarcS opened 11 years ago

jMarcS commented 11 years ago

There are some commands, like PRINT, where a parameter can be either a string expression or a numeric expression. (TODO: get a list of all such commands.)

PRINT first calls evalNumericExpression(). If that fails, it backs up the LineIndex and tries again, this time calling evalStringExpression().

Most of the time, that works very well, but it introduces an ambiguity. When evalNumericExpression() returns false, it may mean there is no numeric expression or there is a syntax error (I suppose sometimes it means both).

This has some interesting consequences.

  1. evalNumericExpression() must not call RunTimeError(), directly or indirectly. It can set SyntaxError -- and should: if all syntax errors it can catch are flagged, the caller could reliably use the value of SyntaxError to resolve the ambiguity. But if the caller decides to ignore the syntax error, it can't do anything about console output.
  2. The expression may include a user-defined function. If evalNumericExpression() fails and evalStringExpression() runs, they both call the UDF. It runs twice.

There may be others as well, but it's double-clutching the UDF that concerns me now.

This may not actually be a problem. It can always be argued, and lots of people do, that anything that has side-effects should be kept as isolated as possible. By that reasoning, a UDF should never be used in anything but a LET statement.

That's a little extreme for my taste, but at the moment, it's probably good policy. So I have two suggestions:

  1. If anybody on the forum runs into this problem, our recommendation is to avoid clever programming that tries to do two (or more!) things at once. (My wife, the test engineer, has been known to exclaim fervently, "God save us from clever programmers!")
  2. Be aware of the situation and try to avoid introducing new cases.

My new Time() function is such a case. I've compromised by checking SyntaxError if evalNumericExpression() returns false.

jMarcS commented 11 years ago

I'm currently looking at the commands that use arrays without indices (like a[]), to see if the new helpers I put in with Issue #39 can simplify the code. This may come under the heading of "it ain't broke", but I figure it's worth a look.

In particular, it turns out the SELECT command is one of the cases that can call a UDF twice. The second parameter may be a numeric expression or a string array variable with no indices. The existing code was like PRINT in that it calls evalNumericExpression() first, then checks for the special case of an array variable.

I found that I can reverse the cases. First call getVar(). If the var is an array, and it has no indices, it can't be part of a numeric expression, so it is safe to assume the special case.

And there's a fringe benefit: by sticking a check for SyntaxError after the getVar() call, the "array must be dimed" run-time error shows up on the console only once, instead of three times.

jMarcS commented 11 years ago

Fn.Def changes released in v01.74. Issue still open.

olimaticer commented 8 years ago

Hi Marc,

In my opinion this is a serious problem. But an ad hock not smart solution is to parse the string function call: If you change PRINT GetBarCodeSimple$(globals) to myResult$ = GetBarCodeSimple$(globals) PRINT myResult$ you get the correct result.

/ Gregor