#SUSPENDing Disbelief - Conditional Code Generation With EMBEDS

by Tom Moseley

Published 1998-06-01    Printer-friendly version

Hi there! It's been a long time. How have you been?

Well, I'm back in a writing mode (Thank God!) and decided with my newest article to attack the component of the Clarion template language that makes all of the power that we have with Clarion possible, and talk about one of the more powerful methods for conditionally generating source. The component is the #EMBED point, and the method is #SUSPEND.

The #EMBED Point

At its simplest, an #EMBED point is a code insertion point provided by a template writer. An #EMBED point has two required parameters. These parameters are the template label which identifies the #EMBED point, and a textual description for display in the #EMBED listing. For example, this #EMBED point…

#EMBED(%SyncWindowBeforeLookup,'Sync Record routine, before lookups')

Provides an entry that looks like this in the EMBEDS window…

'See the highlighted entry?'
'See the highlighted entry?'

When I'm calling Yooooooou!

An #EMBED point is basically a caller (sorta like an old flame). #EMBED doesn't do anything itself, it just basically calls out "Here I am! Who wants to put code here?" It goes like this. Generator processes a template from the first line to the last. When an #EMBED is encountered, Generator pauses and lets the #EMBED make its calls.

The replies to an #EMBED point's call come from two places. The first is the template user, when he or she adds code to the #EMBED point. The second source of code is other templates. Code plugged in from other templates is placed in #AT sections.

Where it's #AT

#AT in a template is used to specify code that goes into an EMBED point. For example, if you wanted to have code that went into the %SyncWindowBeforeLookup #EMBED point, you would use an #AT structure that said...

#AT(%SyncWindowBeforeLookup)
---Your Code---
#ENDAT

This next bit is important. #AT is ENTIRELY passive. In other words, it doesn't do anything but wait for an #EMBED to call. If an #EMBED is an old flame, #AT is waiting by the phone for a call. If the call never comes, the #AT never generates the code. If a wrong number is called, a phone call won't go through. Likewise, if the label inside the #AT statement is misspelled, your code won't go in. Are any errors generated? Nope. The #AT doesn't know it wasn't needed, and #EMBED didn't get a response. Neither has any way of knowing that anything's wrong. If you happen to link up to the wrong #AT statement, the #EMBED again doesn't know anything's wrong, it just adds the code and keeps calling.

The point is that Generator has no error checking for the correctness of #EMBED and #AT labels, nor is such checking appropriate. The #AT is, as I said earlier, entirely passive. It doesn't know where it's going to be called from, it just knows where it goes when called. #EMBED doesn't know if anything goes in the #EMBED point at all, it simply knows that it needs to make the call. If anyone answers, fine. If no one answers, fine. OK, maybe it'll get lonely, but that's not our problem. Remember, though, #EMBED has no knowledge of what's going to happen when it makes its call.

Checking for #EMBED contents

Since, as I said, #EMBED has no knowledge of the results of its call, there's really no easy way to check if an #EMBED would generate any code. If there were a way to check the contents by using a statement such as #IF(CONTENTS(%SyncWindowBeforeLookup)), which there isn't, then everytime you wanted to check the contents, the #EMBED point would need to make all of its calls, and all of the code generated would need to make their calls, etc. Put a few hundred of these in the templates, and generation slows way down. There is a hard way to check the contents of an #EMBED, though I don't recommend it. If you want to check to see if an #EMBED generates any code, you basically need to create a dump file, have the #EMBED make its calls, check to see if any code was generated, then discard the file. This is exactly what Generator would have to do, and you can see that it's slow.

The problem that we have, then, is that it's often nice, or even necessary, to generate code to support #EMBED code.

For example, let's say that you want to provide an #EMBED code for both conditions of an IF structure...

IF Value = True
  #EMBED(%TrueCode,'True Code')
ELSE
  #EMBED(%FalseCode,'False Code')
END

You really don't, however, want to generate ugly code. You want the ELSE to be there only if necessary, don't you? If there is no code in the %FalseCode EMBED point, you want to generate...

IF Value = True
  #EMBED(%TrueCode,'True Code')
END

If there is no code in the %TrueCode EMBED point, you want to generate...

IF Value = False
  #EMBED(%FalseCode,'False Code')
END

If there is no code in either EMBED, you don't want the IF structure to generate at all.

Remember, there's no way of checking for the contents of an #EMBED beforehand, which means that changing the right value with template code would be difficult. In other words, changing "Value = True" to "Value = False" would be hard, and easy is better. What if we used a CASE statement instead...

CASE Value
OF True
  #EMBED(%TrueCode,'True Code')
OF False
  #EMBED(%FalseCode,'False Code')
END

Then we could just omit the OF section that we wanted, if only we could check for the contents of an EMBED statement. You know what would be nice, and what would suffice? How about if we had some sort of transaction logging for generated code, something like LOGOUT, COMMIT, and ROLLBACK for template code. Then we could start a transaction frame before "OF True", and COMMIT the transaction if the #EMBED generated any code, and ROLLBACK it if the #EMBED point didn't generate code. That would work, wouldn't it?

You're #SUSPENDed

Luckily, there is just such a template transaction framing scheme in place. Instead of LOGOUT, COMMIT, and ROLLBACK, the templates have #SUSPEND, #RELEASE, and #RESUME. They work like this.

When a #SUSPEND is encountered, code generation internally gets redirected and stored. When #RELEASE is encountered, then the code is flagged to be generated. When #RESUME is encountered, code generation resumes. If a #RELEASE has flagged code generation, then #RESUME pastes the redirected generated code first. A #SUSPEND structure requires and is terminated by a #RESUME, just like an #IF requires and is terminated by an #ENDIF. Your templates won't generate if there is no #RESUME.

Source code generation release can be invoked explicitly, using the #RELEASE statement, or implicitly, through the generation of code that is not preceeded by #?, or by a filled #EMBED. Let's take a look at our sample, modified with a single #SUSPEND and #RESUME structure.

CASE Value
#SUSPEND
#?OF True
  #EMBED(%TrueCode,'True Code')
#RESUME
OF False
  #EMBED(%FalseCode,'False Code')
END

The #SUSPEND begins the generation transaction. The OF True case is preceeded by a #?, so OF True is generated, but does not trigger source code generation release. The #EMBED point is the next statement. If there is code generated by the EMBED, an implicit source code generation release is given. When #RESUME is encountered on the next line, the source inside the generation transaction frame is generated ony if the #EMBED had any code. We can then add the same type of code for the False case...

CASE Value
#SUSPEND
#?OF True
  #EMBED(%TrueCode,'True Code')
#RESUME
#SUSPEND
#?OF False
  #EMBED(%FalseCode,'False Code')
#RESUME
END

...which controls generation of both OF structures.

#SUSPEND statements can be nested as well. If an inner #SUSPEND is released, all parent #SUSPENDs are released as well. In our example, we don't want the CASE statement generated if there are no OF structures. We can put the CASE structure in a #SUSPEND structure itself, and nest the others inside.

#SUSPEND
#?CASE Value
  #SUSPEND
#?OF True
  #EMBED(%TrueCode,'True Code')
  #RESUME
  #SUSPEND
#?OF False
  #EMBED(%FalseCode,'False Code')
  #RESUME
#?END
#RESUME

The first SUSPEND creates what we'll call Frame 1, The CASE Value line is preceeded by a #?, so this goes into Frame 1 but does not trigger generation of Frame 1.

The next line is another #SUSPEND, which creates Frame 2. #?OF True goes into Frame 2, and does not trigger generation because of the #?.  #EMBED(%TrueCode,...) would trigger generation of Frame 2, but for this example, let's assume no code exists in this #EMBED point. The next line, #RESUME, closes out Frame 2. Since no explicit or implicit releasing statement was encountered, the code in Frame 2 is discarded.

We now get to the next #SUSPEND statement, which recreates Frame 2. #?OF False goes into this new Frame 2, and does not trigger generation because of the #?. This time, there's code generated in the %FalseCode EMBED point. This code goes into Frame 2, and sets the release flag for frame 2. The next line, #RESUME, closes out Frame 2. Since the release flag is set, the code in frame 2 is generated, and the generated code goes into Frame 1. Since this code is not preceeded by #?, this sets the Frame 1 release flag.

The next line is #?END, which goes into Frame 1, but does not trigger source generation because it's preceeded by #?. The final statement, #RESUME, closes Frame 1. Since code generation of Frame 1 was released, the code is generated into souce, looking something like this...

CASE Value
OF False
  !False EMBED Code here!
END

...which is, after all, what we wanted. For an exercise, open up standard.tpw and search for %StandardControlHandling for a working example of #SUSPEND and #RESUME. You've been using this code for quite a while, and you can probably get a better feel for #SUSPEND and #RESUME if you take a look at the template and generated code.

In Closing

As you can see, there's a lot of power and flexibility when it comes to #EMBED, #AT, #SUSPEND, #RELEASE, and #RESUME. There's a lot more to #EMBED and #AT than I covered here, and we'll talk about that in a later issue. Thanks for reading, and have a nice month!

Printer-friendly version

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $184

(includes all back issues since '99)

Renewals from $134

Two years: $274

Renewals from $224

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links