frondoc picture

What is Frontier?
Download
News & Updates

Web Tutorial
Scripting Tutorial
BBEdit Scripting
Directory

Mailing Lists
Sample Scripts
Verb Set
Frontier Site Outline

Search
Guestbook
BBS

User's Guide
1 2 3 4 5 6
7 8 9 10 11

Apple File Edit
Main Open Suites
Web Window

Menubar Outline
Script Table WP

frondoc picture

How Scoping Works

A lesson in variable name scoping from a longtime Frontier user.

By Scott Lawton, http://www.tiac.net/prefab/

Although I've been using Frontier for quite awhile, I just got bit by something that's likely to happen to others. It took me a long time to track down, so I thought I'd share the lesson to perhaps save someone else the trouble.

In Frontier's UserTalk and any other block-structured language, all variables have a "scope", i.e. a context in which they are valid. Although UserTalk doesn't require that you declare variables (using "local"), experienced scripters highly recommend it.

   local  « declare x as a variable
      x

Traditional computer languages such as Pascal and C require that variables be declared with a certain datatype, e.g. string, number or whatever. Frontier lets you change the datatype on the fly, just by assigning a new value. (A very handy feature!) You can also assign an initial value in the "local" block, and define many variables in a single block. (In fact, you don't have to use the block structure for local, though I prefer it.)

   local  « declare x & y and give them initial values
      x = 1
      y = "hello"

In general, the scope of a variable is the "block" (level of outline) in which the "local" definition appears (or the first use, if you don't declare it), plus all deeper levels.

A made-up example:

   local
      x = 10
   msg (x)
   msg (y)  «error: y hasn't been declared or defined
   if x > 5
      local
         y = 5
      msg (x)  «x is still valid here
      msg (y)  «y's "scope" is this "if" block

Note that you can use the same name for two different values, if you declare the variable again at a lower level. However, this approach can lead to confusion and is not recommended.

   local
      x = 10
   msg(x)     « 10
   if x > 5
      local
         x = "hello"  « a new variable that's also called "x"
         y = 5
      msg(x)  « hello ... NOT 10
      msg(y)  « y's "scope" is this "if" block
   msg(x)     « 10 ... NOT hello (note the outline level)

Again, it's confusing to have the letter x stand for two different variables ith different scope, so it's generally not a good idea. Why make scripting harder than it has to be?

By the way, one of the coolest things about Frontier is that the block structure is tangible since it's reflected in the outline (i.e. the indentation).


Of course, the Object Database is central to Frontier. But, be careful. Some locations are "global" to all of your scripts. If you accidently use a name that happens to correspond to a global variable, and you didn't declare yours as a local, you could change the value of something that really shouldn't be changed.

Let's say you liked to use short variable names. Maybe you needed a flag to tell you whether or not you created an outline.

   cr = false  «assume it hasn't been created

Wait! If you run this script, you've just reassigned system.verbs.constants.cr from "\\r" (carriage return) to false. That's likely to break lots of scripts, and maybe confuse you when you eventually discover the error and have long since forgotten about your abbreviated "create" flag.

Two lessons:

1. declare variables

   local  «this is fine, it won't overwrite the global
      cr = false  «assume it hasn't been created

2. use longer variable names, e.g. created or createdFlag


Sometimes you want to use variables from the object database that aren't in a global location, e.g. FileMaker.id. In the old days (Frontier 1), you always had to type out the full Object DB path. Fr. 2 (I think) added the "with" keyword.

The following two are equivalent. msg (FileMaker.id)

with FileMaker msg (id)


Here's where it gets tricky. What if you have some globals, some locals, and some variables in a table referenced by "with"? Which wins? Well, I lost lots of time by not paying attention to this question.

   local
      ID
   with myApplication
      ID = create(s)
      doSomething(ID)
      dispose(ID)

Do you spot the mistake? I remembered to declare the local, but I declared it the wrong place, i.e. one level too high. Frontier (apparently) looks first in locals declared inside the "with" block, that at the tables referenced by "with", then walks up the hierarchy for other locals, then (I think) the globals.

So, although I didn't realize it, my local "ID" was left alone, and I essentially ran:

   myApplication.ID = create(s)  « OOPS, not what I meant

Since my "glue" scripts depended on myApplication.id being the correct string4 creator code (not the values passed back by create), the rest of my script broke.

Here's a sample glue script:

   on doSomething(someValue)
      with myApplication
         return appleEvent (id, 'xxxx', 'yyyy', '----', someValue)

So, Frontier was trying to send the AppleEvent to a sort of random place since the ID was wrong.

Happy scripting; I hope my comments are helpful.


© Copyright 1996-97 UserLand Software. This page was last built on 5/7/97; 1:43:29 PM. It was originally posted on 9/2/96; 3:35:29 PM. Internet service provided by Conxion.