PROGRAM
INCLUDE('StringTheory.inc'),ONCE
MAP
testIt PROCEDURE(STRING p),LONG
END
st stringTheory
CODE
MESSAGE('entering...')
message('test 1: ' & testit('xxx'))
message('test 2: ' & testit(''))
message('test 3: ' & testit(' ')) ! 3 spaces - testit returns 3
RETURN
testIt procedure(String p) !,long
code
if p then return 1.
I would have hoped for a compile error as if a blank string is passed to test1 then it doesn’t return a value, but just falls out the bottom. I didn’t see an error or any warnings either.
however testing indicates it returns the length/size of the passed string.
I didn’t expect that!
is this documented anywhere?
perhaps it is just something strange in my setup (using C11 13505) - curious to see if others can confirm it.
If there is no RETURN on some branch of function’ body, and execution was routed to this branch, returned result is random - it depends from value of the EAX register if result type is integer numeric. If result type is STRING, missed RETURN can cause the fault of the string stack. If result type is REAL, returned result is random - it depends from values on the thread stack at the call time.
In your case the EAX register contained the size of the actual parameter’s string size.
There was an article on CMag authored by Gordon Smith on the topic. IIRC at the time of writing your code would always GPF or shutdown.
His suggestion was to always add an Explicit RETURN, then type your code above it. In your case it would cause a compile error without returning a value from a function. An implicit final Return, like you have, does not error.
The explicit RETURN also provides a line to set a debug breakpoint so you can step out of a procedure. Same with always having an explicit EXIT on a routine can help in debug.
thanks Alexey - I perhaps should have phrased it “testing indicates in this case it returns the length/size of the passed string” which would have been more accurate given EAX register contents will vary as you indicated.
Would you consider the lack of a compiler warning/error when a typed procedure has a reachable path with no RETURN value to be a compiler bug, or would you consider it expected behaviour?
This is a quite ambiguous situation. I don’t know the reason why David Bayliss didn’t do this. Here is my understanding.
Clarion has - in my opinion - a very controversial feature: possibility to return from procedures by RETURN statements executed from their ROUTINEs. The RETURN statement in a ROUTINE also can be used with expression returned to procedure’s caller. ROUTINEs from the compiler view point are separate compiling units. So, if there is at least DO in the procedure code, the compiler can’t be sure whether the execution flow reaches some RETURN statement because ROUTINEs are compiling after the procedure they belong to. It may reach or may not reach. Thereby no error or warning is firing.
If procedure has no any DO statements, potentially the error could be posted. But it would be inconsistent behavior to fire errors in one context and not fire in another one for the same thing.
Clarion must have this for Legacy template code to work. It allows a ProcedureReturn ROUTINE that does a RETURN. So from anywhere you can code DO ProcedureReturn and end the Procedure.
Yes…
I think Geoff’s code is just a very simple example… if not maybe Geoff was thinking about something else, like the next changes, and he missed it. The Compiler sees any RETURN Something so does not warn about a Function not returning a value.
IMO a common way to make this mistake where you do not forsee more values, so skip the ELSE.
StatusName PROCEDURE(STRING StatusCode)
CODE
CASE StatusCode
OF 'A' ; RETURN 'Active'
OF 'I' ; RETURN 'Inactive'
! ELSE ; RETURN 'Unknown '& StatusCode !<-- should have done this
END
! Oops you failed to RETURN on any other letters
! RETURN 'Unknown '& StatusCode !<-- or better Return at end always
Another way (better way?) One Return statement:
StatusName PROCEDURE(STRING StatusCode)
RetName PSTRING(16)
CODE
CASE StatusCode
OF 'A' ; RetName='Active'
OF 'I' ; RetName='Inactive'
ELSE ; RetName='Unknown '& StatusCode !can be omitted to return Blank
END
RETURN RetName
If he had always started with an Explicit RETURN then added new code above it he’d have this:
testIt procedure(String p) !,long
code
if p then return 1.
RETURN !Explicit ... added first
That RETURN without Value in a Function would cause an error.
And I accept such approach for DOS versions. But there were other variants on transition to Windows. The ProcedureReturn ROUTINE plays the role for the finally block of the C++ like exception handling try-catch-finally construct. Constructs with similar semantic can be used outside the context of exceptions, for example, structured jumps in the EL76 and some other languages. Structured jumps have many benefits, for example, safe and controlled passing the execution flow to desired points, possibility to use parameters in the statement, etc.
ROUTINEs are useful things, but usage them for some goals was incorrect - in my opinion - design decision. And this design after introducing local CLASSes led to serious problems. Part of them can’t be solved.
Hi Carl, it’s hard for me to know if this was intended as a joke or not - we programmers are known for reading things literally - I am reminded of that old joke that goes something like:
A husband who is a programmer is sent to the grocery store to get a loaf of bread. Just as he is going out the door, his wife calls out “and if they have eggs, get a dozen”.
Husband returns home with twelve loaves of bread…
Anyway the subject is “falling out the bottom of a procedure” so it is quite deliberate to show the effect. I was surprised by it as I had not come across it before in all my years doing Clarion code.
certainly - I am working hard on the next version of VitTransform and it has largely taken over my life. (My wife, who probably doesn’t trust me with getting a single loaf of bread - joke! - thinks it has taken over our life.) Anyway this issue of the return value came out of that project where the next version has more static analysis - like “known ranges” on a value - which allows more transformations. At times it is surprising me and starting to look like magic - reminding me of Clarke’s Third Law Any sufficiently advanced technology is indistinguishable from magic.
Code appeared to be a simple example, obviously it had no real purpose, right?
I’m often thinking about the next code I’m going to write, then easily miss that last detail, like a final Return.
This is where team of two programming shines. The guy typing is thinking in the future. The 2nd guy is reading, so in the past and tends to catch errors #1 is no longer thinking about.
I really like using a Local Class instead of Routines. You can Pass Parameters and Return Values. I also like the structure of declaring the CLASS at the top as an Index to what “local routines” are available.
I created a tool on GitHub called Do2Class to convert Routines to a local class I usually name DOO. So DO MyRoutine becomes DOO.MyRoutine().
One problem I address is a Return inside a Routine cannot work the same from in a Class Method. It requires some refactoring.
its real and only purpose was to demonstrate the issue - falling out the bottom returns an unexpected/unpredictable/undefined (take your pick) value - as Alexey explained (whatever is sitting in the register).
Sorry for the thread drift but I just thought I’d give the future Vittransform transformation that I saw yesterday that I was referring to.
I am using Claude and it had written some pretty ugly or complicated code:
st.setValue(st.sub(1, y) & ' ' & pStr & | ! so inserting each new note just after
' !' & st.sub(choose(y + 1 < 0, st._DataEnd + y + 1 + 1, y + 1), st._DataEnd - y)) ! the '!' rebuilds left-to-right order
as part of the testing on each change I do a dogfood round where it runs on its own sources and this was transformed to:
st.insert(y + 1, ' ' & pStr & ' !') ! so inserting each new note just after the '!' rebuilds left-to-right order
B5000 machines with their stack-based architecture and tagged memory also heavily influenced the Soviet Elbrus series of mainframes and supercomputers. The first two generations of the series featured tagged memory and stack-based CPUs that were programmed only in high-level languages. There existed a kind of an assembly language for them, called El-76, but it was more or less a modification of ALGOL 68 and supported structured programming and first-class procedures.