Monday 16 July 2012

RTFM, Cameron: CFCASE

Here's another one to file under a "stating the bleeding obvious" category (like the <cfparam> one from y/day).

Possibly like everyone else who isn't 100% au fait with the docs and the way all CFML tags work, and thinking I know better, I have been caught out in the past trying to do this:

<cfswitch expression="#someVariable#">
    <cfcase value="#SomeDynamicValue#">
        <!--- do some stuff --->        
    </cfcase>
</cfswitch>



And this yields the error:

The following information is meant for the website developer for debugging purposes.
Error Occurred While Processing Request

This expression must have a constant value.


ColdFusion cannot determine the line of the template that caused this error.This is often caused by an error in the exception handling subsystem.


Right, so I can't have a variable in the <cfcase> statement.  Duly noted.  And what you should note is what I "duly noted" is not actually what ColdFusion was actually telling me.  I'll get back to that.

Yesterday I started researching another blog post I was intending to write - since shelved because I'm not as clever as I thought I was going to be - and I ended up following a winding path from <cfhttp> to looking at CSV-processing functions on cflib.org, to deciding to write my own because the ones on CFLib only seem to cater for simple cases (and somewhere along the way agreeing to help Ray on CFLib), and I arrived at having a switch statement like this (pseudocode):

switch theNextChar
    case double-quote
        it's quoted
    /case

    case comma
        it's an empty record
    /case    

    case CRLF
        it's the end of the record
    /case

    case else
        it's a simple record
    /case
/switch

 Doing the case for the quote and the comma was easy:

<cfcase value='"'>
    <!--- it's quoted --->
</cfcase>

<cfcase value=",">
    <!---it's an empty record --->
</cfcase>

But then I got to the next one - the CRLF - and found myself thinking... err, OK, how do I express CRLF in CF using a static string... without using a dynamic expression?  Obviously I could use #chr(13)#,#chr(10)# to express each one, but that's no good because it's dynamic and I can't have dynamic values in a case.

I got completely flumoxed, realised my conditions where more complex than would work with a switch anyhow, and changed it to an if/elseif/else instead.  Vowing to return to this situation later.

[six hours later I had "finished" the task of writing the CSV parser... I thought it was gonna take about an hour.  Grumble]

I watched telly for a while, and mulled over this thing of expressing CRLF without using chr().  I drew a blank.  So on a whim I just decided to use #chr(13)#,#chr(10# in my <cfcase> anyhow. It worked.  And I was confused.  I know one cannot use a dynamic expression in a <cfcase>.

Disabused of this, I started refactoring some code, and called a variable to reflect CRLF (because I was using the sequence elsewhere in the code, so figured it tidier to reuse the variable rather than hard-coding), like so:

<cfset crlf = chr(13) & chr(10)>

<cfswitch expression="#someVariable#">
    <cfcase value="#crlf#">
        <!--- it's the end of the record --->       
    </cfcase>
</cfswitch>

And this resulted in our familiar error:

The following information is meant for the website developer for debugging purposes.
Error Occurred While Processing Request

This expression must have a constant value.


ColdFusion cannot determine the line of the template that caused this error.This is often caused by an error in the exception handling subsystem.


At which point I twigged what was going on. By doing something I always recommend to other people I'm helping on the Adobe forums when they get an error: read the error.

Firstly, it's a compiler error, not a runtime error.  This means ColdFusion is finding the problem before any of my code is executed.  So my variable doesn't exist at this stage, so it makes sense that I get an error.

Secondly it doesn't say the value can't be dynamic (ie: it must be static), it says it must be constant.

And the penny dropped.  There's nothing wrong with having an expression for that value, but it must be a constant expression (like... err... how both the error and the docs for <cfcase> clearly state.  Oops).  So chr(13) and chr(10) (and for that matter stuff like pi()) are all fine: they resolve to a constant, and the compiler is clever enough to to a bit of "runtime" kind of stuff and resolve #chr(13)# to be a "\r" in the compiled file.

Next I tried #now()# as the value: same error.  now() is not constant.  Fair enough.

Lastly I tried to see how clever the compiler was, and tried this:

<cfset myVar = "A">

<cfswitch expression="#myVar#">
    <cfcase value="#returnsConstant()#">
        Fourth
    </cfcase>
</cfswitch>

<cffunction name="returnsConstant">
    <cfreturn "A">
</cffunction>

Unsurprisingly by now, this gave an error.  But not the error I was expecting:

The following information is meant for the website developer for debugging purposes.
Error Occurred While Processing Request

Method returnsConstant with 0 arguments is not in class coldfusion.runtime.CFPage.


The error occurred inD:\websites\www.scribble.local\cf\cfml\tags\flowcontrol\switch\dynamic.cfm: line 5
3 : <cfswitch expression="#myVar#">
4 : 
5 :  <cfcase value="#returnsConstant()#">
6 :   Fourth
7 :  </cfcase>

So I don't think that's the error I should get, but it stands to reason I get an error.  I don't think the compiler should allow that code, because returnsConstant is a variable for all intents and purposes, so it should be disallowed for that reason.  It should not get to a runtime situation before erroring.

Anyway, the bottom line is I should pay more attention to the docs.  Which for <cfcase> are here.

Cheers.

--
Adam