Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Scripting for Pnuts


Jan00: Scripting for Pnuts

John is a member of the computer science faculty at Sam Houston State University in Huntsville, Texas. He can be contacted at [email protected].


Scripting has been traditionally associated with "quick-and-dirty" programming. With their increased acceptance, however, scripting languages are now the glue for component integration. These languages tend to follow the philosophy espoused by Larry Wall ("A Conversation with Larry Wall," DDJ, February 1998) that a language should not attempt to force good programming practice, but rather that "...a language...ought to be an amoral artistic medium."

One newcomer on the scripting block is Pnuts -- a language, API, and interpreter that provides a thin procedural wrapper with an interactive interface for manipulating Java objects. Written by Toyokazu Tomatsu of Japan.sun.com, Pnuts was originally developed to simplify testing Java code. Continuing development appears to be a personally funded effort by Tomatsu. Pnuts is freely available from http://www.etale.com/pnuts/ and DDJ (see "Resource Center," page 5).

Rather than being a multipurpose adhesive, Pnuts comes closer to being a Java class superglue. Creating instances of Java objects and choreographing them with Pnuts is almost as easy as supergluing your thumb to your forefinger, while nonJava components act as if they have a teflon coating. In other words, Pnuts is simple to use, but is designed specifically for use with Java and works with nothing else.

Pnuts uses the Java reflection API extensively and relies on the Java components being scripted to provide most of the functionality. What functionality Pnuts adds is primarily related to supporting marshalling. This ties Pnuts tightly to Java, but makes component reusability easily realizable and helps keep the language small.

Almost anything written in Java can be scripted with Pnuts. This does not, however, mean that it should be. As David Flanagan points out when discussing "Invoking a Named Method" in his Java in a Nutshell (O'Reilly & Associates, 1997), "...indirect method invocation through the Reflection API will always be much faster than the response time required by the limits of human perception. Invoking a method by name is not an appropriate technique, however, when repetitive, synchronous calls are required." Tracking the movement of a "mouse on steroids" is not Pnuts' strong suite. Pnuts is intended as a complement to and not a replacement for Java.

As thin as it is, Pnuts is probably not as thin as IBM's Bean Markup Language (BML), which is a wiring language. All functionality in BML comes from the Java code. BML is described by its authors as an abstract model for describing the structure of component-based applications (that is, applications that consist of a set of beans wired together to implement the functions of the applications). BML is an application of XML, and statements written in BML are formatted as XML documents. A BML document is first parsed using an XML parser. It can then be either played, using an interpreter that (like Pnuts) relies on the Reflection API to instantiate objects, set properties, initialize fields, link event triggers with handlers, and so on. Or, it can be compiled into Reflection-free Java code. BML scripts are more natural to generate than Java code when processes such as XSL in an XML environment are used to generate the wiring. The entire process of dynamically generating a BML document on demand, validating it, and playing it is intended to be a process that can be automated, whereas Pnuts is people friendly and better suited for ad hoc applications or an interactive environment.

For example, suppose you had a button and a beeper bean with a beep method. It would be straightforward to wire the beeper's beep method into the button's actionListener list using BML so that the beeper beeps when the button is pressed. However, if the application requires that the number of beeps from the beeper be determined by the color of the button when it is pressed, then either the components will have to be modified or BML will need one or more Wiring Helper objects -- such as a selector and an iterator -- to implement the logic. This is not necessary if a Pnuts script is used to tie the button to the beeper bean because the logic can be implemented using the basic control-flow statements that are included in the Pnuts language.

Pnuts is expression based and the Pnuts interpreter is essentially an expression evaluator. If you prefer your peanut butter "natural," you can process scripts in the command-line batch mode or enter them interactively. If you prefer "smooth" (actually, I'd say it is closer to "crunchy"), then you can use Pnutool. Pnutool is an included script that implements a primitive text-window interface to the interpreter. It provides only basic open, load, edit, save, and evaluate script capabilities and there is no drop-and-drag. The most interesting and useful feature Pnutool provides is the capability to selectively execute script fragments. You can step through a script alternately composing, executing, editing, and reexecuting statements. Or, you can select anything from an expression factor to the entire script and execute it. When evaluating expression components, don't expect the results to replace the evaluated parts as they do in the Excel Spreadsheet Formula bar. That would be nice and if it's what you really want, you can rewrite the script to your taste. As it is, the output typically goes to a separate Pnutool output window and most exceptions display in a popup window.

The entities in a Pnuts script are variables and objects (primitive data types are automatically wrapped with object wrappers). Variables are untyped and can bind to any object. While this is typical of scripting languages, it leaves the programmer responsible for ensuring variables are bound to the "right" type when the variable is passed as a parameter. For instance, the script statement: sc=Applet::newAudioClip(url) to create a sound clip from a sound file expects url to be bound to a URL object when the statement is evaluated. If it isn't, you will likely get a message that there is no such method and script evaluation will be terminated. In single-step mode, the offending statement is obvious, but in a long script snippet both the message and the error location can be obscure. It is not intuitively obvious from a message like: evaluate(java .awt.event.ActionEvent[ACTION_PERFORMED,cmd=null] on menuitem4) that there is a comma missing in line 27 of a script that has nothing to do with menus.

The Pnuts core contains arithmetic, Boolean, comparison, string, and array operations as well as the standard if, else, Switch/case, For, While, continue, break, and a simple Foreach construct. Semicolons, types, and variable declarations are not available in Pnuts, but braces, brackets, and parentheses seem destined to haunt us forever. There is no color-coded, context-sensitive editor to help you find mismatches. Fortunately, scripts tend to be short, and mismatches, while a nuisance, can eventually be sorted out.

Functions are ubiquitous structures in Pnuts. Ten primitive operations are defined as built-in functions that cannot be redefined by you, and a host of others are used to establish the Pnut environment and can be redefined as desired.

A couple of the primitive functions are particularly interesting:

  • import, which in Java serves the purpose of making classes available under their abbreviated names, becomes import() in Pnuts and can be overloaded. Thus, for a slight increase in complexity there is a significant gain in functionality. For instance, import() lets you get the list of imported classes and packages; import(''my.app.*'') registers a class/package name; and import(null) unregisters all previously registered class names and flexibility.
  • Catch-and-throw. Pnuts handles exceptions that are thrown but not caught by terminating the script and displaying the exception object's message. User-defined exceptions are supported as are user-defined exception handlers. There is no try statement in Pnuts and the catch() is placed at the beginning of a function block rather than after a try block.

Example 1 illustrates catching and throwing an exception. Look first at the Pnuts statement to create a new FileNotFoundException object: FileNotFoundException(f.getPath()+'' not found''). Pnuts doesn't have a new operator. Class instances are created through function calls that mirror the Java constructor syntax and return a new instance of the object. The object can be further manipulated or bound to a variable.

To catch exceptions, the statement that can throw it is placed in a function block that begins with a catch function set to catch and handle that exception. In Example 1, a file object is queried to see if the associated file exists. If it doesn't, a FileNotFoundException is thrown. The catch statement catches it and passes it to the exception handler. Unless the handler rethrows the exception, or explicitly terminates the script, the script exits the function and continues. The notation "(...)()" is used to in-line the exception processing.

Pnuts supports callback by a sort of universal listener dispatcher. AWT event objects passed by the dispatcher to the listeners seem to act like clones in that you can set the consume property so the event responds True to an isConsumed() query, but the event still appears to get passed on to other listeners.

Event listeners are added using a bind function and using it can be as simple as Example 2, so that pressing a button results in the printing of a message.

bind is not one of the 10 listed primitives, but its importance is such that while the manner in which it is implemented may be changed, it seems unlikely that it will not continue to be supported. Methods in the Java event.listener class are predefined actions for the bind function. User-written modules that have event-listener methods can also be registered and used.

A Pnuts Demo

Figure 1 shows an application that uses Pnuts scripting to extend the functionality of an existing component bean. The scripts are shown in Listings One through Four and can be downloaded along with the bean and images. To run them you will also need the Java 1.2 run time and the Pnuts distribution (http://www.etale .com/pnuts/). The files that implement this application (available electronically) should be placed in the apps/juggler folder under the Pnut root.

Three of the scripts implement simple independent applications. Packages are used so that each application is implemented in its own namespace. This makes testing the components easier and enhances reusability. The fourth script combines them into one application.

Listing Two is essentially a script implementation of the Java look-and-feel chooser in the Swing demos. Three radio buttons allow selecting Metal, Motif, or Windows look-and-feel. Listing Three instantiates the juggler bean and adds it to a panel along with two buttons for starting and stopping the juggling. Listing Four is similar to Listing Three, except that an inverted slider is used to control the animation rate of the juggling. Finally, Listing One loads the three other scripts, adds the plaf radio button group to a tear off toolbar, then places the toolbar and two juggler pages in a frame and sets it all visible. It should appear similar to what is shown in Figure 1 when it is loaded and run. In Figure 1, however, the look-and-feel has been changed from Windows to Metal and the toolbar has been redocked at the left side rather than at the top where it appears when the script is started.

Conclusion

So, what good is Pnuts if you don't have a Java scripting application? Assuming that you are involved in some way with learning or using Java, you can start Pnuts and leave it running. Then, anytime you need to see how a Java class/component/ method really works, a couple of mouse clicks will bring it up with the jvm initialized and ready to go. You can quickly check ideas and get some insight into how things work that your IDE keeps hidden from you. You don't have to be a Java programmer to use Pnuts, but you can learn a lot about Java from using it. You may even rediscover that programming can be fun! Try it, I think you'll like it.

DDJ

Listing One

//JugglerBook.pnut. Implements a tear-off toolbar with radio-button selection 
//  of plaf, creates two instances of the Duke juggler animation--one 
//  controlled by start/stop buttons and the other with a slider.
// Usage:  Assumes all scripts/class files are unzipped into apps/juggler.
//          Load script and run.  Doesn't catch window closing events.
//  John H. McCoy
//  [email protected]
//  Sam Houston State University

import("javax.swing.*")
import("javax.swing.border.*")
import("java.awt.*")
import("javax.swing.event.*")
import("pnuts.awt.*")
load("apps/juggler/LNF")
load("apps/juggler/StartStopJuggler")
load("apps/juggler/SliderJuggler")
UIManager::setLookAndFeel(UIManager::getSystemLookAndFeelClassName())
jtb=JToolBar()
jtb.addSeparator()
jtb.setFloatable(true)
page=JPanel()
page.setBorder(BevelBorder(BevelBorder::RAISED))
layout(page,[PnutsLayout,"cols=1,halign=fill,valign=fill,,expand=xy",
                                                        SSJ::page,SJ::page])
jf=JFrame("Goobers Demo")
LNF::addLNFChooser(jtb, jf.getContentPane())
jf.getContentPane().add(jtb,BorderLayout::NORTH)
jf.getContentPane().add(page,BorderLayout::CENTER)
jf.getContentPane().setBorder(BevelBorder(BevelBorder::RAISED))
jf.setSize(500,360)
jf.setVisible(true)

Back to Article

Listing Two

//  LNF.pnut. Essentially a script implementation of the look-and-feel
//    from the Swing demos. Creates a radio button group and adds
//    it to a specified buttonBox container.
//  Usage:  load("path/LNF")
//          LNF::addLNFChooser(buttonBox, UIRoot)
//    The button group selection is initialized to lnf in effect
//    Selecting a button updates the UItree at UIRoot with the
//    corresponding lnf.
//  John H. McCoy
//  [email protected]
//  Sam Houston State University

import("java.awt.*")
import("java.awt.event.*")
import("javax.swing.*")
import("Pnuts.lang.*")
package ("LNF")
function myRadioListener(e){
  lnfName = e.getActionCommand()
  UIManager::setLookAndFeel(lnfName)
  SwingUtilities::updateComponentTreeUI(UIRoot)}
function addLNFChooser(buttonBox, UIRoot){
   LNF::UIRoot=UIRoot
   metal= "Metal"
   metalClassName = "javax.swing.plaf.metal.MetalLookAndFeel"
   motif = "Motif"
   motifClassName = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"
   windows = "Windows"
   windowsClassName = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
   // Create the buttons.
   metalButton = JRadioButton(metal)
   metalButton.setActionCommand(metalClassName)
   motifButton = JRadioButton(motif)
   motifButton.setActionCommand(motifClassName)
   windowsButton = JRadioButton(windows)
   windowsButton.setActionCommand(windowsClassName)
   // Group the radio buttons.
   group = ButtonGroup()
   group.add(metalButton)
   group.add(motifButton)
   group.add(windowsButton)
   // Register a listener with the buttons.
   bind(metalButton,"actionPerformed",myRadioListener)
   bind(motifButton,"actionPerformed",myRadioListener)
   bind(windowsButton,"actionPerformed",myRadioListener)
   lnfName = UIManager::getLookAndFeel().getClass().getName()
   if (lnfName.indexOf(metal) >= 0) {
      metalButton.setSelected(true)
   } else if (lnfName.indexOf(windows) >= 0) {
      windowsButton.setSelected(true)
   } else if (lnfName.indexOf(motif) >= 0) {
      motifButton.setSelected(true)
   } else {
      System.err.println("Attempting to use an unknown L&F: " + lnfName);
   }
   //Add the button to the specified container
   buttonBox.add(metalButton)
   buttonBox.add(motifButton)
   buttonBox.add(windowsButton)
}

Back to Article

Listing Three

//  StartStopJuggler.pnut -- A script that creates a lightweight panel and 
//    displays the Duke Juggler applet adding Start/Stop buttons to control 
//    the juggling 
//  John H. McCoy 
//  [email protected] 
//  Sam Houston State University 
//  Assumes the Juggler applet is in Java package apps.juggler and scripts 
//  are in apps/juggler folder. 
//  Usage:  load("apps/juggler/SSJ") 
//          <component name>.add(SSJ::page) 
// 
import("java.awt.*") 
import("javax.swing.*") 
import("javax.swing.border.*") 
import("pnuts.awt.*") 
package("SSJ") 
page=JPanel() 
page.setBorder(BevelBorder(BevelBorder::RAISED)) 
juggler=class apps.juggler.Juggler() 
juggler.setSize(juggler.getPreferredSize()) 
start=JButton("Start Juggler") 
start.setBackground(Color::green) 
stop=JButton("Stop Juggler") 
stop.setBackground(Color::red) 
layout(page,[FlowLayout,[],start,juggler,stop]) 
bind(start,"actionPerformed",function(e) juggler.start()) 
bind(stop,"actionPerformed", function(e) juggler.stop()) 

package("") 

//  Remove "//" in last line to test as a standalone frame 
(function testSSJ(){ 
   jf=JFrame("Test Start Stop Juggler") 
   jf.getContentPane().add(SSJ::page) 
   jf.setSize (400,400) 
   jf.setVisible(true) 
})//() 

Back to Article

Listing Four

//  StartStopJuggler.pnut. Script that creates a lightweight panel and 
//    displays Duke Juggler applet adding start/stop buttons to control 
//    the juggling
//  John H. McCoy
//  [email protected]
//  Sam Houston State University
//  Assumes the Juggler applet is in Java package apps.juggler and scripts
//  are in apps/juggler folder.
//  Usage:  load("apps/juggler/SSJ")
//          <component name>.add(SSJ::page)

import("java.awt.*")
import("javax.swing.*")
import("javax.swing.border.*")
import("pnuts.awt.*")
package("SSJ")
page=JPanel()
page.setBorder(BevelBorder(BevelBorder::RAISED))
juggler=class apps.juggler.Juggler()
juggler.setSize(juggler.getPreferredSize())
start=JButton("Start Juggler")
start.setBackground(Color::green)
stop=JButton("Stop Juggler")
stop.setBackground(Color::red)
layout(page,[FlowLayout,[],start,juggler,stop])
bind(start,"actionPerformed",function(e) juggler.start())
bind(stop,"actionPerformed", function(e) juggler.stop())

package("")

//  Remove "//" in last line to test as a standalone frame
(function testSSJ(){
   jf=JFrame("Test Start Stop Juggler")
   jf.getContentPane().add(SSJ::page)
   jf.setSize (400,400)
   jf.setVisible(true)
})//()

Back to Article

Listing Five

//  SliderJugger.pnut. Script that creates a lightweight panel and then 
//    instantiates and displays Duke Juggler applet. Slider is used to control
//    the juggling rate
//  John H. McCoy
//  [email protected]
//  Sam Houston State University
//  Assumes the Juggler applet is in Java package apps.juggler and scripts
//  are in apps/juggler folder.
//  Usage:  load("apps/juggler/SJ")
//          <component name>.add(SJ::page)

import("javax.swing.*")
import("javax.swing.event.*")
import("javax.swing.border.*")

package("SJ")
page=JPanel()
page.setBorder(BevelBorder(BevelBorder::RAISED))
jsl=JSlider(JSlider::HORIZONTAL,10,100,50)
jsl.setInverted(true)
tb=TitledBorder("  slower        SPEED        faster  ")
tb.setTitleJustification(TitledBorder::CENTER)
tb.setTitlePosition(TitledBorder::BOTTOM)
jsl.setBorder(tb)
juggler=class apps.juggler.Juggler()
juggler.setSize(juggler.getPreferredSize())
juggler.setAnimationRate(jsl.getValue())
registerEventListener(ChangeListener)
bind(jsl,"stateChanged",function(e)
      juggler.setAnimationRate(jsl.getValue()))
page.add(juggler)
page.add(jsl)
juggler.start()

package("")

//  Remove "//" in last line to test as a standalone frame
(function testSJ(){
   jf=JFrame("Test Slider Juggler")
   jf.getContentPane().add(SJ::page)
   jf.setSize (400,200)
   jf.setVisible(true)
})//()

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.