Syntax for HTML Island in Script

i am with @ddspringle on this issue.

I would like to see
a) good examples of how this would be used that cannot be done with existing features
b) a marketing case for developers to use this feature. (no point in adding a “secret” feature for the GT 5 Taggers)

As has been widely mentioned before, I don’t see this adding anything to the language that cannot be done more elegantly in other ways.

When I have talked to developers about this subject (and trust me, I look at a LOT of different code and talk to a bunch of different developers) and it is generally because they like using SQL and the script format isn’t as “nice” as using tags. Is that what we are solving? (yes , I know. HTML output, right… including a template isn’t good enough for some reason)

Enough of the arguing, turn this into a feature description:

To solve problem X we want to implement Y.

I haven’t been won over despite coming up with some solutions. I still think this is just asking for people to abuse it and in turn giving Lucee a bad image for implementing something horrible.

1 Like

Ah, yeah that’s it haha… I derped. :wink:

This argument has gotten a little heated and I would like to apologize to anyone who I’ve offended or launched ad hominem attacks against. It was unnecessary to do so to make my points and I regret not refraining from such pointless mental masturbation.

I’m out. You guys sort this out. I will keep my fingers crossed that we don’t unintentionally hurt Lucee by implementing CFML tags inside of cfscript.

2 Likes

Denard, everything you say is opinion restated as fact.

You have 2 decades of experience with CFML? I have 15 years. For all intents and purposes, we have the same level of experience, assuming a logarithmic learning curve. (Which can be substantiated as fact)

I am NOT part of LAS, nor am I affiliated with Lucee in any way, other than an interested party.

Everything you state is your opinion.

“Moving the language backwards” is subjective.

“Abuse of CFML”… can’t exist. You can’t abuse a language. It’s not a person. It either works, passes unit tests, and functions as the requirements says it should, or it doesn’t. As far as the server process or language is concerned, it’ll do what you tell it to do.

You CAN argue code reuse, future readability, better ways of doing things, efficiency of the solution, and adherence best practices… but again, these are subjective concepts, which are decided by a developer or organization, and are NOT facts.

You are welcome to believe that this will be “abused”, in your judgement. Just as I am welcome to believe that it’s very unlikely, based on my experience.

You establish the standards, FOR YOUR PROJECTS, however you like. If you decide this feature should not have non-html tags in it, then you enforce that yourself, in your own projects, through in person or automated code reviews. If you decide this feature is crap, don’t use it. And if you get code from someone else who does, rewrite it or send it back to them until they do it right (by your measurements).

In MY 15 years of experience, consultants will always do what’s easiest for them. And I’ll always send it back until it’s right, based on our best practices, and based on the requirements of the job. That’s YOUR job, not the language’s job, and not the servers job. Just as it’s my job in my organization to make sure our developers are writing code in a standard, reusable, readable way, based on our standards.

For example, it’s been widely accepted there are BETTER options than cfform format=“flash”, or the builtin YUI components in coldfusion. In our organization they’re banned. However, someone else using that component is not language abuse, no matter how bad you feel the practice is - it’s a supported feature of ACF and if people want to use it, for whatever reason, including laziness, that’s their right to do, and your right to reject until it meets your standards. Just like I enforce var scoping in all CFCs, and params in all sql statements, and a number of other things.

We use PD4ML for PDF document creation, we NEVER write new code using cfdocument. Does that mean in my situation that cfdocument is language abuse, and it should be excised from coldfusion? Is it my job to go on a crusade against anyone using cfdocument, up to and including lobbying Lucee and ACF to remove cfdocument from the code, because it’s language abuse and some hypothetical developer, who doesn’t like CF to begin with, has their own OPINIONS about what makes good or bad code, might judge what I’ve written? I don’t give two shits what they think about my code, the language, or anything else. It’s none of their business.

I’ve seen crap Perl. I’ve seen crap Python. I’ve seen crap Java. Arguing a language feature shouldn’t exist because of the PERCEIVED possibility that some may abuse it is untenable because you can’t quantify it. It’s FUD. And the thought that somehow the CFML community will die off because of the judgement of others is insane. What I have found through many years of experience is those who make the quickest snap judgements and have a lot to say about a language’s structure and best practices are usually the ones who have the least amount of experience with the language to begin with. (note this is NOT aimed at you - but rather the hypothetical “other language” zealot)

This argument is no different from any of the opinion based arguments of any language community. Spaces vs tabs. Natural DB keys vs Autonumber fields. Composite key usage. ORM or roll your own queries. Which CF framework to use. Which javascript framework to use. Everyone has a choice.

And if some hypothetical “5-tagger” out there decides to “abuse” this feature in your opinion, that reflects on THEM, and their inexperience, not on Lucee.

What I’m far more concerned with is one, a few, or a group of community members deciding for the ENTIRE community what individual best practices should be. It’s none of your business to correct my code, nor take a red pen to it. That’s my business. Stay out of it.

I think it’s pretty clear you and I disagree. I think it’s pretty clear that there’s disagreement on a lot of these topics. But none of it matters. As I said days ago, can we PLEASE take the FUD, “religious” arguments, and cruft out of this discussion?

2 Likes

Me too. Just in the opposite direction.

1 Like

I think your examples are causing confusion then. At least for me.

Because I would expect SQL to be processed server side. Javascript/HTML would be pass through to client side… sort of, because you’re using # variables, so those need replacement.

Is this a glorified cfsavecontent block? If so, all your examples work… I’d expect the JS/HTML examples would later be passed to writeoutput. But passing the SQL to writeoutput doesn’t make sense…

With @markdrew’s example of moustache and others, again… is this being processed server side or client side? Because the idea of having multiple plug-in parsers for Lucee… I like that. Load a moustache plugin and lucee can now render *.moustache files, and also deal with moustache{} blocks, using bindings to expose the requisite scopes. This also means you can use moustache code as both server-side rendering, and javascript rendering, depending on how you provide it. (Write it out or render it first)

Were you to use the moustache{} block to return a template to the browser, you’d probably use html {} instead. (It’s moustache code, but you don’t want lucee to parse and execute it like moustache) . That blurs your syntax highlighting use case - so maybe the parser needs an option to differentiate between “I want this content saved in a variable and it’s a moustache template that should be highlighted as such” and “I want this moustache template rendered as HTML based on {{ }} variable replacements against my PageContext so I can reuse this moustache template in both JS and CF”… Because ultimately those are completely different parsers.

If you’re just treating ‘’’ ‘’’ as a construct that replaces variables, (like, a GString in groovy), then it’s really more like cfsavecontent than it is a renderer. If I missed this I apologize but it’s an important distinction.

I wouldn’t want Lucee to take some different action based on a comment in a block. Comments should be comments and don’t necessarily have to follow (m)any rules. If all you’re concerned with is syntax highlighting, then Lucee would only have one parser, and it would ignore the coment.

I guess I’m with @markdrew in that I’d love to see some real-world examples of what we’re trying to solve.

Personally, I’d love to have

java {
}

Or

groovy {
}

Because guess what I use now? <cf_groovy />. I’d rather be using script. Does the use of a groovy script-based island within a script-cfc/cfscript block sway anyone?

I believe that Java code or Groovy code parsed as JSR223 to be far more readable than all the new() statements, javacast() calls, running new() against static objects just so you can reference them, and all the other tricks required to make Java/Groovy code work w/ CF syntax. And implementing an interface or anonymous inner class through Java dynamic proxies is interesting, but sometimes it’s easier to do java in java.

But then again, I’m assuming server-side parsing and execution. The html {} block or cfml {} block would behave differently. (likely based on the parser implementation, which would either return the rendered string, or writeoutput - because it should probably have the same options as render() )
(The above notes in no way assume Option 4 has won)

Or is the solution to be able to specify a body for a custom tag in a portable way in cfscript…

cf_groovy {
  This is java code
}

cf_html {
  this is my HTML renderer,  which really, runs render(), or some combination of Evaluate() and cfsavecontent
}

Using a custom tag in cfscript implies the ability to provide a tag based body IMHO.

2 Likes

The comments are simply hints for syntax hilighters and similar tools. These are CFML comments so they will be ignored by Lucee.

The contents between the backticks will be processed according to its placement, so if it’s inside server side it will be processed as such, and if it’s client side it will simply be written to the response stream to be processed by the client.

So the result of a backtick block is a string, after # variable replacement?

Which would mean your examples are more like this.

/* Server Side processed */
queryExecute('''<!--- sql !--->;
SELECT *
FROM some_table;
''');

/* Passed to client after variable replacement */
writeOutput('''<!--- javascript !--->
console.log("Hello from JavaScript!");
''');

writeOutput('''<!--- html !--->
<h1>Some <em>HTML</em> markup.</h1>''');

writeOutput('''<h1>Default, processed at <em>#now()#</em></h1>''');

You mention you wanted to get away from strings - but I’m assuming just because of the fun of keeping " and ’ straight, and preventing highlighting everything as “this is a string” color.

Because let’s be real - Lucee doesn’t have a HTML parser, so even “html tags only” parser just means a CFML parser that errors on certain tag use.

Are you saying you want

'''<h1>Default, processed at <em>#now()#</em></h1>'''

To be output directly? or the result saved as a string? Because to me, parsing “tag islands” in script implies the CFML parser will compile that block into code.

Were the last example in a cfm file, you’d end up with a Java class with the java equivalent of:

writeOutput("<h1>Default, processed at <em>");
writeOutput(now());
writeOutput("</em></h1>");

So are we outputting to our ResponseStream or are we returning a String? Exactly what behavior is Lucee providing?

When you use the tag island, the Parser is switched from cfscript to cfml.

So whatever you put inside the triple backticks is processed as if you had something like, so the following this pseudo-code snippets are equivalent.

Current syntax (assuming you are inside a cfscript block):

</cfscript>
  <style>h1 { color: #00F; }</style>
  <cfoutput>
    <h1>"Tags" n' Script on #dateFormat(now(), "mm/dd/yy")#</h1>
  </cfoutput>
<cfscript>

Proposed syntax (without evaluating # signs):

```
  <style>h1 { color: #00F; }</style>
  <cfoutput>
    <h1>"Tags" n' Script on #dateFormat(now(), "mm/dd/yy")#</h1>
  </cfoutput>
```

But of course the first (current) syntax will not work in a script-only file that has no opening <cfscript> tag, while the new syntax will.

I would personally prefer that it will be processed as if it is inside a <cfoutput> tag by default, which means that it will be like so:

Proposed syntax (evaluating # signs by default):

```
  <style>h1 { color: ##00F; }</style>
  <h1>"Tags" n' Script on #dateFormat(now(), "mm/dd/yy")#</h1>
```

Both of these examples assume cfoutput is off - earlier examples assumed it was on. (i.e. #now()#)

Note also the java equivalent of

      <style>h1 { color: #00F; }</style>
      <cfoutput>
        <h1>"Tags" n' Script on #dateFormat(now(), "mm/dd/yy")#</h1>
      </cfoutput>

Would just be

      writeoutput("<style>h1 { color: #00F; }</style>
        <h1>\"Tags\" n' Script on ");
      writeoutput(dateFormat(now(), "mm/dd/yy"));
      writeoutput("</h1>
");

Your thought being that the performance of the above statement is better than having Java resort to using a StringBuffer/Builder?

No judgement, just making sure I fully understand what you’re saying.

Sure.

First, StringBuffer should not be used in general. StringBuilder replaced it because StringBuffer is synchronized which in 99% of the cases adds unnecessary overhead for no reason.

Secondly, we are talking here on the parsing and compilation phase. You are talking about the runtime phase.

The generated Java class file will have statements like

response.getOutputStream().write("<style>h1 { color: #00F; }</style>");`

but these are written straight to the response stream and there is no need to write them to a buffer or to a StringBuilder object simply for the sake of writing that buffer to the response stream later. Doing so will take more memory and more cpu cycles for no reason.

In other words, writing in your CFML template:

<h1>Lucee</h1>

will always be faster than

<cfset msg = "<h1>Lucee</h1>">
<cfset echo(msg)>
  1. Yes, correct, anything above Java 1.4 should be using StringBuilder (and it’s what javac compiles any “This is something” + “Something Else” statement) . Hence I included both cases.

  2. Also correct, I’m talking about the runtime phase, because that’s where the performance that matters is - since compilation should happen once. I was using writeOutput just so it was more understandable, but yes, you’re right, the full statement would be more like the response.getOutputStream.write() you’ve written.

I submit that the performance benefit is negligible for this specific use case because it’s so tiny and the RAM usage is minimal. Most of the times when this would matter can be replaced with cfcontent file=“something”. (Because spraying the network stream with a 100MB PDF IS noticeable in both memory and CPU cycles) BY FAR the network output part will be slower than the “writing to memory” part.

While I agree in theory, you’re talking about nanoseconds. In fact, the JVM hotspot compiler might even optimize the variable out, leaving you with almost exactly the same bytecode.

I think it’d be hard to find a case when the tag island were so big as to cause any sort of measurable performance impact.

So let’s look at other reasons:

  1. Script tags are independently compiled (as per @micstriit )… Since this wouldn’t change, some of the examples you’ve posted wouldn’t work… Because having a tag island within a for would break things. (because the open and close braces are in different cfscript contexts) So it can’t be syntactically equivalent as you’ve indicated, without some other hoops. (see post 79 https://lucee.daemonite.io/t/syntax-for-html-island-in-script/2347/79)

It seems the only way to go switching back and forth is for the page to be processed in multiple passes based on the primary parser. Right now that’s CFML, unless it’s a script CFC. You’d have to parse the entire CFML, with placeholders for the cfscript bits it can’t understand. Then go back and parse those cfscript bits, and put them in the compilation in the appropriate place. Or as Micha said, basically each parser treats the other language as a string.

If CF is going to be smart enough to point out the mismatched braces it would have to treat multiple script blocks as 1 unit.

This seems really really hard to do. Not saying it’s not a good idea, but cost/benefit is a real thing.

I know you’re not looking for string eval, but treating ‘’’ thru ‘’’ as string delimiters eliminates that problem, because cfscript can easily find the beginning and end.

I’d actually prefer the result end up in a string, because then it gives more options - pass into QueryExecute for instance. I don’t think it feasible to change the functionality based on context.

If ''' is equivalent to </cfscript> block </cfscript>, then queryexecute(</cfscript>blah<cfscript>) makes no sense.

IMHO, it should either always return a string, or always do straight output - and of those two options, always returning a string gives far more flexibility. Like passing the result into a moustache parser. Or a GroovyScriptEngine object. Or anything else.

Which also really just means it’s a language markup equivalent of cfsavecontent with the “variable” being the return value.

  1. Migration issues - will this actually help? TL;DR probably not.

I think most CFC conversions will start with:

<cfcomponent>
  <cfscript>
    nothing in script
  </cfscript>
  everything not yet migrated
</cfcomponent>

As time goes on, stuff moves from the tagged area to the scripted area, until the cfscript can be removed and the file can go component {} natively.

I don’t know that having a tag island makes this job any easier/better.

Note that personally I like cfquery way better than queryExecute, and we have a lot of good reasons to go that route here. I don’t see that changing. Thankfully, I have the option to use tagged CFCs to make that possible in those specific scenarios. And that doesn’t make me a 5 tagger. But I’d rather have a tagged CFC than a tag-island for each cfquery.

  1. Ability to render output - largely removing quote mismatch issues

I see the merit here - having to do writeOutput("html"); means you can’t use " anywhere in the string literal, it has to be escaped as #Chr(34)# or some other option. This IS a pain point. Being able to do writeOutput(''' whatever I want '''); does make sense to me from a string parsing perspective.

But I think the original examples also make sense in that cfoutput should be explicitly on. Basically, this doesn’t sound like a tag island at all to me - it sounds like a multi-line string solution, and were we to put this string in WriteOutput or in cfset, it would evaluate hash variables.

Much like groovy:

It means for HTML output you explicitly add the writeoutput(), for queryExecute you explicitly put that in, for other languages, you return the result or you execute a parser.

It might also be worth adding a cfoutput=“yes” option to cfsavecontent - which eliminates the need to have a cfoutput in your block.

I get from a java perspective writing to the output stream directly makes sense from an efficiency perspective, I’m just having a hard time imagining a case when it would be noticeable… and I think the benefits of multi-line strings outweigh the possible performance issue.

I think this is in line with what @markdrew’s examples were illustrating and eliminates the possible concerns from @ddspringle, because multi-line strings wouldn’t evaluate <cfloop>, just like cfset doesn’t. (Basically it’d use the expression parser, NOT the CFML parser)

I’m also a fan of @markdrew’s include with output variable or include returning a string (i.e. include like a function).

And even though it’d be cool I’m willing to accept that parsing other languages directly (like moustache, java, groovy) at the Lucee compilation level is unacceptable scope creep. (especially given how easy it’d be to pass multi-line strings into a function)

However, I believe it’s also possible to have some contextual logic - though I’d defer to @micstriit and @Igal… as an optimization, if the cfscript parser sees writeoutput(‘’‘some big string’‘’); then instead of dumping in a string buffer, it could write to the stream. That would provide an optimization for stream output without losing the flexibility and benefits of everything else, and essentially just be a mode toggle within the multiline-script parser/expression parser.

And just FYI, based on the source I believe

  1. cfsavecontent calls getBody() after the tag executes - so, big string buffer.
  2. render calls the Lucee Renderer directly, and supports a dialect, which right now needs to be a CFML or Lucee dialect. (Which I do not believe can be extended through extensions)
  3. cfoutput uses tag visitation to output directly to the stream. (not surprising)
1 Like

I didn’t read the whole thing, but I want to clarify –

The compiled code should be the same. This is not a performance enhancement. The performance topic has only come up after some have proposed other “options”.

The proposal is purely syntactical, to make coding easier.

I personally prefer to always write cfscript instead of cfml. Once we have all-script files, e.g. index.cfs, I will use it much more often than a cfm file. The problem is that then I can not easily output HTML markup. It has to be in a string, escaped, etc. as mentioned above.

This enhancement would help also now, because I have many blocks of <cfscript>, and then I have to “close” them in order to output markup.

1 Like

I think I’d like to reframe this so we can hopefully make a decision.

Discussion point 1)

a) It’s currently possible to write something in CFML and switch to cfscript parsing.
b) It’s currently possible to switch out of cfscript parsing if the template is a CFML template, though it may be/look clumsy, largely because of how </cfscript> and <cfscript> blocks look around all your markup.
c) It’s currently not possible to switch out of cfscript parsing if the template is CFScript native.

Given that in CFML you can switch in/out of cfscript at will, it makes logical sense to me that in cfscript (the arguably superior syntax) you’d be able to switch to CFML for those areas where tagging are more appropriate. Whether those tags are CFML, HTML, XML, etc… It shouldn’t matter. The developer has deemed that that section of code is more readable/writable in tagged format, and they’re the ones solving the problem, and making all the design decisions. (good or bad)

Since most developers agree cfscript is the better syntax, or the future, or is cleaner, etc… It makes sense that more developers will want to use cfscript syntax, including in their views, where markup is necessary.

Personally, I’d prefer something like:

   for(i=0;i<10;i++) {  {?
       <cfoutput><option value="#i#">Option #i#</option></cfoutput>
   ?} }

Whether “cfoutput mode” is on or not when the block opens is up for debate - or perhaps controllable through cfsetting? I can see if the primary use case for this is markup, in most cases, you’d want the cfoutput mode to be on.

I’m also thinking {? ?} could implicitly be a block as far as CFScript is concerned, so the extra braces above are extraneous, and equivalent to this:

   for(i=0;i<10;i++) {?
       <cfoutput><option value="#i#">Option #i#</option></cfoutput>
   ?}

I prefer {? ?} because it looks like what it is - a block of code…albeit mostly echoed out code, but code none the less. I prefer it to backticks, apostrophes and quotes because the convention for using those constructs in other languages (groovy, slack, etc) in general means “Multi-Line string” not “Block of Code”.

The corollary to this is that if this works:

    <cfloop index="i" from="1" to="10">
      <cfscript>writeOutput("something "&i);</cfscript>
    </cfloop>

Then so should this:

    <cfscript>
      for(i=0;i<10;i++) {
    </cfscript>
    <cfoutput>something #i#</cfoutput>
    <cfscript>
      }
    </cfscript>

If it doesn’t already. Which according to trycf, it doesn’t, as Micha pointed out earlier. Since the CFML parser is able to deal with it, so should the cfscript parser, even if it only works with {? ?} constructs where the cfscript engine is the “primary” engine. (see the first example above with the tag island)

Discussion point 2) Possible Scope Creep items

a) Should cfscript support multi-line strings? I think it should. Whether that’s ``` or “”" or ‘’’ I don’t much care. It could be used to solve this problem, or other problems like wanting to have markup or code in other languages passed into another parser. Multi-line strings should be evaluated with the expression parser, not the cfml parser. (which means yes to #var#, no to cftags)
b) I like Mark Drew’s suggestions regarding include, and the simplification of removing the savecontent requirement - and I think that should be considered.

Discussion point 3)

Supporting any of the above is a separation from what ACF supports - but I see it as an added benefit for the developers using Lucee, and as long as those writing in Lucee know it’s a Lucee specific feature, it’s up to them to use it or not. (Just like any other Lucee-specific feature)

Can we reach some sort of consensus?

1 Like

On discussion point 1, I agree that logically you should be able to switch to tags in cfscript, just as you can switch to script in a tag-based component or cfm template. In addition, script-based templates could actually be a thing (e.g. drop the requirement for <cfscript></cfscript>) and in those you might also want to switch in to tags. At the end of the day, if a developer makes a determination that it’s a better solution than include or something else, so be it, it should be possible :slight_smile:

As for the syntax, I don’t think we need to invent something entirely new, or unexpected, or anything that would even require much learning (e.g. new character combinations {?). The backticks might not be great because it makes code harder to share or embed in Markdown, and as Micha noted the script based block (such as somekeyword { }) it’s not as easily parsable due to what it contains. A tag based block might do the job though?

Immediate output

    var name = "world";
    <cfoutput>
        <h1>hello #name#</h1>
        <cfloop from="1" to="10" index="i">
            #i#
        </cfloop>
    </cfoutput>

Captured output

    var name = "world";
    var out = <cfoutput>
        <h1>hello #name#</h1>
        <cfloop from="1" to="10" index="i">
            #i#
        </cfloop>
    </cfoutput>
    // do something with 'out'
    return out;

This avoids the need for savecontent, and doesn’t really introduce any new concepts that would require significant documentation. The tag could be something other than cfoutput but I think the intention with cfoutput is entirely obvious, particularly when you want immediate output, and it might be less clear with other names.

On discussion point 2, (I know this is different to the suggestion you’re making, but) multi-line strings are supported in script as;

	var test = "
		this is a
		multi-line
		string!
	";

For strings/output that is parsed or optimised earlier, I think that the “output blocks” above would be the perfect use case? This means that we probably don’t need to introduce another type of multi-line string syntax (i.e. no need for `, “”", etc), the output blocks would take care of it?

On discussion point 3, this can always be an issue with new features, but at the very least there is evidence that ACF has followed Lucee in the past :slight_smile:

1 Like

It just looks wrong to me - having a script expression = a tag. But not enough to argue strongly for something else. Maybe cfsilent or something else that implies cftag without output should work as well. Or just <cfml>.

The main concern here being if the multi line string contains single or double quotes, especially if it contains a language (like java or SQL) where single and double quotes are not interchangable. That would be the reason to introduce a multiline string syntax.

Yes, having the assignment there seems wrong to me as well.

I don’t see an issue with using backticks as most markdown processors support 4 leading spaces or a tab, e.g.

```backticks in markdown```

But, I actually like the idea of <cfml>. After all, that is the equivalent to <cfscript> so it seems like a very intuitive option.

Possibly even with a boolean attribute like output (name can use more thought, with default false), which will be the equivalent of wrapping everything inside those tags with an implicit <cfoutput>, so that

<cfml>This is option #5 @ <cfoutput>#now()#</cfoutput></cfml>

and with output=true

<cfml output="true">This is option ##5 @ #now()#</cfml>

Surely it would be <cftag> vs. <cfml> since <cfscript> IS CFML too ??

I think not.

CF-Markup-Language is markup tags, like HTML, XML, etc., while CF-Script is… well, script, akin to JavaScript, VBScript, etc.

Saying that “CF-Script is CFML”, is like saying that “JavaScript is HTML”. Surely you would agree that that is incorrect?