Jim Lawless' Blog


TAP : A Command Processor Library

Originally published on: Sun, 11 Oct 2009 01:33:22 +0000

Many programs contain embedded scripting languages that allow the user to write their own scripts to customize the program's behavior. Often, if the user does not write scripts themselves, they find themselves using scripts written by others.

I would like to embed a scripting language in some of my programs, but I haven't found anything that meets my needs. Most options available are larger than I'd like. I really want a simple command-processor shell that I can extend with custom commands specialized for a given piece of software.

I wrote a processor for a language that I call TAP. TAP is a recursive acronym that stands for "TAP Ain't Pretty." The name is derived from the spartan syntax and lack of expressiveness in the language.

In this post, I will describe the TAP syntax, the six core TAP commands, and the mechanisms used to extend TAP with new commands.

TAP is line-oriented. Its syntax is similar to that of a Windows batch file. The '#' character at the beginning of a line denotes a comment. The ':' character at the beginnng of a line denotes a label ( a target for the GOTO or CALL commands. ) Blank lines are ignored.

The general syntax for invoking a TAP command is simply the command name followed by any number of parameters separated by spaces.

If a parameter begins with a '$' character, it is treated as a variable. In this case, the variable's value is passed to the command in place of this parameter. If the variable has not been set to a specific value, an empty string is passed.

If a parameter begins with double quotation-marks, the string bounded by a closing double-quotation mark ( or the end of the line ) is passed to the command. A single quotation-mark may begin a string, but it must be terminated by another single quotation-mark.

All other words appearing on a line separated by spaces are passed as strings to the command.

TAP commands and variables are not case-sensitive.

TAP has six core commands:

Note that these commands don't really do much other than provide for control-flow and the ability to set variables. MSGBOX was added so that all programs using the core implementation would have a common mechanism for displaying a message.

The SET Command

The SET command sets a variable's value. You may specify multiple parameters to the right of the variable name. The current implementation of TAP allows up to 12 parameters to follow any command.

All of the paramters are then concatenated into a single value. The single value is then placed in the specified variable.

The GOTO Command

The GOTO command transfers control to the line containing a specified label. If the label is not found, an error message is displayed and further TAP processing ceases.

Note that a variable can be used in place of a literal label.

The CALL Command

The CALL command temporarily transfers control to the subroutine at the line containing a specified label. If the label is not found, an error message is displayed and further TAP processing ceases.

Note that a variable can also be used with CALL in place of a literal label.

The RETURN Command

When a RETURN command is encountered during a CALLed subroutine, processing continues at the line following the line originating the invocation of CALL.

The IF Command

The IF command performs a numeric comparison of parm1 and parm2 using the specified operator. Valid operators are:

If the numeric expression yields true, either a GOTO, a CALL, or a RETURN may be invoked.

The MSGBOX Command

The MSGBOX command is a thin wrapper for the Windows MessageBox API function. You may specify a message, a title, and a code that determines what kinds of buttons show up. Please consult other Windows API sources for the appropriate codes for the buttons. By default, a zero is passed ( which shows only the "OK" button. )

The message and title paramaters default to empty strings if omitted.

The TAP API library allows a host C program to augment TAP with new commands, load TAP scripts, execute TAP scripts, call labeled subroutines, and set/get variables.

The TAP API functions are as follows (excerpted from tap_core.h ):


   // Initialize the TAP runtime.
int tap_init();

   // Set a variable
void tap_setvar(char *,char *);

   // Get a variable's value
char *tap_getvar(char *);

   // Load a TAP script from a file
int tap_load_file(char *);
   
   // Load a TAP script from a string
void tap_load_string(char *);

   // Execute the loaded script at the
   // Specified line number. Use 0
   // to run from the beginning.
void tap_run(int);

   // Register a custom (new) command.
int tap_register_cmd(char *name,CMDPROC cmdproc);

   // Find the line-number of a label. The return
   // value ( if not negative ) can then be used
   // by tap_run().
int tap_find_label(char *label);

We will now create our own interpreter host program for the TAP language called tap_test. We'll add two commmands to TAP:

tap_test.c


// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#include <stdio.h>
#include <stdlib.h>
#include "tap_core.h"

  // New commands.

  // Increment a value and leave the
  // result in $retval.
static void do_inc(int argc,char **argv);

   // Print all of the specified arguments
   // on the console.
static void do_println(int argc,char **argv);

int main(int argc,char **argv) {
   int i;
   if(argc<2) {
      fprintf(stderr,"Syntax:\n\ttap_test script_filename\n");
      return 1;
   }
   i=tap_init();
   if(!i) {
      printf("Init error!");
      return 1;
   }

   tap_register_cmd("inc",do_inc);
   tap_register_cmd("println",do_println);

   i=tap_load_file(argv[1]);
   if(!i) {
      printf("load error");
      return 1;
   }
   tap_run(0);
   return 0;
}


static void do_inc(int argc,char **argv) {
   int x;
   char wrk[14];
   x=atoi(argv[0])+1;
   sprintf(wrk,"%d",x);
   tap_setvar("$retval",wrk);
}

static void do_println(int argc,char **argv) {
   int i;
   for(i=0;i<argc;i++) {
      printf("%s",argv[i]);
   }
   printf("\n");
}

Note that command extension functions are not unlike the common C main function. Each receives a count of arguments passed and an array of those arguments. All arguments appear as evaluated text by the time the function is invoked. ( All variables are expanded, all appropriate quotation-marks are removed, ...etc. )

Here are two test scripts:

test1.txt


# sample TAP script
  set $a Hello
  set $b ", there"

  set $c $a $b
  msgbox $c "Here's a message"

# by calling msgbox with a 1 at the end
# ok/cancel will be displayed
# if the user clicks "ok", a 1 will be returned.
# if they click cancel, a 2 will be returned
  msgbox "Would you like to continue?" "" 1

# if they clicked "ok", lets' just call the
# subroutine "continue"
  if $retval eq 1 call continue

# otherwise, get out
  goto end
  
:continue
  msgbox "Continuing..."
  return


:end
# all done

test2.txt


# sample TAP script
# Let's try the new INC
# and PRINTLN commands.


# Display numbers from 1 to 10

  set $a 1
   
:loop
  println "Iteration #" $a
  inc $a
  set $a $retval
  if $a le 10 goto loop

# all done.
  println ""
  println "Done."

To run the first script, from a command-prompt enter: tap_test test1.txt

You should first see a message box that says "Hello, there" with a title of "Here's a message". The first several lines of the script test1.txt concatenated the phrase "Hello, there" together into variable $c. Then, the MSGBOX command was invoked using $c as the message and "Here's a message" as the title.

Then, you should see a message box that says "Would you like to continue?" If you click "OK" a value of 1 will be left in the variable $retval. If you click "Cancel" a value of 2 will be left in $retval. We were presented with the ok/cancel options because we passed a 1 in as the code for the button parameter.

We then check $retval to see if it holds the value 1 indicating that "OK" had been pressed. If you click "OK", the continue subroutine will be invoked. That subroutine simply displays a message box that says "Continuing..."

The script then terminates.

To run the second script, from a command-prompt enter: tap_test test2.txt

This script leverages the new commands in our host. The console output is: Iteration #1 Iteration #2 Iteration #3 Iteration #4 Iteration #5 Iteration #6 Iteration #7 Iteration #8 Iteration #9 Iteration #10

Done.

The first executable line of code sets the variable $a to 1. The next executable line of code uses the new PRINTLN command to print "Iteration #" and the value of $a.

The line INC $a increments the value of $a leaving the result in the variable $retval Note that the value of $a itself doesn't change until we issue the command SET $a $retval.

It may appear as though INC is incrementing $a, but all variables are expanded to their values before they are passed to the command. So, the INC function only sees the value of $a. The only exception to this is the special case for the SET command. The argument immediately following the SET verb is known to be a variable. You might take a look at tap_core.c to see the special handler for this case.

An IF is then used to determine if $a is less than or equal to 10 by using the lt operator. If the expression is true, control is transferred to the line beginning with the label :loop via the GOTO command.

Otherwise, the script flows to the two ending PRINTLN statements.

In forthcoming articles, I'll provide a few more augmentations to TAP and will embed it in more practical code.

The source, executable sample file, and sample scripts for the TAP core can be downloaded in a single archive at: http://www.mailsend-online.com/wp/tapcore.zip

Unless otherwise noted, all code and text entries are Copyright ©2009 by James K. Lawless



Views expressed in this blog are those of the author and do not necessary reflect those of the author's employer. Views expressed in the comments are those of the responding individual.

stumbleupon Save to StumbleUpon
digg Digg it
reddit Save to Reddit
facebook Share on Facebook
twitter Share on Twitter
aolfav More bookmarks


Previous post: Book Review : Using Google App Engine
Next post:Thwarting HTTP Referer Trackbacks


About Jim ...


Click **here**
to try out MailWrench;
a command-line SMTP /
SMTPS (Google Gmail)
mailer for Windows.


Follow me on Twitter

http://twitter.com/lawlessGuy


Recent Posts

A JavaScript REPL for Android Devices

MailSend is Free

My Blog Engine

The October 10th Bug

A Review of Kevin Mitnick's Book Ghost in the Wires

Spellbound by Web Programming

Backlinks to my Blog Posts

Play MP3 Files with Python on Windows


Random Posts

Pi Day Meets the HTML5 Canvas

Flirting with Forth

A DSL in JavaScript

Obfuscated Ruby

COM Scripting in C by way of JavaScript

Backlinks to my Blog Posts

Book Review : Using Google App Engine

An SMTP Server Simulator in Perl

Obfuscated C

A Quine in C


Full List of Posts

http://www.mailsend-online.com/bloglist.htm


Recent Posts from my Other Blog

Remembering Dr. San Guinary

Why Some Web Sites will go Dark on Jan 18th

SNL Superhero Skit

More Ruby Games

My Ruby Game Challenge Entry

Steal this Bookmarklet

Nerd Toys

Learn New Jargon, You Must

Spot the Wiebe

Tech Magazine Glory Days

Book Review : Paull Allen - Idea Man

A 90's Experiment in Online Systems - The U.S. West CommunityLink Service