added macLib

This commit is contained in:
Marty Kraimer
1996-07-10 14:49:55 +00:00
parent 01402c6bbd
commit 236cf1ddf8
12 changed files with 3349 additions and 0 deletions

View File

@@ -21,6 +21,7 @@ INC += pal.h
INC += fdMgr.h
INC += osiTime.h
INC += osiTimer.h
INC += macLib.h
SRCS.c += ../bucketLib.c
SRCS.c += ../calcPerform.c
@@ -44,6 +45,8 @@ SRCS.c += ../assertUNIX.c
SRCS.c += ../fdMgr.cc
SRCS.c += ../osiTimer.cc
SRCS.c += ../osiTimeOSD.cc
SRCS.c += ../macCore.c
SRCS.c += ../macUtil.c
LIBOBJS += bucketLib.o
LIBOBJS += calcPerform.o
@@ -67,6 +70,8 @@ LIBOBJS += assertUNIX.o
LIBOBJS += fdMgr.o
LIBOBJS += osiTimer.o
LIBOBJS += osiTimeOSD.o
LIBOBJS += macCore.o
LIBOBJS += macUtil.o
LIBNAME = libCom.a

View File

@@ -19,6 +19,8 @@ SRCS.c += ../paldef.c
SRCS.c += errSymTbl.c
SRCS.c += ../errPrintfVX.c
SRCS.c += ../assertVX.c
SRCS.c += ../macCore.c
SRCS.c += ../macUtil.c
LIBOBJS += calcPerform.o
LIBOBJS += cvtFast.o
@@ -37,6 +39,8 @@ LIBOBJS += pal.o
LIBOBJS += paldef.o
LIBOBJS += errPrintfVX.o
LIBOBJS += assertVX.o
LIBOBJS += macCore.o
LIBOBJS += macUtil.o
LIBNAME = libCom

749
src/libCom/macCore.c Normal file
View File

@@ -0,0 +1,749 @@
/* $Id$
*
* Implementation of core macro substitution library (macLib)
*
* Just about all functionality proposed in tech-talk on 19-Feb-96 has
* been implemented with only minor departures. Missing features are:
*
* 1. All the attribute-setting routines (for setting and getting
* special characters etc)
*
* 2. Error message verbosity control (although a debug level is
* supported)
*
* 3. The macro substitution tool (although the supplied macTest
* program is in fact a simple version of this)
*
* The implementation is fairly unsophisticated and linked lists are
* used to store macro values. Typically there will will be only a
* small number of macros and performance won't be a problem. Special
* measures are taken to avoid unnecessary expansion of macros whose
* definitions reference other macros. Whenever a macro is created,
* modified or deleted, a "dirty" flag is set; this causes a full
* expansion of all macros the next time a macro value is read
*
* See the files README (original specification from tech-talk) and
* NOTES (valid input file for the macTest program) for further
* information
*
* Testing has been primarily under SunOS, but the code has been built
* and macTest <NOTES run under vxWorks
*
* William Lupton, W. M. Keck Observatory
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "macLib.h"
/*
* Static function prototypes (these static functions provide an low-level
* library operating on macro entries)
*/
static MAC_ENTRY *first ( MAC_HANDLE *handle );
static MAC_ENTRY *last ( MAC_HANDLE *handle );
static MAC_ENTRY *next ( MAC_ENTRY *entry );
static MAC_ENTRY *previous( MAC_ENTRY *entry );
static MAC_ENTRY *create( MAC_HANDLE *handle, char *name, long special );
static MAC_ENTRY *lookup( MAC_HANDLE *handle, char *name, long special );
static char *rawval( MAC_HANDLE *handle, MAC_ENTRY *entry, char *value );
static void delete( MAC_HANDLE *handle, MAC_ENTRY *entry );
static long expand( MAC_HANDLE *handle );
static void trans ( MAC_HANDLE *handle, MAC_ENTRY *entry, long level,
char *term, char **rawval, char **value, char *valend );
static char *Strdup( char *string );
/*
* Static variables
*/
static MAC_CHARS macDefaultChars = {
MAC_STARTCHARS,
MAC_LEFTCHARS,
MAC_RIGHTCHARS,
MAC_SEPARCHARS,
MAC_ASSIGNCHARS,
MAC_ESCAPECHARS
};
/*
* Create a new macro substitution context and return an opaque handle
* associated with the new context. Also optionally load an initial set
* of macro definitions
*/
long /* 0 = OK; <0 = ERROR */
macCreateHandle(
MAC_HANDLE **pHandle, /* address of variable to receive pointer */
/* to new macro substitution context */
char *pairs[] ) /* pointer to NULL-terminated array of */
/* {name,value} pair strings; a NULL */
/* value implies undefined; a NULL */
/* argument implies no macros */
{
MAC_HANDLE *handle; /* pointer to macro substitution context */
/* ensure NULL handle pointer is returned on error */
*pHandle = NULL;
/* allocate macro substitution context */
handle = ( MAC_HANDLE * ) malloc( sizeof( MAC_HANDLE ) );
if ( handle == NULL ) {
macErrMessage0( -1, "macCreateHandle: failed to allocate context" );
return -1;
}
/* initialize context */
handle->magic = MAC_MAGIC;
handle->chars = macDefaultChars;
handle->dirty = FALSE;
handle->level = 0;
handle->debug = 0;
ellInit( &handle->list );
/* if supplied, load macro definitions */
for ( ; pairs && pairs[0]; pairs += 2 ) {
if ( macPutValue( handle, pairs[0], pairs[1] ) < 0 ) {
free( handle );
return -1;
}
}
/* set returned handle pointer */
*pHandle = handle;
return 0;
}
/*
* Expand a string that may contain macro references and return the
* expanded string
*
* This is a very basic and powerful routine. It's basically a wrapper
* around the the translation "engine" trans()
*/
long /* #chars copied, <0 if any macros are */
/* undefined */
macExpandString(
MAC_HANDLE *handle, /* opaque handle */
char *src, /* source string */
char *dest, /* destination string */
long maxlen ) /* maximum number of characters to copy */
/* to destination string */
{
MAC_ENTRY entry;
char *s, *d;
long length;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macExpandString: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macExpandString( %s, maxlen = %d )\n", src, maxlen );
/* expand raw values if necessary */
if ( expand( handle ) < 0 )
macErrMessage0( -1, "macExpandString: failed to expand raw values" );
/* fill in necessary fields in fake macro entry struncture */
entry.name = "<string>";
entry.error = FALSE;
/* expand the string */
s = src;
d = dest;
*d = '\0';
trans( handle, &entry, 0, "", &s, &d, d + maxlen );
/* return +/- #chars copied depending on successful expansion */
length = d - dest;
length = ( entry.error ) ? -length : length;
/* debug output */
if ( handle->debug & 1 )
printf( "macExpandString() -> %d\n", length );
return length;
}
/*
* Define the value of a macro. A NULL value deletes the macro if it
* already existed
*/
long /* length of value */
macPutValue(
MAC_HANDLE *handle, /* opaque handle */
char *name, /* macro name */
char *value ) /* macro value */
{
MAC_ENTRY *entry; /* pointer to this macro's entry structure */
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macPutValue: NULL or invalid handle\n" );
return -1;
}
if ( handle->debug & 1 )
epicsPrintf( "macPutValue( %s, %s )\n", name, value ? value : "NULL" );
/* handle NULL value case: if name was found, delete entry (may be
several entries at different scoping levels) */
if ( value == NULL ) {
while ( ( entry = lookup( handle, name, FALSE ) ) != NULL )
delete( handle, entry );
return 0;
}
/* look up macro name */
entry = lookup( handle, name, FALSE );
/* new entry must be created if macro doesn't exist or if it only
exists at a lower scoping level */
if ( entry == NULL || entry->level < handle->level ) {
entry = create( handle, name, FALSE );
if ( entry == NULL ) {
macErrMessage2( -1, "macPutValue: failed to create macro %s = %s",
name, value );
return -1;
}
}
/* copy raw value */
if ( rawval( handle, entry, value ) == NULL ) {
macErrMessage2( -1, "macPutValue: failed to copy macro %s = %s",
name, value ) ;
return -1;
}
/* return length of value */
return strlen( value );
}
/*
* Return the value of a macro
*/
long /* #chars copied (<0 if undefined) */
macGetValue(
MAC_HANDLE *handle, /* opaque handle */
char *name, /* macro name or reference */
char *value, /* string to receive macro value or name */
/* argument if macro is undefined */
long maxlen ) /* maximum number of characters to copy */
/* to value */
{
MAC_ENTRY *entry; /* pointer to this macro's entry structure */
long length; /* number of characters returned */
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macGetValue: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
epicsPrintf( "macGetValue( %s )\n", name );
/* look up macro name */
entry = lookup( handle, name, FALSE );
/* if maxlen <= 0 or VALUE == NULL just return -1 / 0 for undefined /
defined macro */
if ( maxlen <= 0 || value == NULL ) {
return ( entry == NULL ) ? -1 : 0;
}
/* if not found, copy name to value and return minus #chars copied */
if ( entry == NULL ) {
strncpy( value, name, maxlen );
return ( value[maxlen-1] == '\0' ) ? -strlen( name ) : -maxlen;
}
/* expand raw values if necessary; if fail (can only fail because of
memory allocation failure), return same as if not found */
if ( expand( handle ) < 0 ) {
macErrMessage0( -1, "macGetValue: failed to expand raw values" );
strncpy( value, name, maxlen );
return ( value[maxlen-1] == '\0' ) ? -strlen( name ) : -maxlen;
}
/* copy value and return +/- #chars copied depending on successful
expansion */
strncpy( value, entry->value, maxlen );
length = ( value[maxlen-1] == '\0' ) ? entry->length : maxlen;
return ( entry->error ) ? -length : length;
}
/*
* Free up all storage associated with and delete a macro substitution
* context
*/
long /* 0 = OK; <0 = ERROR */
macDeleteHandle(
MAC_HANDLE *handle ) /* opaque handle */
{
MAC_ENTRY *entry, *nextEntry;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macDeleteHandle: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macDeleteHandle()\n" );
/* delete all entries */
for ( entry = first( handle ); entry != NULL; entry = nextEntry ) {
nextEntry = next( entry );
delete( handle, entry );
}
/* clear magic field and free context structure */
handle->magic = 0;
free( handle );
return 0;
}
/*
* Mark the start of a new scoping level
*/
long /* 0 = OK; <0 = ERROR */
macPushScope(
MAC_HANDLE *handle ) /* opaque handle */
{
MAC_ENTRY *entry;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macPushScope: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macPushScope()\n" );
/* increment scoping level */
handle->level++;
/* create new "special" entry of name "<scope>" */
entry = create( handle, "<scope>", TRUE );
if ( entry == NULL ) {
handle->level--;
macErrMessage0( -1, "macPushScope: failed to push scope" );
return -1;
}
return 0;
}
/*
* Pop all macros defined since the last call to macPushScope()
*/
long /* 0 = OK; <0 = ERROR */
macPopScope(
MAC_HANDLE *handle ) /* opaque handle */
{
MAC_ENTRY *entry, *nextEntry;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macPopScope: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macPopScope()\n" );
/* check scoping level isn't already zero */
if ( handle->level == 0 ) {
macErrMessage0( -1, "macPopScope: no scope to pop" );
return -1;
}
/* look up most recent scope entry */
entry = lookup( handle, "<scope>", TRUE );
if ( entry == NULL ) {
macErrMessage0( -1, "macPopScope: no scope to pop" );
return -1;
}
/* delete scope entry and all macros defined since it */
for ( ; entry != NULL; entry = nextEntry ) {
nextEntry = next( entry );
delete( handle, entry );
}
/* decrement scoping level */
handle->level--;
return 0;
}
/*
* Report macro details to standard output
*/
long /* 0 = OK; <0 = ERROR */
macReportMacros(
MAC_HANDLE *handle ) /* opaque handle */
{
char *format = "%-1s %-16s %-16s %s\n";
MAC_ENTRY *entry;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macReportMacros: NULL or invalid handle\n" );
return -1;
}
/* expand raw values if necessary; report but ignore failure */
if ( expand( handle ) < 0 )
macErrMessage0( -1, "macGetValue: failed to expand raw values" );
/* loop through macros, reporting names and values */
epicsPrintf( format, "e", "name", "rawval", "value" );
epicsPrintf( format, "-", "----", "------", "-----" );
for ( entry = first( handle ); entry != NULL; entry = next( entry ) ) {
/* differentiate between "special" (scope marker) and ordinary
entries */
if ( entry->special )
epicsPrintf( format, "s", "----", "------", "-----" );
else
epicsPrintf( format, entry->error ? "*" : " ", entry->name,
entry->rawval ? entry->rawval : "",
entry->value ? entry->value : "");
}
return 0;
}
/******************** beginning of static functions ********************/
/*
* Return pointer to first macro entry (could be preprocessor macro)
*/
static MAC_ENTRY *first( MAC_HANDLE *handle )
{
return ( MAC_ENTRY * ) ellFirst( &handle->list );
}
/*
* Return pointer to last macro entry (could be preprocessor macro)
*/
static MAC_ENTRY *last( MAC_HANDLE *handle )
{
return ( MAC_ENTRY * ) ellLast( &handle->list );
}
/*
* Return pointer to next macro entry (could be preprocessor macro)
*/
static MAC_ENTRY *next( MAC_ENTRY *entry )
{
return ( MAC_ENTRY * ) ellNext( ( ELLNODE * ) entry );
}
/*
* Return pointer to previous macro entry (could be preprocessor macro)
*/
static MAC_ENTRY *previous( MAC_ENTRY *entry )
{
return ( MAC_ENTRY * ) ellPrevious( ( ELLNODE * ) entry );
}
/*
* Create new macro entry (can assume it doesn't exist)
*/
static MAC_ENTRY *create( MAC_HANDLE *handle, char *name, long special )
{
ELLLIST *list = &handle->list;
MAC_ENTRY *entry = ( MAC_ENTRY * ) malloc( sizeof( MAC_ENTRY ) );
if ( entry != NULL ) {
entry->name = Strdup( name );
if ( entry->name == NULL ) {
free( entry );
entry = NULL;
}
else {
entry->rawval = NULL;
entry->value = NULL;
entry->length = 0;
entry->error = FALSE;
entry->visited = FALSE;
entry->special = special;
entry->level = handle->level;
ellAdd( list, ( ELLNODE * ) entry );
}
}
return entry;
}
/*
* Look up macro entry with matching "special" attribute by name
*/
static MAC_ENTRY *lookup( MAC_HANDLE *handle, char *name, long special )
{
MAC_ENTRY *entry;
/* search backwards so scoping works */
for ( entry = last( handle ); entry != NULL; entry = previous( entry ) ) {
if ( entry->special != special )
continue;
if ( strcmp( name, entry->name ) == 0 )
break;
}
return entry;
}
/*
* Copy raw value to macro entry
*/
static char *rawval( MAC_HANDLE *handle, MAC_ENTRY *entry, char *value )
{
if ( entry->rawval != NULL )
free( entry->rawval );
entry->rawval = Strdup( value );
handle->dirty = TRUE;
return entry->rawval;
}
/*
* Delete a macro entry; requires re-expansion of macro values since this
* macro may be referenced by another one
*/
static void delete( MAC_HANDLE *handle, MAC_ENTRY *entry )
{
ELLLIST *list = &handle->list;
ellDelete( list, ( ELLNODE * ) entry );
free( entry->name );
if ( entry->rawval != NULL )
free( entry->rawval );
if ( entry->value != NULL )
free( entry->value );
free( entry );
handle->dirty = TRUE;
}
/*
* Expand macro definitions (expensive but done very infrequently)
*/
static long expand( MAC_HANDLE *handle )
{
MAC_ENTRY *entry;
char *rawval;
char *value;
if ( !handle->dirty )
return 0;
for ( entry = first( handle ); entry != NULL; entry = next( entry ) ) {
if ( handle->debug & 2 )
epicsPrintf( "\nexpand %s = %s\n", entry->name,
entry->rawval ? entry->rawval : "" );
if ( entry->value == NULL ) {
if ( ( entry->value = malloc( MAC_SIZE + 1 ) ) == NULL ) {
return -1;
}
}
/* start at level 1 so quotes and escapes will be removed from
expanded value */
rawval = entry->rawval;
value = entry->value;
*value = '\0';
entry->error = FALSE;
trans( handle, entry, 1, "", &rawval, &value, entry->value + MAC_SIZE );
entry->length = value - entry->value;
entry->value[MAC_SIZE] = '\0';
}
handle->dirty = FALSE;
return 0;
}
/*
* Translate raw macro value (recursive). This is by far the most complicated
* of the macro routines and calls itself recursively both to translate any
* macros referenced in the name and to translate the resulting name
*
* For now, use default special characters
*/
static void trans( MAC_HANDLE *handle, MAC_ENTRY *entry, long level,
char *term, char **rawval, char **value, char *valend )
{
char quote;
char *r, *v, *n2, *r2;
char name2[MAC_SIZE + 1];
long discard;
long macRef;
char *macEnd;
MAC_ENTRY *entry2;
/* return immediately if raw value is NULL */
if ( *rawval == NULL ) return;
/* discard quotes and escapes if level is > 0 (i.e. if these aren't
the user's quotes and escapes) */
discard = ( level > 0 );
/* debug output */
if ( handle->debug & 2 )
epicsPrintf( "trans-> level = %d, maxlen = %4d, discard = %s, "
"rawval = %s\n", level, valend - *value, discard ? "T" : "F", *rawval );
/* initially not in quotes */
quote = 0;
/* scan characters until hit terminator or end of string */
for ( r = *rawval, v = *value; strchr( term, *r ) == NULL; r++ ) {
/* handle quoted characters (quotes are discarded if in name) */
if ( quote ) {
if ( *r == quote ) {
quote = 0;
if ( discard ) continue;
}
}
else if ( *r == '"' || *r == '\'' ) {
quote = *r;
if ( discard ) continue;
}
/* macro reference if '$' followed by '(' or '{' */
macRef = ( *r == '$' && *( r + 1 ) != '\0' &&
strchr( "({", *( r + 1 ) ) != NULL );
/* if not macro reference (macros are not expanded in single quotes) */
if ( quote == '\'' || !macRef ) {
/* handle escaped characters (escape is discarded if in name) */
if ( *r == '\\' && *( r + 1 ) != '\0' ) {
if ( v < valend && !discard ) *v++ = '\\';
if ( v < valend ) *v++ = *++r;
}
/* copy character to output */
else {
if ( v < valend ) *v++ = *r;
}
/* ensure string remains properly terminated */
if ( v < valend ) *v = '\0';
}
/* macro reference */
else {
/* step over '$(' or '${' */
r++;
macEnd = ( *r == '(' ) ? ")" : "}";
r++;
/* translate name (may contain macro references); truncated
quietly if too long but always guaranteed zero-terminated */
n2 = name2;
*n2 = '\0';
trans( handle, entry, level + 1, macEnd, &r, &n2, n2 + MAC_SIZE );
name2[MAC_SIZE] = '\0';
/* look up resultant name; if not there, set error flag and
copy reference */
if ( ( entry2 = lookup( handle, name2, FALSE ) ) == NULL ) {
entry->error = TRUE;
macErrMessage2( -1, "%s: %s referenced but undefined",
entry->name, name2 );
sprintf( v, "$(%s)", name2 ); v += strlen( v );
}
/* check for recursive reference; if so, set error flag and
copy reference */
else if ( entry2->visited ) {
entry->error = TRUE;
macErrMessage2( -1, "%s: %s referenced recursively",
entry->name, entry2->name );
sprintf( v, "$(%s)", name2 ); v += strlen( v );
}
/* if all expanded values are valid (not "dirty") copy the
value directly */
else if ( !handle->dirty ) {
strcpy( v, entry2->value ); v += strlen( v );
}
/* otherwise, translate raw value */
else {
r2 = entry2->rawval;
entry2->visited = TRUE;
trans( handle, entry, level + 1, "", &r2, &v, valend );
entry2->visited = FALSE;
}
}
}
/* debug output */
if ( handle->debug & 2 )
epicsPrintf( "<-trans level = %d, length = %4d, value = %s\n",
level, v - *value, *value );
/* update pointers to next characters to scan in raw value and to fill
in in output value (if at end of input, step back so terminator is
still there to be seen) */
*rawval = ( *r == '\0' ) ? r - 1 : r;
*value = v;
return;
}
/*
* strdup() implementation (because it's not always available)
*/
char *Strdup( char *string )
{
char *copy = malloc( strlen( string ) + 1 );
if ( copy != NULL )
strcpy( copy, string );
return copy;
}
/* $Log$
* Revision 1.9 1996/06/26 09:43:14 wlupton
* first released version
*
*/

198
src/libCom/macLib.h Normal file
View File

@@ -0,0 +1,198 @@
/* $Id$
*
* Definitions for macro substitution library (macLib)
*
* William Lupton, W. M. Keck Observatory
*/
/*
* EPICS include files needed by this file
*/
#include "ellLib.h"
#include "epicsPrint.h"
#include "errMdef.h"
/*
* Standard FALSE and TRUE macros
*/
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
/*
* Magic number for validating context.
*/
#define MAC_MAGIC 0xabadcafe /* ...sells sub-standard coffee? */
/*
* Maximum size of macro name or value string (simpler to make fixed)
*/
#define MAC_SIZE 256
/*
* Macros passing __FILE__ and __LINE__ to what will be macErrPrintf()
*/
#define macErrMessage0( _status, _format ) \
errPrintf( _status, __FILE__, __LINE__, _format )
#define macErrMessage1( _status, _format, _arg1 ) \
errPrintf( _status, __FILE__, __LINE__, _format, _arg1 )
#define macErrMessage2( _status, _format, _arg1, _arg2 ) \
errPrintf( _status, __FILE__, __LINE__, _format, _arg1, _arg2 )
#define macErrMessage3( _status, _format, _arg1, _arg2, _arg3 ) \
errPrintf( _status, __FILE__, __LINE__, _format, _arg1, _arg2, _arg3 )
/*
* Special characters
*/
#define MAC_STARTCHARS "$" /* characters that indicate macro ref */
#define MAC_LEFTCHARS "{(" /* characters to left of macro name */
#define MAC_RIGHTCHARS "})" /* characters to right of macro name */
#define MAC_SEPARCHARS "," /* characters separating macro defns */
#define MAC_ASSIGNCHARS "=" /* characters denoting values assignment */
#define MAC_ESCAPECHARS "\\" /* characters escaping the next char */
typedef struct {
char *start;
char *left;
char *right;
char *separ;
char *assign;
char *escape;
} MAC_CHARS;
/*
* Macro substitution context. One of these contexts is allocated each time
* macCreateHandle() is called
*/
typedef struct {
long magic; /* magic number (used for authentication) */
MAC_CHARS chars; /* special characters */
long dirty; /* values need expanding from raw values? */
long level; /* scoping level */
long debug; /* debugging level */
ELLLIST list; /* macro name / value list */
} MAC_HANDLE;
/*
* Entry in linked list of macro definitions
*/
typedef struct mac_entry {
ELLNODE node; /* prev and next pointers */
char *name; /* macro name */
char *rawval; /* raw (unexpanded) value */
char *value; /* expanded macro value */
long length; /* length of value */
long error; /* error expanding value? */
long visited; /* ever been visited? */
long special; /* special (internal) entry? */
long level; /* scoping level */
} MAC_ENTRY;
/*
* Function prototypes (core library)
*/
long /* 0 = OK; <0 = ERROR */
macCreateHandle(
MAC_HANDLE **handle, /* address of variable to receive pointer */
/* to new macro substitution context */
char *pairs[] /* pointer to NULL-terminated array of */
/* {name,value} pair strings; a NULL */
/* value implies undefined; a NULL */
/* argument implies no macros */
);
long /* #chars copied, <0 if any macros are */
/* undefined */
macExpandString(
MAC_HANDLE *handle, /* opaque handle */
char *src, /* source string */
char *dest, /* destination string */
long maxlen /* maximum number of characters to copy */
/* to destination string */
);
long /* length of value */
macPutValue(
MAC_HANDLE *handle, /* opaque handle */
char *name, /* macro name */
char *value /* macro value */
);
long /* #chars copied (<0 if undefined) */
macGetValue(
MAC_HANDLE *handle, /* opaque handle */
char *name, /* macro name or reference */
char *value, /* string to receive macro value or name */
/* argument if macro is undefined */
long maxlen /* maximum number of characters to copy */
/* to value */
);
long /* 0 = OK; <0 = ERROR */
macDeleteHandle(
MAC_HANDLE *handle /* opaque handle */
);
long /* 0 = OK; <0 = ERROR */
macPushScope(
MAC_HANDLE *handle /* opaque handle */
);
long /* 0 = OK; <0 = ERROR */
macPopScope(
MAC_HANDLE *handle /* opaque handle */
);
long /* 0 = OK; <0 = ERROR */
macReportMacros(
MAC_HANDLE *handle /* opaque handle */
);
/*
* Function prototypes (utility library)
*/
long /* #defns encountered; <0 = ERROR */
macParseDefns(
MAC_HANDLE *handle, /* opaque handle; can be NULL if default */
/* special characters are to be used */
char *defns, /* macro definitions in "a=xxx,b=yyy" */
/* format */
char **pairs[] /* address of variable to receive pointer */
/* to NULL-terminated array of {name, */
/* value} pair strings; all storage is */
/* allocated contiguously */
);
long /* #macros defined; <0 = ERROR */
macInstallMacros(
MAC_HANDLE *handle, /* opaque handle */
char *pairs[] /* pointer to NULL-terminated array of */
/* {name,value} pair strings; a NULL */
/* value implies undefined; a NULL */
/* argument implies no macros */
);
/* $Log$
* Revision 1.8 1996/06/26 09:43:16 wlupton
* first released version
*
*/

749
src/libCom/macLib/macCore.c Normal file
View File

@@ -0,0 +1,749 @@
/* $Id$
*
* Implementation of core macro substitution library (macLib)
*
* Just about all functionality proposed in tech-talk on 19-Feb-96 has
* been implemented with only minor departures. Missing features are:
*
* 1. All the attribute-setting routines (for setting and getting
* special characters etc)
*
* 2. Error message verbosity control (although a debug level is
* supported)
*
* 3. The macro substitution tool (although the supplied macTest
* program is in fact a simple version of this)
*
* The implementation is fairly unsophisticated and linked lists are
* used to store macro values. Typically there will will be only a
* small number of macros and performance won't be a problem. Special
* measures are taken to avoid unnecessary expansion of macros whose
* definitions reference other macros. Whenever a macro is created,
* modified or deleted, a "dirty" flag is set; this causes a full
* expansion of all macros the next time a macro value is read
*
* See the files README (original specification from tech-talk) and
* NOTES (valid input file for the macTest program) for further
* information
*
* Testing has been primarily under SunOS, but the code has been built
* and macTest <NOTES run under vxWorks
*
* William Lupton, W. M. Keck Observatory
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "macLib.h"
/*
* Static function prototypes (these static functions provide an low-level
* library operating on macro entries)
*/
static MAC_ENTRY *first ( MAC_HANDLE *handle );
static MAC_ENTRY *last ( MAC_HANDLE *handle );
static MAC_ENTRY *next ( MAC_ENTRY *entry );
static MAC_ENTRY *previous( MAC_ENTRY *entry );
static MAC_ENTRY *create( MAC_HANDLE *handle, char *name, long special );
static MAC_ENTRY *lookup( MAC_HANDLE *handle, char *name, long special );
static char *rawval( MAC_HANDLE *handle, MAC_ENTRY *entry, char *value );
static void delete( MAC_HANDLE *handle, MAC_ENTRY *entry );
static long expand( MAC_HANDLE *handle );
static void trans ( MAC_HANDLE *handle, MAC_ENTRY *entry, long level,
char *term, char **rawval, char **value, char *valend );
static char *Strdup( char *string );
/*
* Static variables
*/
static MAC_CHARS macDefaultChars = {
MAC_STARTCHARS,
MAC_LEFTCHARS,
MAC_RIGHTCHARS,
MAC_SEPARCHARS,
MAC_ASSIGNCHARS,
MAC_ESCAPECHARS
};
/*
* Create a new macro substitution context and return an opaque handle
* associated with the new context. Also optionally load an initial set
* of macro definitions
*/
long /* 0 = OK; <0 = ERROR */
macCreateHandle(
MAC_HANDLE **pHandle, /* address of variable to receive pointer */
/* to new macro substitution context */
char *pairs[] ) /* pointer to NULL-terminated array of */
/* {name,value} pair strings; a NULL */
/* value implies undefined; a NULL */
/* argument implies no macros */
{
MAC_HANDLE *handle; /* pointer to macro substitution context */
/* ensure NULL handle pointer is returned on error */
*pHandle = NULL;
/* allocate macro substitution context */
handle = ( MAC_HANDLE * ) malloc( sizeof( MAC_HANDLE ) );
if ( handle == NULL ) {
macErrMessage0( -1, "macCreateHandle: failed to allocate context" );
return -1;
}
/* initialize context */
handle->magic = MAC_MAGIC;
handle->chars = macDefaultChars;
handle->dirty = FALSE;
handle->level = 0;
handle->debug = 0;
ellInit( &handle->list );
/* if supplied, load macro definitions */
for ( ; pairs && pairs[0]; pairs += 2 ) {
if ( macPutValue( handle, pairs[0], pairs[1] ) < 0 ) {
free( handle );
return -1;
}
}
/* set returned handle pointer */
*pHandle = handle;
return 0;
}
/*
* Expand a string that may contain macro references and return the
* expanded string
*
* This is a very basic and powerful routine. It's basically a wrapper
* around the the translation "engine" trans()
*/
long /* #chars copied, <0 if any macros are */
/* undefined */
macExpandString(
MAC_HANDLE *handle, /* opaque handle */
char *src, /* source string */
char *dest, /* destination string */
long maxlen ) /* maximum number of characters to copy */
/* to destination string */
{
MAC_ENTRY entry;
char *s, *d;
long length;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macExpandString: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macExpandString( %s, maxlen = %d )\n", src, maxlen );
/* expand raw values if necessary */
if ( expand( handle ) < 0 )
macErrMessage0( -1, "macExpandString: failed to expand raw values" );
/* fill in necessary fields in fake macro entry struncture */
entry.name = "<string>";
entry.error = FALSE;
/* expand the string */
s = src;
d = dest;
*d = '\0';
trans( handle, &entry, 0, "", &s, &d, d + maxlen );
/* return +/- #chars copied depending on successful expansion */
length = d - dest;
length = ( entry.error ) ? -length : length;
/* debug output */
if ( handle->debug & 1 )
printf( "macExpandString() -> %d\n", length );
return length;
}
/*
* Define the value of a macro. A NULL value deletes the macro if it
* already existed
*/
long /* length of value */
macPutValue(
MAC_HANDLE *handle, /* opaque handle */
char *name, /* macro name */
char *value ) /* macro value */
{
MAC_ENTRY *entry; /* pointer to this macro's entry structure */
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macPutValue: NULL or invalid handle\n" );
return -1;
}
if ( handle->debug & 1 )
epicsPrintf( "macPutValue( %s, %s )\n", name, value ? value : "NULL" );
/* handle NULL value case: if name was found, delete entry (may be
several entries at different scoping levels) */
if ( value == NULL ) {
while ( ( entry = lookup( handle, name, FALSE ) ) != NULL )
delete( handle, entry );
return 0;
}
/* look up macro name */
entry = lookup( handle, name, FALSE );
/* new entry must be created if macro doesn't exist or if it only
exists at a lower scoping level */
if ( entry == NULL || entry->level < handle->level ) {
entry = create( handle, name, FALSE );
if ( entry == NULL ) {
macErrMessage2( -1, "macPutValue: failed to create macro %s = %s",
name, value );
return -1;
}
}
/* copy raw value */
if ( rawval( handle, entry, value ) == NULL ) {
macErrMessage2( -1, "macPutValue: failed to copy macro %s = %s",
name, value ) ;
return -1;
}
/* return length of value */
return strlen( value );
}
/*
* Return the value of a macro
*/
long /* #chars copied (<0 if undefined) */
macGetValue(
MAC_HANDLE *handle, /* opaque handle */
char *name, /* macro name or reference */
char *value, /* string to receive macro value or name */
/* argument if macro is undefined */
long maxlen ) /* maximum number of characters to copy */
/* to value */
{
MAC_ENTRY *entry; /* pointer to this macro's entry structure */
long length; /* number of characters returned */
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macGetValue: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
epicsPrintf( "macGetValue( %s )\n", name );
/* look up macro name */
entry = lookup( handle, name, FALSE );
/* if maxlen <= 0 or VALUE == NULL just return -1 / 0 for undefined /
defined macro */
if ( maxlen <= 0 || value == NULL ) {
return ( entry == NULL ) ? -1 : 0;
}
/* if not found, copy name to value and return minus #chars copied */
if ( entry == NULL ) {
strncpy( value, name, maxlen );
return ( value[maxlen-1] == '\0' ) ? -strlen( name ) : -maxlen;
}
/* expand raw values if necessary; if fail (can only fail because of
memory allocation failure), return same as if not found */
if ( expand( handle ) < 0 ) {
macErrMessage0( -1, "macGetValue: failed to expand raw values" );
strncpy( value, name, maxlen );
return ( value[maxlen-1] == '\0' ) ? -strlen( name ) : -maxlen;
}
/* copy value and return +/- #chars copied depending on successful
expansion */
strncpy( value, entry->value, maxlen );
length = ( value[maxlen-1] == '\0' ) ? entry->length : maxlen;
return ( entry->error ) ? -length : length;
}
/*
* Free up all storage associated with and delete a macro substitution
* context
*/
long /* 0 = OK; <0 = ERROR */
macDeleteHandle(
MAC_HANDLE *handle ) /* opaque handle */
{
MAC_ENTRY *entry, *nextEntry;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macDeleteHandle: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macDeleteHandle()\n" );
/* delete all entries */
for ( entry = first( handle ); entry != NULL; entry = nextEntry ) {
nextEntry = next( entry );
delete( handle, entry );
}
/* clear magic field and free context structure */
handle->magic = 0;
free( handle );
return 0;
}
/*
* Mark the start of a new scoping level
*/
long /* 0 = OK; <0 = ERROR */
macPushScope(
MAC_HANDLE *handle ) /* opaque handle */
{
MAC_ENTRY *entry;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macPushScope: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macPushScope()\n" );
/* increment scoping level */
handle->level++;
/* create new "special" entry of name "<scope>" */
entry = create( handle, "<scope>", TRUE );
if ( entry == NULL ) {
handle->level--;
macErrMessage0( -1, "macPushScope: failed to push scope" );
return -1;
}
return 0;
}
/*
* Pop all macros defined since the last call to macPushScope()
*/
long /* 0 = OK; <0 = ERROR */
macPopScope(
MAC_HANDLE *handle ) /* opaque handle */
{
MAC_ENTRY *entry, *nextEntry;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macPopScope: NULL or invalid handle\n" );
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macPopScope()\n" );
/* check scoping level isn't already zero */
if ( handle->level == 0 ) {
macErrMessage0( -1, "macPopScope: no scope to pop" );
return -1;
}
/* look up most recent scope entry */
entry = lookup( handle, "<scope>", TRUE );
if ( entry == NULL ) {
macErrMessage0( -1, "macPopScope: no scope to pop" );
return -1;
}
/* delete scope entry and all macros defined since it */
for ( ; entry != NULL; entry = nextEntry ) {
nextEntry = next( entry );
delete( handle, entry );
}
/* decrement scoping level */
handle->level--;
return 0;
}
/*
* Report macro details to standard output
*/
long /* 0 = OK; <0 = ERROR */
macReportMacros(
MAC_HANDLE *handle ) /* opaque handle */
{
char *format = "%-1s %-16s %-16s %s\n";
MAC_ENTRY *entry;
/* check handle */
if ( handle == NULL || handle->magic != MAC_MAGIC ) {
macErrMessage0( -1, "macReportMacros: NULL or invalid handle\n" );
return -1;
}
/* expand raw values if necessary; report but ignore failure */
if ( expand( handle ) < 0 )
macErrMessage0( -1, "macGetValue: failed to expand raw values" );
/* loop through macros, reporting names and values */
epicsPrintf( format, "e", "name", "rawval", "value" );
epicsPrintf( format, "-", "----", "------", "-----" );
for ( entry = first( handle ); entry != NULL; entry = next( entry ) ) {
/* differentiate between "special" (scope marker) and ordinary
entries */
if ( entry->special )
epicsPrintf( format, "s", "----", "------", "-----" );
else
epicsPrintf( format, entry->error ? "*" : " ", entry->name,
entry->rawval ? entry->rawval : "",
entry->value ? entry->value : "");
}
return 0;
}
/******************** beginning of static functions ********************/
/*
* Return pointer to first macro entry (could be preprocessor macro)
*/
static MAC_ENTRY *first( MAC_HANDLE *handle )
{
return ( MAC_ENTRY * ) ellFirst( &handle->list );
}
/*
* Return pointer to last macro entry (could be preprocessor macro)
*/
static MAC_ENTRY *last( MAC_HANDLE *handle )
{
return ( MAC_ENTRY * ) ellLast( &handle->list );
}
/*
* Return pointer to next macro entry (could be preprocessor macro)
*/
static MAC_ENTRY *next( MAC_ENTRY *entry )
{
return ( MAC_ENTRY * ) ellNext( ( ELLNODE * ) entry );
}
/*
* Return pointer to previous macro entry (could be preprocessor macro)
*/
static MAC_ENTRY *previous( MAC_ENTRY *entry )
{
return ( MAC_ENTRY * ) ellPrevious( ( ELLNODE * ) entry );
}
/*
* Create new macro entry (can assume it doesn't exist)
*/
static MAC_ENTRY *create( MAC_HANDLE *handle, char *name, long special )
{
ELLLIST *list = &handle->list;
MAC_ENTRY *entry = ( MAC_ENTRY * ) malloc( sizeof( MAC_ENTRY ) );
if ( entry != NULL ) {
entry->name = Strdup( name );
if ( entry->name == NULL ) {
free( entry );
entry = NULL;
}
else {
entry->rawval = NULL;
entry->value = NULL;
entry->length = 0;
entry->error = FALSE;
entry->visited = FALSE;
entry->special = special;
entry->level = handle->level;
ellAdd( list, ( ELLNODE * ) entry );
}
}
return entry;
}
/*
* Look up macro entry with matching "special" attribute by name
*/
static MAC_ENTRY *lookup( MAC_HANDLE *handle, char *name, long special )
{
MAC_ENTRY *entry;
/* search backwards so scoping works */
for ( entry = last( handle ); entry != NULL; entry = previous( entry ) ) {
if ( entry->special != special )
continue;
if ( strcmp( name, entry->name ) == 0 )
break;
}
return entry;
}
/*
* Copy raw value to macro entry
*/
static char *rawval( MAC_HANDLE *handle, MAC_ENTRY *entry, char *value )
{
if ( entry->rawval != NULL )
free( entry->rawval );
entry->rawval = Strdup( value );
handle->dirty = TRUE;
return entry->rawval;
}
/*
* Delete a macro entry; requires re-expansion of macro values since this
* macro may be referenced by another one
*/
static void delete( MAC_HANDLE *handle, MAC_ENTRY *entry )
{
ELLLIST *list = &handle->list;
ellDelete( list, ( ELLNODE * ) entry );
free( entry->name );
if ( entry->rawval != NULL )
free( entry->rawval );
if ( entry->value != NULL )
free( entry->value );
free( entry );
handle->dirty = TRUE;
}
/*
* Expand macro definitions (expensive but done very infrequently)
*/
static long expand( MAC_HANDLE *handle )
{
MAC_ENTRY *entry;
char *rawval;
char *value;
if ( !handle->dirty )
return 0;
for ( entry = first( handle ); entry != NULL; entry = next( entry ) ) {
if ( handle->debug & 2 )
epicsPrintf( "\nexpand %s = %s\n", entry->name,
entry->rawval ? entry->rawval : "" );
if ( entry->value == NULL ) {
if ( ( entry->value = malloc( MAC_SIZE + 1 ) ) == NULL ) {
return -1;
}
}
/* start at level 1 so quotes and escapes will be removed from
expanded value */
rawval = entry->rawval;
value = entry->value;
*value = '\0';
entry->error = FALSE;
trans( handle, entry, 1, "", &rawval, &value, entry->value + MAC_SIZE );
entry->length = value - entry->value;
entry->value[MAC_SIZE] = '\0';
}
handle->dirty = FALSE;
return 0;
}
/*
* Translate raw macro value (recursive). This is by far the most complicated
* of the macro routines and calls itself recursively both to translate any
* macros referenced in the name and to translate the resulting name
*
* For now, use default special characters
*/
static void trans( MAC_HANDLE *handle, MAC_ENTRY *entry, long level,
char *term, char **rawval, char **value, char *valend )
{
char quote;
char *r, *v, *n2, *r2;
char name2[MAC_SIZE + 1];
long discard;
long macRef;
char *macEnd;
MAC_ENTRY *entry2;
/* return immediately if raw value is NULL */
if ( *rawval == NULL ) return;
/* discard quotes and escapes if level is > 0 (i.e. if these aren't
the user's quotes and escapes) */
discard = ( level > 0 );
/* debug output */
if ( handle->debug & 2 )
epicsPrintf( "trans-> level = %d, maxlen = %4d, discard = %s, "
"rawval = %s\n", level, valend - *value, discard ? "T" : "F", *rawval );
/* initially not in quotes */
quote = 0;
/* scan characters until hit terminator or end of string */
for ( r = *rawval, v = *value; strchr( term, *r ) == NULL; r++ ) {
/* handle quoted characters (quotes are discarded if in name) */
if ( quote ) {
if ( *r == quote ) {
quote = 0;
if ( discard ) continue;
}
}
else if ( *r == '"' || *r == '\'' ) {
quote = *r;
if ( discard ) continue;
}
/* macro reference if '$' followed by '(' or '{' */
macRef = ( *r == '$' && *( r + 1 ) != '\0' &&
strchr( "({", *( r + 1 ) ) != NULL );
/* if not macro reference (macros are not expanded in single quotes) */
if ( quote == '\'' || !macRef ) {
/* handle escaped characters (escape is discarded if in name) */
if ( *r == '\\' && *( r + 1 ) != '\0' ) {
if ( v < valend && !discard ) *v++ = '\\';
if ( v < valend ) *v++ = *++r;
}
/* copy character to output */
else {
if ( v < valend ) *v++ = *r;
}
/* ensure string remains properly terminated */
if ( v < valend ) *v = '\0';
}
/* macro reference */
else {
/* step over '$(' or '${' */
r++;
macEnd = ( *r == '(' ) ? ")" : "}";
r++;
/* translate name (may contain macro references); truncated
quietly if too long but always guaranteed zero-terminated */
n2 = name2;
*n2 = '\0';
trans( handle, entry, level + 1, macEnd, &r, &n2, n2 + MAC_SIZE );
name2[MAC_SIZE] = '\0';
/* look up resultant name; if not there, set error flag and
copy reference */
if ( ( entry2 = lookup( handle, name2, FALSE ) ) == NULL ) {
entry->error = TRUE;
macErrMessage2( -1, "%s: %s referenced but undefined",
entry->name, name2 );
sprintf( v, "$(%s)", name2 ); v += strlen( v );
}
/* check for recursive reference; if so, set error flag and
copy reference */
else if ( entry2->visited ) {
entry->error = TRUE;
macErrMessage2( -1, "%s: %s referenced recursively",
entry->name, entry2->name );
sprintf( v, "$(%s)", name2 ); v += strlen( v );
}
/* if all expanded values are valid (not "dirty") copy the
value directly */
else if ( !handle->dirty ) {
strcpy( v, entry2->value ); v += strlen( v );
}
/* otherwise, translate raw value */
else {
r2 = entry2->rawval;
entry2->visited = TRUE;
trans( handle, entry, level + 1, "", &r2, &v, valend );
entry2->visited = FALSE;
}
}
}
/* debug output */
if ( handle->debug & 2 )
epicsPrintf( "<-trans level = %d, length = %4d, value = %s\n",
level, v - *value, *value );
/* update pointers to next characters to scan in raw value and to fill
in in output value (if at end of input, step back so terminator is
still there to be seen) */
*rawval = ( *r == '\0' ) ? r - 1 : r;
*value = v;
return;
}
/*
* strdup() implementation (because it's not always available)
*/
char *Strdup( char *string )
{
char *copy = malloc( strlen( string ) + 1 );
if ( copy != NULL )
strcpy( copy, string );
return copy;
}
/* $Log$
* Revision 1.9 1996/06/26 09:43:14 wlupton
* first released version
*
*/

198
src/libCom/macLib/macLib.h Normal file
View File

@@ -0,0 +1,198 @@
/* $Id$
*
* Definitions for macro substitution library (macLib)
*
* William Lupton, W. M. Keck Observatory
*/
/*
* EPICS include files needed by this file
*/
#include "ellLib.h"
#include "epicsPrint.h"
#include "errMdef.h"
/*
* Standard FALSE and TRUE macros
*/
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
/*
* Magic number for validating context.
*/
#define MAC_MAGIC 0xabadcafe /* ...sells sub-standard coffee? */
/*
* Maximum size of macro name or value string (simpler to make fixed)
*/
#define MAC_SIZE 256
/*
* Macros passing __FILE__ and __LINE__ to what will be macErrPrintf()
*/
#define macErrMessage0( _status, _format ) \
errPrintf( _status, __FILE__, __LINE__, _format )
#define macErrMessage1( _status, _format, _arg1 ) \
errPrintf( _status, __FILE__, __LINE__, _format, _arg1 )
#define macErrMessage2( _status, _format, _arg1, _arg2 ) \
errPrintf( _status, __FILE__, __LINE__, _format, _arg1, _arg2 )
#define macErrMessage3( _status, _format, _arg1, _arg2, _arg3 ) \
errPrintf( _status, __FILE__, __LINE__, _format, _arg1, _arg2, _arg3 )
/*
* Special characters
*/
#define MAC_STARTCHARS "$" /* characters that indicate macro ref */
#define MAC_LEFTCHARS "{(" /* characters to left of macro name */
#define MAC_RIGHTCHARS "})" /* characters to right of macro name */
#define MAC_SEPARCHARS "," /* characters separating macro defns */
#define MAC_ASSIGNCHARS "=" /* characters denoting values assignment */
#define MAC_ESCAPECHARS "\\" /* characters escaping the next char */
typedef struct {
char *start;
char *left;
char *right;
char *separ;
char *assign;
char *escape;
} MAC_CHARS;
/*
* Macro substitution context. One of these contexts is allocated each time
* macCreateHandle() is called
*/
typedef struct {
long magic; /* magic number (used for authentication) */
MAC_CHARS chars; /* special characters */
long dirty; /* values need expanding from raw values? */
long level; /* scoping level */
long debug; /* debugging level */
ELLLIST list; /* macro name / value list */
} MAC_HANDLE;
/*
* Entry in linked list of macro definitions
*/
typedef struct mac_entry {
ELLNODE node; /* prev and next pointers */
char *name; /* macro name */
char *rawval; /* raw (unexpanded) value */
char *value; /* expanded macro value */
long length; /* length of value */
long error; /* error expanding value? */
long visited; /* ever been visited? */
long special; /* special (internal) entry? */
long level; /* scoping level */
} MAC_ENTRY;
/*
* Function prototypes (core library)
*/
long /* 0 = OK; <0 = ERROR */
macCreateHandle(
MAC_HANDLE **handle, /* address of variable to receive pointer */
/* to new macro substitution context */
char *pairs[] /* pointer to NULL-terminated array of */
/* {name,value} pair strings; a NULL */
/* value implies undefined; a NULL */
/* argument implies no macros */
);
long /* #chars copied, <0 if any macros are */
/* undefined */
macExpandString(
MAC_HANDLE *handle, /* opaque handle */
char *src, /* source string */
char *dest, /* destination string */
long maxlen /* maximum number of characters to copy */
/* to destination string */
);
long /* length of value */
macPutValue(
MAC_HANDLE *handle, /* opaque handle */
char *name, /* macro name */
char *value /* macro value */
);
long /* #chars copied (<0 if undefined) */
macGetValue(
MAC_HANDLE *handle, /* opaque handle */
char *name, /* macro name or reference */
char *value, /* string to receive macro value or name */
/* argument if macro is undefined */
long maxlen /* maximum number of characters to copy */
/* to value */
);
long /* 0 = OK; <0 = ERROR */
macDeleteHandle(
MAC_HANDLE *handle /* opaque handle */
);
long /* 0 = OK; <0 = ERROR */
macPushScope(
MAC_HANDLE *handle /* opaque handle */
);
long /* 0 = OK; <0 = ERROR */
macPopScope(
MAC_HANDLE *handle /* opaque handle */
);
long /* 0 = OK; <0 = ERROR */
macReportMacros(
MAC_HANDLE *handle /* opaque handle */
);
/*
* Function prototypes (utility library)
*/
long /* #defns encountered; <0 = ERROR */
macParseDefns(
MAC_HANDLE *handle, /* opaque handle; can be NULL if default */
/* special characters are to be used */
char *defns, /* macro definitions in "a=xxx,b=yyy" */
/* format */
char **pairs[] /* address of variable to receive pointer */
/* to NULL-terminated array of {name, */
/* value} pair strings; all storage is */
/* allocated contiguously */
);
long /* #macros defined; <0 = ERROR */
macInstallMacros(
MAC_HANDLE *handle, /* opaque handle */
char *pairs[] /* pointer to NULL-terminated array of */
/* {name,value} pair strings; a NULL */
/* value implies undefined; a NULL */
/* argument implies no macros */
);
/* $Log$
* Revision 1.8 1996/06/26 09:43:16 wlupton
* first released version
*
*/

View File

@@ -0,0 +1,160 @@
# $Id$
#
# Test input file for macTest filter, doubling as notes on usage of the
# macro library. Some special strings at start of line are supported:
#
# 1. '#' indicates a comment and is ignored
#
# 2. '%set' is followed by "a=b, c=d" style macro definitions which are
# passed through macParseDefs() and macInstallMacros()
#
# 3. '%push' pushes a scoping level by calling macPushScope()
#
# 4. '%pop' pops a scoping level by calling macPopScope()
#
# 5. '%report' reports macro definitions by calling macReportMacros()
#
# 6. all other lines are expanded by callins macExpandString()
#
introduction
------------
See the README file for the library specification and see the header
comments of macCore.c (the core library) for notes on what has been
implemented.
This file contains tutorial information and examples. It's the
best documentation that there is.
simple examples
---------------
To define the a, b, c and d macros to be 1, 2, 3 and 4 (note optional
white space is ignored around '=' and ',' characters):
%set a=1, b=2, c = 3 , d = 4
These macros can be dereferenced using '$(xxx)' or '${xxx}' notation:
a = $(a), b = $(b), c = ${c}, d = ${d}.
Macro values can reference other macros in an arbitrarily complex way.
The only current restrictions are that a macro name or value cannot
exceed 256 characters in length. This restriction could fairly easily
be lifted.
Here's an example:
%set x = ${$(y$(z))}
If this is expanded now: $(x), it won't work because the other macros
aren't defined yet. So:
%set cash=lucre, ywork=cash, z=work
Now expansion yields "$(x)" (work -> ywork -> cash -> lucre).
One can inadvertently set up circular references. For example:
%set mac1=$(mac2), mac2=$(mac3), mac3=$(mac1)
An attempt to dereference mac1 gives $(mac1). When a macro expansion
fails, the translation that failed is replaced with the text that could
not be expanded.
You can get a report of current macro definitions by doing
%report
The '*' character in the first column indicates a problem expanding that
macro. We'll get rid of these problem macros:
%set mac1, mac2, mac3
You can also push a new scoping level by doing
%push
and pop back to it by doing
%pop
For example:
%set level = 0
%push
%set level = 1
%push
%set level = 2
%push
%set level = 3
%report
Level is $(level)
%pop
Level is $(level)
%pop
Level is $(level)
%pop
Level is $(level)
%pop
(That last error was deliberate)
quote and escape handling
-------------------------
Both single and double quotes are supported, as are escapes. Some of the
implications are quite subtle and some of the design choices made may not
meet with universal approval. The basic idea is that a string in which
macro references have been expanded should look like the source string
apart from the where macros have been expanded. This implies that quote
and escape characters should not be removed from the string.
Single and double quotes are different in that (as in most shells),
macros are substituted within double quotes but not within single
quotes. Back quotes are not special characters. Missing quotes at the
end of a string are automatically and quietly supplied.
We've already seen some examples but what happens here: $(x)?
Should not that have been expanded? Why wasn't it? The answer is given
later on. As a clue, this is: $(x). This isn't: $(x)!
Characters may be escaped by preceding them with a \ (back slash).
Escapes work even within quotes, which is more like C than most shells.
Thus, '\'' works, and it doesn't with the C shell.
Quotes and escapes can also be used in "a=b, c=d" style assignments
but they are not part of macro names. For example:
%set \= = equal, \' = quote
defines macros called "=" and "'", not "\=" and "\'". To reference
these macros, "$(=)" ('$(=)') works because "=" is not special in
this context. However the quote must be escaped because quotes are
always special, as in "$(\')" ('$(\')').
miscellaneous notes
-------------------
In the "a=b, c=d" strings that are parsed by macParseDefns(), a macro
can be set to an empty value by putting nothing after the equals
sign, as in
%set empty =
resulting in empty = "$(empty)". Omitting the equal sign gives the
macro a NULL rather than empty value (with the result that the macro
is deleted since this is how macPutValue() interprets a NULL value), so
%set empty
deletes the empty macro, as can be seen when we try to de-reference
it: $(empty).
The only special characters in these "a=b, c=d" strings are "=", ","
and white space surrounding these characters (plus quotes and escapes
of course). This means that
%set fred 2
actually sets a macro called "fred 2" to a NULL value (i.e. it deletes
it if it existed). This is probably wrong and the space should be
taken as an implicit '='. However, to do this and still to support
ignored white space around '=' and ',' characters is a bigger change
than I am prepared to make at present.
What was the problem expanding "$(x)" before? It was single quotes
from words like "wasn't"! Is this a problem or a feature?

View File

@@ -0,0 +1,288 @@
README file for etc/macLib directory
------------------------------------
This directory contains all files necessary to build and test the
EPICS macro library macLib. The specification for this library
appears later in this file.
Files in this release are:
-rw-r--r-- 1 wlupton 141 Sep 22 1995 Makefile
-rw-r--r-- 1 wlupton 522 Jun 23 20:42 Makefile.Unix
-rw-r--r-- 1 wlupton 473 Jun 23 20:43 Makefile.Vx
-rw-r--r-- 1 wlupton 5052 Jun 25 23:17 NOTES
-rw-r--r-- 1 wlupton 10429 Jun 25 23:34 README
-rw-r--r-- 1 wlupton 20158 Jun 25 23:40 macCore.c
-rw-r--r-- 1 wlupton 5056 Jun 25 21:33 macLib.h
-rw-r--r-- 1 wlupton 2050 Jun 25 23:18 macTest.c
-rw-r--r-- 1 wlupton 7307 Jun 25 23:13 macUtil.c
25-Jun-96, William Lupton (wlupton@keck.hawaii.edu), CVS tag: macLib-1-0
------------------------------------------------------------------------
first release
------------------------------------------------------------------------
This is the original final specification for the EPICS macro library:
From wlupton Mon Feb 19 14:10:08 1996
To: tech-talk@aps.anl.gov
Subject: Re: macro substitution
Content-Length: 9716
X-Lines: 251
Status: RO
Dear all,
Here's yet another proposal for the macro library. There has been some
private discussion between myself, Jim and Marty, and I hope that this
is nearly final (or final enough to be implemented). After all the
discussion, the actual implementation is going to be a breeze!
The biggest changes are a move a way from the "filesystem-like"
routine names and a reduction in the emphasis placed on the standalone
tool.
I'm afraid there are still a few minor issues to be resolved but
probably nothing that can't wait for an alpha release.
William
------------------------------------------------------------------------
1. Macro substitution library
-----------------------------
This library could be used directly by applications which need to
support macro substitution. It will be implemented on all platforms.
1.1 Core library
----------------
The core library provides a minimal set of basic operations. Some
utility routines, described later, use core routines to provide a more
convenient interface for some purposes.
a) long macCreateHandle( MAC_HANDLE **handle, char *pairs[] );
Creates a new macro substitution context and returns an opaque handle
to that context. An application can, if it desires, have several
active contexts, although most will not.
If desired, an initial set of macro definitions may be loaded
("pairs" is set to NULL to avoid this). The definitions are in the
"standard" pairs format, as described under macParseDefns().
Note that MAC_HANDLE is a typedef for the context structure. The
opaque handle is of type "MAC_HANDLE *", which is a pointer to the
context structure. The memory for this context is allocated by this
routine.
b) long macGetXxxx( MAC_HANDLE *handle, <Xxxx-specific> );
long macPutXxxx( MAC_HANDLE *handle, <Xxxx-specific> );
These routines replace the earlier-proposed macIoctl(). "Xxxx" is the
name of the attribute in question. An initial list (which can grow)
might be:
Attribute default value
--------- -------------
ErrRoutine errPrintf
Verbose TRUE
StartChars "$"
LeftChars "{("
RightChars "})"
SeparChars ","
AssignChars "="
A NULL handle can be used to set or get global values (used by
macParseDefns()).
c) long macGetValue ( MAC_HANDLE *handle, char *name,
char *value, long maxlen );
Returns up to maxlen characters of the value of macro "name". "value"
will be zero-terminated if the length of the value is less than
maxlen. The function value will be the number of characters copied to
"value" (see below for behavior if the macro is undefined). If maxlen
is zero, no characters will be copied to "value" (which can be NULL)
and the call can be used to check whether the macro is defined. Note
that truncation of the value is therefore not reported. Is this a
problem?
If the macro is undefined, the macro reference will be returned in
the value string (if permitted by maxlen) and the function value will
be _minus_ the number of characters copied. Note that treatment of
maxlen is intended to be consistent with what people are used to with
strncpy().
"name" can either be just the macro name or can have the syntax of a
macro reference (e.g. it can be "fred" or "$(fred)"). In the latter
case, any characters after the logical end of the macro reference
will be ignored (this is to ease expansion of a string which may
contain many macro references), but no means of returning the number
of characters scanned in "name" is provided. [It may prove expedient
to make macGetValue() simpler and provide a separate routine with
more parameters which returns this information.]
If the name or value contains a macro reference then the reference
will be expanded recursively. This expansion will detect a direct or
indirect self reference.
Macro references begin with a "$" immediately followed by either a
"(" or a "{" character. The reference is terminated by the matching
")" or "}" character. These characters can be changed via
macPut{Start,Left,Right}Chars() above.
d) long macPutValue( MAC_HANDLE *handle, char *name, char *value );
Sets the value of the macro "name". If "value" is NULL, undefines
"name" (it's not an error if "name" was already undefined). Macros
referenced by "value" need not be defined at this point.
The function value is the length of the value.
e) long macDeleteHandle( MAC_HANDLE *handle );
Marks a handle invalid, and frees all storage associated with it.
Note that this does not free any strings into which macro values have
been returned. Macro values are always returned into strings which
were pre-allocated by the caller.
f) long macPushScope( MAC_HANDLE *handle );
Marks the start of a new scoping level such that all definitions made
up until the next macPopScope() call will be lost on macPopScope()
and those current at macPushScope() will be re-instated.
May not be implemented first time round.
g) long macPopScope( MAC_HANDLE *handle );
See above.
h) Error handling
These routines conform to 0 (=OK) for success, -1 (=ERROR) for
failure, and small positive values for extra info. I contravened this
for macGetValue() and macExpandString() because I felt that it was
worth returning information both on success / failure and on value
length.
Errors are reported using an errPrintf()-compatible routine.
A "verbose" variable will control error reporting.
1.2 Utility library
-------------------
These are convenience functions. If other useful functions emerge during
implementation, the list may grow.
a) macParseDefns( char *defns, char **pairs[] );
This takes a set of macro definitions in "a=xxx,b=yyy" format and
converts them into an array of pointers to character strings which
are, in order, "first name", "first value", "second name", "second
value" etc. The array is terminated with two NULL pointers and all
storage is allocated contiguously so that it can be freed with a
single call to free().
This routine is independent of any handle and provides a generally
useful service which may be used elsewhere. Any macro references in
values are not expanded at this point since the referenced macros may
be defined or redefined before the macro actually has to be
translated.
Shell-style escapes and quotes are supported, as are things like
"A=B,B=$(C$(A)),CA=CA,CB=CB" (sets B to "CB"). White space is
significant within values but ignored elsewhere (i.e. surrounding "="
and "," characters).
Probably noone will ever want to, but the special meanings of "$",
"{", "}", "(", ")", "=" and "," can all be changed via macPutXxxx()
calls. This routine does not have a handle argument, so they must be
changed globally for it to use the new definitions. Should it have a
handle argument? This makes it more of a pain to use but guarantees
that there will be no conflicts. I think it should really.
The function value is the number of definitions encountered, or -1 if
the supplied string is invalid.
b) long macInstallMacros( MAC_HANDLE *handle, char *pairs[] );
This takes an array of pairs as defined above and installs them as
definitions by calling macPutValue(). The pairs array is terminated
by a NULL pointer.
The function value is the number of macros defined.
c) long macExpandString( MAC_HANDLE *handle, char *src,
char *dest, long maxlen );
This operates on a string which may contain macro references. It
parses the string looking for such references and passes them to
macGetValue() for translation. It returns the expanded string in the
supplied argument.
The function value is similar to that of macGetValue(). Its absolute
value is the number of characters copied. If negative, at least one
undefined macro has been left unexpanded.
d) long macReportMacros( MAC_HANDLE *handle );
This reports details of current definitions to standard output (or
standard error?). It's purely for debugging purposes.
2. Macro substitution tool
--------------------------
This section is hardly changed from the previous version. The library will
be implemented first.
A "macsub" (or "mactool"?) application will be written which would use the
above library and use a command like:
macsub a=b,c=d e=f -Iaaa -Ibbb
to parse a file like:
# comment
%include filename
%xxx = ppp
%yyy = $(xxx)
<arbitrary text containing macro references>
There will also be options to read and write specified files, control the
comment character, control the command prefix, change the macro substitution
characters etc.
Syntax will be a loose subset of corresponding shell syntax.
The use of a command prefix should be optional and perhaps it should default
to an empty string. However there will be cases where the file in question
has a syntax over which we have no control, which may include assignments,
"set" commands, "define" commands etc. This is why I proposed a command
prefix, which might be an arbitrary string such as "macro " or might just
be "%" as in the above example.
The tool can also support the following syntax (or perhaps some equivalent
syntax which will sit more easily inside a file that may have a completely
different syntax from it?):
file gizmo_database.db
{
instance1{ pre=part1,parm=big, alarm_limit=20 }
instance2{ pre=part2,parm=medium,alarm_limit=30 }
}
as an implied multiple include of "gizmo_database.db" with the specified
macro definitions for each include (here's a good application for
macPushScope() and macPopScope() actually).

275
src/libCom/macLib/macUtil.c Normal file
View File

@@ -0,0 +1,275 @@
/* $Id$
*
* Implementation of utility macro substitution library (macLib)
*
* William Lupton, W. M. Keck Observatory
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "macLib.h"
/*
* Parse macros definitions in "a=xxx,b=yyy" format and convert them to
* a contiguously allocated array pointers to names and values, and the
* name and value strings, terminated with two NULL pointers. Quotes
* and escapes are honored but only removed from macro names (not
* values)
*
* Doesn't yet use handle (so uses default special characters)
*/
long /* #defns encountered; <0 = ERROR */
macParseDefns(
MAC_HANDLE *handle, /* opaque handle; can be NULL if default */
/* special characters are to be used */
char *defns, /* macro definitions in "a=xxx,b=yyy" */
/* format */
char **pairs[] ) /* address of variable to receive pointer */
/* to NULL-terminated array of {name, */
/* value} pair strings; all storage is */
/* allocated contiguously */
{
long i;
long num;
long quote;
long escape;
long nbytes;
char **ptr;
char **end;
long *del;
char *memCp, **memCpp;
char *c, *s, *d, **p;
enum { preName, inName, preValue, inValue } state;
/* debug output */
if ( handle->debug & 1 )
printf( "macParseDefns( %s )\n", defns );
/* allocate temporary pointer arrays; in worst case they need to have
as many entries as the length of the defns string */
ptr = ( char ** ) malloc( strlen( defns ) * sizeof( char * ) );
end = ( char ** ) malloc( strlen( defns ) * sizeof( char * ) );
del = ( long * ) malloc( strlen( defns ) * sizeof( long ) );
if ( ptr == NULL || end == NULL || del == NULL ) goto error;
/* go through definitions, noting pointers to starts and ends of macro
names and values; honor quotes and escapes; ignore white space
around assignment and separator characters */
num = 0;
del[0] = FALSE;
quote = 0;
state = preName;
for ( c = defns; *c != '\0'; c++ ) {
/* handle quotes */
if ( quote )
quote = ( *c == quote ) ? 0 : quote;
else if ( *c == '\'' || *c == '"' )
quote = *c;
/* handle escapes (pointer incremented below) */
escape = ( *c == '\\' && *( c + 1 ) != '\0' );
switch ( state ) {
case preName:
if ( !quote && !escape && ( isspace( *c ) || *c == ',' ) ) break;
ptr[num] = c;
state = inName;
/* fall through (may be empty name) */
case inName:
if ( quote || escape || ( *c != '=' && *c != ',' ) ) break;
end[num] = c;
while ( end[num] > ptr[num] && isspace( *( end[num] - 1 ) ) )
end[num]--;
num++;
del[num] = FALSE;
state = preValue;
if ( *c != ',' ) break;
del[num] = TRUE;
/* fall through (','; will delete) */
case preValue:
if ( !quote && !escape && isspace( *c ) ) break;
ptr[num] = c;
state = inValue;
/* fall through (may be empty value) */
case inValue:
if ( quote || escape || *c != ',' ) break;
end[num] = c;
while ( end[num] > ptr[num] && isspace( *( end[num] - 1 ) ) )
end[num]--;
num++;
del[num] = FALSE;
state = preName;
break;
}
/* if this was escape, increment pointer now (couldn't do
before because could have ignored escape at start of name
or value) */
if ( escape ) c++;
}
/* tidy up from state at end of string */
switch ( state ) {
case preName:
break;
case inName:
end[num] = c;
while ( end[num] > ptr[num] && isspace( *( end[num] - 1 ) ) )
end[num]--;
num++;
del[num] = TRUE;
case preValue:
ptr[num] = c;
case inValue:
end[num] = c;
while ( end[num] > ptr[num] && isspace( *( end[num] - 1 ) ) )
end[num]--;
num++;
del[num] = FALSE;
}
/* debug output */
if ( handle != NULL && handle->debug & 4 )
for ( i = 0; i < num; i += 2 )
printf( "[%d] %.*s = [%d] %.*s (%s) (%s)\n",
end[i+0] - ptr[i+0], end[i+0] - ptr[i+0], ptr[i+0],
end[i+1] - ptr[i+1], end[i+1] - ptr[i+1], ptr[i+1],
del[i+0] ? "del" : "nodel",
del[i+1] ? "del" : "nodel" );
/* calculate how much memory to allocate: pointers followed by
strings */
nbytes = ( num + 2 ) * sizeof( char * );
for ( i = 0; i < num; i++ )
nbytes += end[i] - ptr[i] + 1;
/* allocate memory and set returned pairs pointer */
memCp = malloc( nbytes );
if ( memCp == NULL ) goto error;
memCpp = ( char ** ) memCp;
*pairs = memCpp;
/* copy pointers and strings (memCpp accesses the pointer section
and memCp accesses the string section) */
memCp += ( num + 2 ) * sizeof( char * );
for ( i = 0; i < num; i++ ) {
/* if no '=' followed the name, macro will be deleted */
if ( del[i] )
*memCpp++ = NULL;
else
*memCpp++ = memCp;
/* copy value regardless of the above */
strncpy( memCp, ptr[i], end[i] - ptr[i] );
memCp += end[i] - ptr[i];
*memCp++ = '\0';
}
/* add two NULL pointers */
*memCpp++ = NULL;
*memCpp++ = NULL;
/* remove quotes and escapes from names in place (unlike values, they
will not be re-parsed) */
for ( p = *pairs; *p != NULL; p += 2 ) {
quote = 0;
for ( s = d = *p; *s != '\0'; s++ ) {
/* quotes are not copied */
if ( quote ) {
if ( *s == quote ) {
quote = 0;
continue;
}
}
else if ( *s == '\'' || *s == '"' ) {
quote = *s;
continue;
}
/* escapes are not copied but next character is */
if ( *s == '\\' && *( s + 1 ) != '\0' )
s++;
/* others are copied */
*d++ = *s;
}
/* need to terminate destination */
*d++ = '\0';
}
/* free workspace */
free( ptr );
free( end );
free( ( char * ) del );
/* debug output */
if ( handle->debug & 1 )
printf( "macParseDefns() -> %d\n", num / 2 );
/* success exit; return number of definitions */
return num / 2;
/* error exit */
error:
macErrMessage0( -1, "macParseDefns: failed to allocate memory" );
if ( ptr != NULL ) free( ptr );
if ( end != NULL ) free( end );
if ( del != NULL ) free( ( char * ) del );
*pairs = NULL;
return -1;
}
/*
* Install an array of name / value pairs as macro definitions. The
* array should have an even number of elements followed by at least
* one (preferably two) NULL pointers
*/
long /* #macros defined; <0 = ERROR */
macInstallMacros(
MAC_HANDLE *handle, /* opaque handle */
char *pairs[] ) /* pointer to NULL-terminated array of */
/* {name,value} pair strings; a NULL */
/* value implies undefined; a NULL */
/* argument implies no macros */
{
long n;
char **p;
/* debug output */
if ( handle->debug & 1 )
printf( "macInstallMacros( %s, %s, ... )\n",
pairs && pairs[0] ? pairs[0] : "NULL",
pairs && pairs[1] ? pairs[1] : "NULL" );
/* go through array defining macros */
for ( n = 0, p = pairs; p != NULL && p[0] != NULL; n++, p += 2 ) {
if ( macPutValue( handle, p[0], p[1] ) < 0 )
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macInstallMacros() -> %d\n", n );
/* return number of macros defined */
return n;
}
/* $Log$
* Revision 1.6 1996/06/26 09:43:19 wlupton
* first released version
*
*/

160
src/libCom/macLibNOTES Normal file
View File

@@ -0,0 +1,160 @@
# $Id$
#
# Test input file for macTest filter, doubling as notes on usage of the
# macro library. Some special strings at start of line are supported:
#
# 1. '#' indicates a comment and is ignored
#
# 2. '%set' is followed by "a=b, c=d" style macro definitions which are
# passed through macParseDefs() and macInstallMacros()
#
# 3. '%push' pushes a scoping level by calling macPushScope()
#
# 4. '%pop' pops a scoping level by calling macPopScope()
#
# 5. '%report' reports macro definitions by calling macReportMacros()
#
# 6. all other lines are expanded by callins macExpandString()
#
introduction
------------
See the README file for the library specification and see the header
comments of macCore.c (the core library) for notes on what has been
implemented.
This file contains tutorial information and examples. It's the
best documentation that there is.
simple examples
---------------
To define the a, b, c and d macros to be 1, 2, 3 and 4 (note optional
white space is ignored around '=' and ',' characters):
%set a=1, b=2, c = 3 , d = 4
These macros can be dereferenced using '$(xxx)' or '${xxx}' notation:
a = $(a), b = $(b), c = ${c}, d = ${d}.
Macro values can reference other macros in an arbitrarily complex way.
The only current restrictions are that a macro name or value cannot
exceed 256 characters in length. This restriction could fairly easily
be lifted.
Here's an example:
%set x = ${$(y$(z))}
If this is expanded now: $(x), it won't work because the other macros
aren't defined yet. So:
%set cash=lucre, ywork=cash, z=work
Now expansion yields "$(x)" (work -> ywork -> cash -> lucre).
One can inadvertently set up circular references. For example:
%set mac1=$(mac2), mac2=$(mac3), mac3=$(mac1)
An attempt to dereference mac1 gives $(mac1). When a macro expansion
fails, the translation that failed is replaced with the text that could
not be expanded.
You can get a report of current macro definitions by doing
%report
The '*' character in the first column indicates a problem expanding that
macro. We'll get rid of these problem macros:
%set mac1, mac2, mac3
You can also push a new scoping level by doing
%push
and pop back to it by doing
%pop
For example:
%set level = 0
%push
%set level = 1
%push
%set level = 2
%push
%set level = 3
%report
Level is $(level)
%pop
Level is $(level)
%pop
Level is $(level)
%pop
Level is $(level)
%pop
(That last error was deliberate)
quote and escape handling
-------------------------
Both single and double quotes are supported, as are escapes. Some of the
implications are quite subtle and some of the design choices made may not
meet with universal approval. The basic idea is that a string in which
macro references have been expanded should look like the source string
apart from the where macros have been expanded. This implies that quote
and escape characters should not be removed from the string.
Single and double quotes are different in that (as in most shells),
macros are substituted within double quotes but not within single
quotes. Back quotes are not special characters. Missing quotes at the
end of a string are automatically and quietly supplied.
We've already seen some examples but what happens here: $(x)?
Should not that have been expanded? Why wasn't it? The answer is given
later on. As a clue, this is: $(x). This isn't: $(x)!
Characters may be escaped by preceding them with a \ (back slash).
Escapes work even within quotes, which is more like C than most shells.
Thus, '\'' works, and it doesn't with the C shell.
Quotes and escapes can also be used in "a=b, c=d" style assignments
but they are not part of macro names. For example:
%set \= = equal, \' = quote
defines macros called "=" and "'", not "\=" and "\'". To reference
these macros, "$(=)" ('$(=)') works because "=" is not special in
this context. However the quote must be escaped because quotes are
always special, as in "$(\')" ('$(\')').
miscellaneous notes
-------------------
In the "a=b, c=d" strings that are parsed by macParseDefns(), a macro
can be set to an empty value by putting nothing after the equals
sign, as in
%set empty =
resulting in empty = "$(empty)". Omitting the equal sign gives the
macro a NULL rather than empty value (with the result that the macro
is deleted since this is how macPutValue() interprets a NULL value), so
%set empty
deletes the empty macro, as can be seen when we try to de-reference
it: $(empty).
The only special characters in these "a=b, c=d" strings are "=", ","
and white space surrounding these characters (plus quotes and escapes
of course). This means that
%set fred 2
actually sets a macro called "fred 2" to a NULL value (i.e. it deletes
it if it existed). This is probably wrong and the space should be
taken as an implicit '='. However, to do this and still to support
ignored white space around '=' and ',' characters is a bigger change
than I am prepared to make at present.
What was the problem expanding "$(x)" before? It was single quotes
from words like "wasn't"! Is this a problem or a feature?

288
src/libCom/macLibREADME Normal file
View File

@@ -0,0 +1,288 @@
README file for etc/macLib directory
------------------------------------
This directory contains all files necessary to build and test the
EPICS macro library macLib. The specification for this library
appears later in this file.
Files in this release are:
-rw-r--r-- 1 wlupton 141 Sep 22 1995 Makefile
-rw-r--r-- 1 wlupton 522 Jun 23 20:42 Makefile.Unix
-rw-r--r-- 1 wlupton 473 Jun 23 20:43 Makefile.Vx
-rw-r--r-- 1 wlupton 5052 Jun 25 23:17 NOTES
-rw-r--r-- 1 wlupton 10429 Jun 25 23:34 README
-rw-r--r-- 1 wlupton 20158 Jun 25 23:40 macCore.c
-rw-r--r-- 1 wlupton 5056 Jun 25 21:33 macLib.h
-rw-r--r-- 1 wlupton 2050 Jun 25 23:18 macTest.c
-rw-r--r-- 1 wlupton 7307 Jun 25 23:13 macUtil.c
25-Jun-96, William Lupton (wlupton@keck.hawaii.edu), CVS tag: macLib-1-0
------------------------------------------------------------------------
first release
------------------------------------------------------------------------
This is the original final specification for the EPICS macro library:
From wlupton Mon Feb 19 14:10:08 1996
To: tech-talk@aps.anl.gov
Subject: Re: macro substitution
Content-Length: 9716
X-Lines: 251
Status: RO
Dear all,
Here's yet another proposal for the macro library. There has been some
private discussion between myself, Jim and Marty, and I hope that this
is nearly final (or final enough to be implemented). After all the
discussion, the actual implementation is going to be a breeze!
The biggest changes are a move a way from the "filesystem-like"
routine names and a reduction in the emphasis placed on the standalone
tool.
I'm afraid there are still a few minor issues to be resolved but
probably nothing that can't wait for an alpha release.
William
------------------------------------------------------------------------
1. Macro substitution library
-----------------------------
This library could be used directly by applications which need to
support macro substitution. It will be implemented on all platforms.
1.1 Core library
----------------
The core library provides a minimal set of basic operations. Some
utility routines, described later, use core routines to provide a more
convenient interface for some purposes.
a) long macCreateHandle( MAC_HANDLE **handle, char *pairs[] );
Creates a new macro substitution context and returns an opaque handle
to that context. An application can, if it desires, have several
active contexts, although most will not.
If desired, an initial set of macro definitions may be loaded
("pairs" is set to NULL to avoid this). The definitions are in the
"standard" pairs format, as described under macParseDefns().
Note that MAC_HANDLE is a typedef for the context structure. The
opaque handle is of type "MAC_HANDLE *", which is a pointer to the
context structure. The memory for this context is allocated by this
routine.
b) long macGetXxxx( MAC_HANDLE *handle, <Xxxx-specific> );
long macPutXxxx( MAC_HANDLE *handle, <Xxxx-specific> );
These routines replace the earlier-proposed macIoctl(). "Xxxx" is the
name of the attribute in question. An initial list (which can grow)
might be:
Attribute default value
--------- -------------
ErrRoutine errPrintf
Verbose TRUE
StartChars "$"
LeftChars "{("
RightChars "})"
SeparChars ","
AssignChars "="
A NULL handle can be used to set or get global values (used by
macParseDefns()).
c) long macGetValue ( MAC_HANDLE *handle, char *name,
char *value, long maxlen );
Returns up to maxlen characters of the value of macro "name". "value"
will be zero-terminated if the length of the value is less than
maxlen. The function value will be the number of characters copied to
"value" (see below for behavior if the macro is undefined). If maxlen
is zero, no characters will be copied to "value" (which can be NULL)
and the call can be used to check whether the macro is defined. Note
that truncation of the value is therefore not reported. Is this a
problem?
If the macro is undefined, the macro reference will be returned in
the value string (if permitted by maxlen) and the function value will
be _minus_ the number of characters copied. Note that treatment of
maxlen is intended to be consistent with what people are used to with
strncpy().
"name" can either be just the macro name or can have the syntax of a
macro reference (e.g. it can be "fred" or "$(fred)"). In the latter
case, any characters after the logical end of the macro reference
will be ignored (this is to ease expansion of a string which may
contain many macro references), but no means of returning the number
of characters scanned in "name" is provided. [It may prove expedient
to make macGetValue() simpler and provide a separate routine with
more parameters which returns this information.]
If the name or value contains a macro reference then the reference
will be expanded recursively. This expansion will detect a direct or
indirect self reference.
Macro references begin with a "$" immediately followed by either a
"(" or a "{" character. The reference is terminated by the matching
")" or "}" character. These characters can be changed via
macPut{Start,Left,Right}Chars() above.
d) long macPutValue( MAC_HANDLE *handle, char *name, char *value );
Sets the value of the macro "name". If "value" is NULL, undefines
"name" (it's not an error if "name" was already undefined). Macros
referenced by "value" need not be defined at this point.
The function value is the length of the value.
e) long macDeleteHandle( MAC_HANDLE *handle );
Marks a handle invalid, and frees all storage associated with it.
Note that this does not free any strings into which macro values have
been returned. Macro values are always returned into strings which
were pre-allocated by the caller.
f) long macPushScope( MAC_HANDLE *handle );
Marks the start of a new scoping level such that all definitions made
up until the next macPopScope() call will be lost on macPopScope()
and those current at macPushScope() will be re-instated.
May not be implemented first time round.
g) long macPopScope( MAC_HANDLE *handle );
See above.
h) Error handling
These routines conform to 0 (=OK) for success, -1 (=ERROR) for
failure, and small positive values for extra info. I contravened this
for macGetValue() and macExpandString() because I felt that it was
worth returning information both on success / failure and on value
length.
Errors are reported using an errPrintf()-compatible routine.
A "verbose" variable will control error reporting.
1.2 Utility library
-------------------
These are convenience functions. If other useful functions emerge during
implementation, the list may grow.
a) macParseDefns( char *defns, char **pairs[] );
This takes a set of macro definitions in "a=xxx,b=yyy" format and
converts them into an array of pointers to character strings which
are, in order, "first name", "first value", "second name", "second
value" etc. The array is terminated with two NULL pointers and all
storage is allocated contiguously so that it can be freed with a
single call to free().
This routine is independent of any handle and provides a generally
useful service which may be used elsewhere. Any macro references in
values are not expanded at this point since the referenced macros may
be defined or redefined before the macro actually has to be
translated.
Shell-style escapes and quotes are supported, as are things like
"A=B,B=$(C$(A)),CA=CA,CB=CB" (sets B to "CB"). White space is
significant within values but ignored elsewhere (i.e. surrounding "="
and "," characters).
Probably noone will ever want to, but the special meanings of "$",
"{", "}", "(", ")", "=" and "," can all be changed via macPutXxxx()
calls. This routine does not have a handle argument, so they must be
changed globally for it to use the new definitions. Should it have a
handle argument? This makes it more of a pain to use but guarantees
that there will be no conflicts. I think it should really.
The function value is the number of definitions encountered, or -1 if
the supplied string is invalid.
b) long macInstallMacros( MAC_HANDLE *handle, char *pairs[] );
This takes an array of pairs as defined above and installs them as
definitions by calling macPutValue(). The pairs array is terminated
by a NULL pointer.
The function value is the number of macros defined.
c) long macExpandString( MAC_HANDLE *handle, char *src,
char *dest, long maxlen );
This operates on a string which may contain macro references. It
parses the string looking for such references and passes them to
macGetValue() for translation. It returns the expanded string in the
supplied argument.
The function value is similar to that of macGetValue(). Its absolute
value is the number of characters copied. If negative, at least one
undefined macro has been left unexpanded.
d) long macReportMacros( MAC_HANDLE *handle );
This reports details of current definitions to standard output (or
standard error?). It's purely for debugging purposes.
2. Macro substitution tool
--------------------------
This section is hardly changed from the previous version. The library will
be implemented first.
A "macsub" (or "mactool"?) application will be written which would use the
above library and use a command like:
macsub a=b,c=d e=f -Iaaa -Ibbb
to parse a file like:
# comment
%include filename
%xxx = ppp
%yyy = $(xxx)
<arbitrary text containing macro references>
There will also be options to read and write specified files, control the
comment character, control the command prefix, change the macro substitution
characters etc.
Syntax will be a loose subset of corresponding shell syntax.
The use of a command prefix should be optional and perhaps it should default
to an empty string. However there will be cases where the file in question
has a syntax over which we have no control, which may include assignments,
"set" commands, "define" commands etc. This is why I proposed a command
prefix, which might be an arbitrary string such as "macro " or might just
be "%" as in the above example.
The tool can also support the following syntax (or perhaps some equivalent
syntax which will sit more easily inside a file that may have a completely
different syntax from it?):
file gizmo_database.db
{
instance1{ pre=part1,parm=big, alarm_limit=20 }
instance2{ pre=part2,parm=medium,alarm_limit=30 }
}
as an implied multiple include of "gizmo_database.db" with the specified
macro definitions for each include (here's a good application for
macPushScope() and macPopScope() actually).

275
src/libCom/macUtil.c Normal file
View File

@@ -0,0 +1,275 @@
/* $Id$
*
* Implementation of utility macro substitution library (macLib)
*
* William Lupton, W. M. Keck Observatory
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "macLib.h"
/*
* Parse macros definitions in "a=xxx,b=yyy" format and convert them to
* a contiguously allocated array pointers to names and values, and the
* name and value strings, terminated with two NULL pointers. Quotes
* and escapes are honored but only removed from macro names (not
* values)
*
* Doesn't yet use handle (so uses default special characters)
*/
long /* #defns encountered; <0 = ERROR */
macParseDefns(
MAC_HANDLE *handle, /* opaque handle; can be NULL if default */
/* special characters are to be used */
char *defns, /* macro definitions in "a=xxx,b=yyy" */
/* format */
char **pairs[] ) /* address of variable to receive pointer */
/* to NULL-terminated array of {name, */
/* value} pair strings; all storage is */
/* allocated contiguously */
{
long i;
long num;
long quote;
long escape;
long nbytes;
char **ptr;
char **end;
long *del;
char *memCp, **memCpp;
char *c, *s, *d, **p;
enum { preName, inName, preValue, inValue } state;
/* debug output */
if ( handle->debug & 1 )
printf( "macParseDefns( %s )\n", defns );
/* allocate temporary pointer arrays; in worst case they need to have
as many entries as the length of the defns string */
ptr = ( char ** ) malloc( strlen( defns ) * sizeof( char * ) );
end = ( char ** ) malloc( strlen( defns ) * sizeof( char * ) );
del = ( long * ) malloc( strlen( defns ) * sizeof( long ) );
if ( ptr == NULL || end == NULL || del == NULL ) goto error;
/* go through definitions, noting pointers to starts and ends of macro
names and values; honor quotes and escapes; ignore white space
around assignment and separator characters */
num = 0;
del[0] = FALSE;
quote = 0;
state = preName;
for ( c = defns; *c != '\0'; c++ ) {
/* handle quotes */
if ( quote )
quote = ( *c == quote ) ? 0 : quote;
else if ( *c == '\'' || *c == '"' )
quote = *c;
/* handle escapes (pointer incremented below) */
escape = ( *c == '\\' && *( c + 1 ) != '\0' );
switch ( state ) {
case preName:
if ( !quote && !escape && ( isspace( *c ) || *c == ',' ) ) break;
ptr[num] = c;
state = inName;
/* fall through (may be empty name) */
case inName:
if ( quote || escape || ( *c != '=' && *c != ',' ) ) break;
end[num] = c;
while ( end[num] > ptr[num] && isspace( *( end[num] - 1 ) ) )
end[num]--;
num++;
del[num] = FALSE;
state = preValue;
if ( *c != ',' ) break;
del[num] = TRUE;
/* fall through (','; will delete) */
case preValue:
if ( !quote && !escape && isspace( *c ) ) break;
ptr[num] = c;
state = inValue;
/* fall through (may be empty value) */
case inValue:
if ( quote || escape || *c != ',' ) break;
end[num] = c;
while ( end[num] > ptr[num] && isspace( *( end[num] - 1 ) ) )
end[num]--;
num++;
del[num] = FALSE;
state = preName;
break;
}
/* if this was escape, increment pointer now (couldn't do
before because could have ignored escape at start of name
or value) */
if ( escape ) c++;
}
/* tidy up from state at end of string */
switch ( state ) {
case preName:
break;
case inName:
end[num] = c;
while ( end[num] > ptr[num] && isspace( *( end[num] - 1 ) ) )
end[num]--;
num++;
del[num] = TRUE;
case preValue:
ptr[num] = c;
case inValue:
end[num] = c;
while ( end[num] > ptr[num] && isspace( *( end[num] - 1 ) ) )
end[num]--;
num++;
del[num] = FALSE;
}
/* debug output */
if ( handle != NULL && handle->debug & 4 )
for ( i = 0; i < num; i += 2 )
printf( "[%d] %.*s = [%d] %.*s (%s) (%s)\n",
end[i+0] - ptr[i+0], end[i+0] - ptr[i+0], ptr[i+0],
end[i+1] - ptr[i+1], end[i+1] - ptr[i+1], ptr[i+1],
del[i+0] ? "del" : "nodel",
del[i+1] ? "del" : "nodel" );
/* calculate how much memory to allocate: pointers followed by
strings */
nbytes = ( num + 2 ) * sizeof( char * );
for ( i = 0; i < num; i++ )
nbytes += end[i] - ptr[i] + 1;
/* allocate memory and set returned pairs pointer */
memCp = malloc( nbytes );
if ( memCp == NULL ) goto error;
memCpp = ( char ** ) memCp;
*pairs = memCpp;
/* copy pointers and strings (memCpp accesses the pointer section
and memCp accesses the string section) */
memCp += ( num + 2 ) * sizeof( char * );
for ( i = 0; i < num; i++ ) {
/* if no '=' followed the name, macro will be deleted */
if ( del[i] )
*memCpp++ = NULL;
else
*memCpp++ = memCp;
/* copy value regardless of the above */
strncpy( memCp, ptr[i], end[i] - ptr[i] );
memCp += end[i] - ptr[i];
*memCp++ = '\0';
}
/* add two NULL pointers */
*memCpp++ = NULL;
*memCpp++ = NULL;
/* remove quotes and escapes from names in place (unlike values, they
will not be re-parsed) */
for ( p = *pairs; *p != NULL; p += 2 ) {
quote = 0;
for ( s = d = *p; *s != '\0'; s++ ) {
/* quotes are not copied */
if ( quote ) {
if ( *s == quote ) {
quote = 0;
continue;
}
}
else if ( *s == '\'' || *s == '"' ) {
quote = *s;
continue;
}
/* escapes are not copied but next character is */
if ( *s == '\\' && *( s + 1 ) != '\0' )
s++;
/* others are copied */
*d++ = *s;
}
/* need to terminate destination */
*d++ = '\0';
}
/* free workspace */
free( ptr );
free( end );
free( ( char * ) del );
/* debug output */
if ( handle->debug & 1 )
printf( "macParseDefns() -> %d\n", num / 2 );
/* success exit; return number of definitions */
return num / 2;
/* error exit */
error:
macErrMessage0( -1, "macParseDefns: failed to allocate memory" );
if ( ptr != NULL ) free( ptr );
if ( end != NULL ) free( end );
if ( del != NULL ) free( ( char * ) del );
*pairs = NULL;
return -1;
}
/*
* Install an array of name / value pairs as macro definitions. The
* array should have an even number of elements followed by at least
* one (preferably two) NULL pointers
*/
long /* #macros defined; <0 = ERROR */
macInstallMacros(
MAC_HANDLE *handle, /* opaque handle */
char *pairs[] ) /* pointer to NULL-terminated array of */
/* {name,value} pair strings; a NULL */
/* value implies undefined; a NULL */
/* argument implies no macros */
{
long n;
char **p;
/* debug output */
if ( handle->debug & 1 )
printf( "macInstallMacros( %s, %s, ... )\n",
pairs && pairs[0] ? pairs[0] : "NULL",
pairs && pairs[1] ? pairs[1] : "NULL" );
/* go through array defining macros */
for ( n = 0, p = pairs; p != NULL && p[0] != NULL; n++, p += 2 ) {
if ( macPutValue( handle, p[0], p[1] ) < 0 )
return -1;
}
/* debug output */
if ( handle->debug & 1 )
printf( "macInstallMacros() -> %d\n", n );
/* return number of macros defined */
return n;
}
/* $Log$
* Revision 1.6 1996/06/26 09:43:19 wlupton
* first released version
*
*/