Articles Archive
Articles Search
Director Wiki
 

Using the value() function

July 11, 2000
by Zac Belado

Director is an interesting environment to work in because of the unexpected uses you can put some of the commands to. One of the more interesting, and flexible, functions in Lingo is value(). This function is used to evaluate strings, but it can do far more than this simple explanation might imply.

The Lingo Dictionary defines value() as:

Function; returns the value of a string. The string can be any expression that Lingo can understand. When value() is called, Lingo parses through the stringExpression provided and returns its logical value.

The key here is that even though the definition says that value() returns the value of a string, the important thing to notice is that this string can represent any expression that Lingo can understand. This includes objects, variables, lists and even other functions.

Numbers

The simplest use of value() is to evaluate a string that represents a number, or an equation that results in a number.

put value("5")
-- 5
put value("5+1")
-- 6

This is of limited value in and of itself, but it does prove useful when you have to convert the text in a Text or Field member on stage into a numerical value.

put member("aNumber").text
-- "3.1415926"
put value(member("aNumber").text)
-- 3.1416

Notice that the number returned is limited by the current setting of the floatPrecision property.

Of course, you could do the same thing with the integer() and float() functions as well.

put float(sprite(1).member.text)
-- 3.1416
put integer(sprite(1).member.text)
-- <Void>

As you can see, this is somewhat problematic. The integer() function will not return a number if the string represents a float value, but float() will always return a float value, even if the text is an integer value. So if the string in the Field member on stage were "4", the two functions would return different numbers.

put float(sprite(1).member.text)
-- 4.0000
put integer(sprite(1).member.text)
-- 4

While this is a minor difference (since 4.0000 still equals 4), this might not be an advisable way to evaluate numbers. This is primarily because the floatPrecision property limits the number of decimal places to which a number is displayed, so there are instances when looks might be deceiving.

thisString = "4.000001"
thisVar = float(thisString)
thisOtherVar = float("4")
put thisOtherVar
-- 4.0000
put thisVar
-- 4.0000
put thisVar = thisOtherVar
-- 0

This is a general limitation of Director, but if you use value() to evaluate an integer, you get an integer and not a float value. If you use float() you will always get a float value, which makes the possibility of the above-mentioned problem more likely.

Using value() instead of integer() or float() avoids all these problems. As well, integer() and float() will not work unless the string is already a number. This means that these two functions will not work on strings that represent equations, so using value() is the only way to evaluate strings of this type.

put float("1 + 1")
-- "1 + 1"
put integer ("1 + 1")
-- 1
put value("1 + 1")
-- 2

Lists

Perhaps the most widely used application of the value() function is to turn text into lists.

thisString="[1,2,3,4,5]"
put value(thisString)
-- [1, 2, 3, 4, 5]

This is extremely useful when converting data in a text file or communicating with a server-side CGI. Instead of saving out individual pieces of data (or writing them using a CGI) you can save them as a list or a property list and then use value() to convert the string into a list.

Check out this example. You can enter a series of comma-separated elements and then click on the "Evaluate" button to convert them to a list.

Sample movies are available for download in Mac or PC format.

The code to do this just takes the text, adds opening and closing braces, and then uses the value() function to try to evaluate the new string.

thisString = member("listValues").text
member("results").text = ""

thisString = "[" & thisString & "]"
thisList = value(thisString)

if NOT voidP(thisList) AND thisList <> [] then
  returnString = string(thisList) & RETURN
  returnString = returnString & "This list has" && thisList.count && "entries" & RETURN
  returnString = returnString & "The last entry is" && thisList[thisList.count]
  member("results").text = returnString
else
  alert "This string doesn't appear to be a valid list"
end if

Property lists can be evaluated in the same fashion as linear lists.

thisString = "[#foo:2, #bar:12]"
thisList = value(thisString)
put thislist
-- [#foo: 2, #bar: 12]
put thisList.foo
-- 2

There are two things you need to remember when using this technique. The first is that any line breaks or carriage returns in a string will terminate the evaluation of a string. This is not a major concern with small strings or single character strings, but it is a problem that often crops up when getting data from a CGI or from a Text member.

Examine these two images. The Text member on the left has a carriage return in it, and the field on the right doesn't.

Can you tell the difference? Director can, and if you try to use the value() function with the text in the Text member on the left you won't get a list back. The simplest thing to do in this instance is to check to make sure that the only return in the text is at the end.

on textHasCR thisText

  CRLoc = offset(RETURN, thisText)
  limit = thisText.chars.count

  return CRloc < limit

end

The function checks to get the offset of the first RETURN character in the string, and then does a bit of Boolean trickery to return TRUE if the RETURN character was before the final character of the string, or FALSE if it is the last character.

Any text chunk that has a RETURN character before the end of the text will not be usable with the value() function, so you need to strip it out. Fortunately, this is also easy to do.

on stripCR thisText

  limit = thisText.chars.count
  done = FALSE

  repeat while NOT done
    
    CRLoc = offset(RETURN, thisText)
    if CRLoc = 0 OR CRloc = limit then
      done = TRUE
    else
      delete char CRLoc of thisText
      limit = limit - 1
    end if
    
  end repeat

  return thisText

end

This function goes into a repeat loop that is terminated when the offset function returns no instances of a carriage return, or when the instance it finds is the last character of the string. For each non-terminal (i.e., not the last character) RETURN it finds, it deletes that character and then decrements the limit variable so that the function always has an accurate measure of what the last character is in the string.

The second, and more vexing, problem to remember when converting strings to lists is that there is an upper limit to the number of list elements that the value() function is able to process. You may have seen posts on the many Director mailing lists about there being a text size limit to the amount of data you can convert to a list. This is not the case. The problem resides in the fact that the value() function seems to have a hard-coded internal limit to the number of elements it can keep track of.

That exact number is 2 15 - 1, or 32767. So, if the string you want to evaluate has 32768 elements in it, then value() will return incorrect results. The function will still return something that Director recognizes as a list, but if you use the count() function on this "list" it will return a negative number of elements. In fact, it will return a count of n - 2 16, where n is the number of elements in the original list.

You can test this out yourself. Create a list with a very large number of elements in it, convert that list to a string, and store that new string in a Field or Text member. Then create a new list by using the value() function on the Text or Field member's text data.

on testList limit

  if voidP(limit) then limit = 40000

  tempList = []
  repeat with index = 1 to limit
    append tempList,"*"
  end repeat

  member("list text").text = string(tempList)
  put "Original list had" && count(tempList) && "elements"

  newList = value(member("list text").text)
  put "The new returns" && listP(newList) && "using the listP() function"
  put "And the new list has" && newList.count && "elements"

end

Since you still have the original list, you can compare it to the new list you created. Using the count() function on the original list verifies that the problem isn't with an upper limit on the number of elements in a list.

Testing the code using a number over the upper limit shows this hard-coded limit in effect.

testList 32769
-- "Original list had 32769 elements"
-- "The new returns 1 using the listP() function"
-- "And the new list has -32767 elements"

But compare that to a number less than the upper limit of 32767.

testList 32765
testList 32765
-- "Original list had 32765 elements"
-- "The new returns 1 using the listP() function"
-- "And the new list has 32765 elements"

This means that you have to keep the number of elements you are going to evaluate to less than or equal to the limit of 32767. If you need to evaluate more data than this, you need to break the data up into a series of lists.

Complex evaluations

The value() function isn't just limited to evaluating numbers and lists. To demonstrate this, let's take the sample movie we examined earlier and modify it so that it evaluates an arbitrary sprite property.

To use this movie, just enter a valid sprite property in the field and click on the "Evaluate" button.

thisString = member("propName").text
member("results").text = ""

thisString = "sprite(1)." & thisString
thisPropValue= value(thisString)

if NOT voidP(thisPropValue) then
  returnString = thisString && "equals" & RETURN
  returnString = returnString & thisPropValue
  member("results").text = returnString
else
  alert "This string doesn't appear to be a valid sprite property"
end if

In this case, the property has to be a valid property for a Field member (which is what sprite(1) is in the movie), but the value() function will properly evaluate any valid property for that sprite.

You could even substitute a variable for the number. Director would first evaluate the variable, then continue to evaluate the entire expression with the value of the variable substituted for the variable name.

thisString = "sprite(thisSpriteNum)." & thisString

This is possible because the value() function tries to evaluate each element of the string, and does so until it either gets a usable value or determines that it can't get a value for that part of the string. So you could even include a call to a function inside the original string. Let's assume you have a very pointless function that just returns the number 1.

on getSpriteNum
  return 1
end

Then you can rewrite the original string so that instead of saying "1" it has the name of the function instead.

thisString = "sprite(getSpriteNum())." & thisString

If you enter loc as a property in the Field member, then Director will correctly evaluate the string as:

sprite(1).loc

This ability to call functions and handlers is very much like the do command. In fact the only difference is that value() can be used to directly assign the data returned from a function to a variable.

Lets assume you have a very basic handler.

on dummyHandler
  put "You just called dummyHandler"
end

You could then call that handler by using the handler name as a string. In the message window, type:

thisString = "dummyHandler()"
thisCall = value(thisString)
-- "You just called dummyHandler"

Notice that you need to include the closed set of brackets. If you don't, and just define the string as "dummyHandler", then Director won't know that you are referring to a handler, and instead will try to evaluate it as a string or variable, neither of which has a value.

This isn't really all that novel, though, since you can do exactly the same thing with the do command.

thisString = "dummyHandler"
do thisString
-- "You just called dummyHandler"

In fact, it's easier to use do. It doesn't require you to add brackets to the string, and you can use it without having to initiate the call to the handler via a variable assignment as you needed to do with value().

Where value() is far more useful is in calling handlers. Let's assume you have a simple handler that just returns a random number from 1 to 100.

on dummyFunction
  return random(100)
end

If you wanted to use do to store the value returned by this function, you would need to use a function called result(). This function returns the result of whatever was the last handler called. So, if you use do to call dummyFunction , you then use result() to get the random value that it generated.

thisString = "dummyFunction()"
do thisString
thisVar = the result
put thisVar
-- 73

If you use value() to call the function, you can simply assign the returned value directly to a variable instead.

thisString = "dummyFunction()"
thisVar = value(thisString)
put thisVar
-- 47

This is largely a cosmetic difference. Both value() and do can accomplish the same thing, but using value() with function calls saves you a line of code and makes the code somewhat easier to read.

The value() function isn't limited to just these few examples. Since you can embed variables and other evaluations into statements, it's possible to develop very complicated strings that are evaluated. You can also use value() to aid in localisations, which will help to build truly dynamic getPropertyDescriptionList dialogs. All in all, it's a very versatile command that gives you the flexibility to store and interact with Lingo elements in a way that can make some tasks easier to perform.

Zac Belado is a programmer, web developer and rehabilitated ex-designer based in Vancouver, British Columbia. He currently works as an Application Developer for a Vancouver software company. His primary focus is web applications built using ColdFusion. He has been involved in multimedia and web-based development, producing work for clients such as Levi Straus, Motorola and Adobe Systems. As well, he has written for the Macromedia Users Journal and been a featured speaker at the Macromedia Users Convention.

Copyright 1997-2017, Director Online. Article content copyright by respective authors.