typedef struct { char *Text; long Length; } t_String; #define STRING_MAKE_COPY 001 #define STRING_HAS_NUL 002 #define STRING_WAS_READ_ONLY 004 #define STRING_NEW_LEN (STRING_MAKE_COPY|STRING_HAS_NUL) #define STRING_NEW_NUL (STRING_MAKE_COPY) INLINE t_String * NewString(base, length, flags) char *base; long length; int flags; { t_String *Result = emalloc(base, length + 1); /* see if we need to copy the string: */ if (!(flags & STRING_HAS_NUL) || (flags & STRING_WAS_READ_ONLY)) { flags |= STRING_MAKE_COPY; } if (flags & STRING_MAKE_COPY) { if (flags & STRING_HAS_NUL) { Result->Text = emalloc(base, length); } else { Result->Text = emalloc(base, length + 1); Result->Text[length] = '\0'; } } else { Result->Text = base; } Result->Length = length; return Result; } static char * ProcessGlueString(base, length, lengthp) char *base; long length; long *lengthp; { char *result; register char *p, *q; result = emalloc(base, length); for (p = base, q = result; p - base < length; p++) { if (*p == '\\') { ++p; if (p - base >= length) { /* silently delete a trailing backslash, is this OK? */ Error(E_FATAL|E_MULTILINE, "Glue string has a trailing \\, use \\\\ for that!" ); Error(E_FATAL|E_MULTILINE|E_LASTLINE, "String fragment was [%*.*s]", length, length, base ); /*NOTREACHED*/ break; } switch (*p) { /* special characters: */ case 'n': *q++ = '\n'; break; case 'e': *q++ = '\037'; break; /* escape */ case 't': *q++ = '\t'; break; case 'b': *q++ = '\b'; break; /* things that could reasonably be quoted: */ case '$': case '[': case ']': case '"': case '\'': case '/': case '\\': case '{': case '}': *q++ = *p; break; /* octal escapes: */ case '0': { char value = 0; int digits = 0; do { p++; if (p - base >= length) { break; } if (!isascii(*p) || !isdigit(*p)) { p--; /* gone too far */ break; } digits *= 8; digits += (*p - '0'); } while (digits <= 3); *q = value; break; } /* case '0' */ default: Error(E_FATAL|E_MULTILINE, "Unknown escape sequence \\%c in glue fragment", *p ); Error(E_FATAL|E_MULTILINE|E_LASTLINE, "String fragment was [%*.*s]", length, length, base ); } } else { /* not a \\ */ *q++ = *p; } } *q = '\0'; *lengthp = q - result; return result; } typedef enum { GT_Error, GT_ConstantString, GT_ConstantInteger, GT_Variable, GT_CompiledString, GT_End } t_GluonType; struct s_CompiledString; /* forward declaration */ typedef struct s_GluonItem { t_GluonType GluonType; union { long Long; t_String String; t_NameRef NameRef; struct s_CompiledString *CompiledString; } Value; struct s_GluonItem *Next; } t_GluonItem; typedef struct s_CompiledString { t_NameSpace *NameSpace; t_GluonItem *Items; } t_CompiledString; /* a simple Glue expression */ t_GluonItem * NewGluon(Type, Value) t_GluonType Type; void *Value; { t_GluonItem *Result = (t_GluonItem *) emalloc( "new gluon", sizeof(t_GluonItem) ); Result->Type = Type; switch (Type) { case GT_Error: Error(E_FATAL|E_INTERNAL, "Error compiling string: NewGluon(%d) illegal", (int) Type ); case GT_ConstantString: Result->Value.String = (t_String) Value; break; case GT_ConstantInteger: Result->Value.Long = (long) Value; break; case GT_Variable: Result->Value.NameRef = (t_NameRef) Value; break; case GT_CompiledString: Result->Value.CompiledString = (t_CompiledString *) Value; case GT_End: Result->Value.Long = 0L; break; default: Error(E_FATAL|E_INTERNAL, "%s: %d: illegal type value %d; may need recompiling!", __FILE__, __LINE__, Type ); } return Result; } /* Example: * the string * [The boy ${boy.name} had ${if boy.shoes then "shod" else "bare"} feet] * becomes * { * constantText: "The boy " * Glue: * variableReference: * NameSpace: boy * Name: "name" * } -> { * constantText " had " * Glue: * conditionalExpression * if: * NameSpace: boy * variableReference: * NameSpace: boy * Name: "shoes" * then: * constantString: "shod" * else: * constantString: "bare" * } -> { * constantString " feet" * Glue: NULL * Next: NULL * } * * In this example, the NameSpace passed to LQG_compileString() must * contain the Namespace "boy", or the namespace "boy" must be global, * or you'll get an error. * * The variableReference will actually be stored in the parsed Glue * as a NameRef, so there will literally be a pointer to a value there; * at runtime, there is no need to deal with the vairbale names. * * The Glue compiler also does constant folding, so if "boy" is readonly, * or if his feet are readonly, or are represented by constant functions, * the if statement should be folded out at compile time. * * The result is that a Glue string can be handled at runtime at least * as quickly as a corresponding call to printf(), and usually faster. * [TODO: measure that! That's a design goal, not a result, yet...] */ #define GLUESTR_VOLATILE 01 API t_CompiledString LQG_compileString(theString, theLength, NS, flags) char *String; int theLength; t_NameSpace NS; int flags; { register char *p; char *Start; t_CompiledString *Result = 0; t_GluonItem **ThisItemp; Result = (t_CompiledString *) emalloc( "compiled string", sizeof(t_CompiledString) ); Result->NameSpace = NS; Result->Items = (t_GluonItem *) NULL; ThisItemp = &(Result->Items); for (Start = p = theString; p - theString < theLength; p++) { if (*p == '$') { /* $ can be followed by: * [ for an embedded glue string; * $ for a literal dollar sign (same as \$) * { for a glue expression */ p++; if (p - theString >= theLength) { Error(E_FATAL|E_MULTILINE, "string ends in $; use \\$ or $$ to embed a \"$\"-sign." ); Error(E_FATAL|E_MULTILINE|E_LASTLINE, "string was: %*.*s", theLength, theLength, theString ); } if (p > Start) { /* make a constant gluon */ t_String S; /* copy leading fragment, handling \ as we go: */ if (NeedStringProcessing || (flags & GLUESTR_VOLATILE)) { if (NeedStringProcessing) { S.Text = ProcessGlueString( Start, p - Start, &S.Length ); } else { S.Text = emalloc(base, (p - Start)); /* bcopy() allows embedded NULs: */ (void) bcopy(Start, s.Text, p - Start); s.Text[p - Start] = '\0'; S.Length = p - Start; } } else { S.Text = base; S.Length = p - Start; } /* NewGluon will copy the String S... * It would be more efficient to do that in place. * In fact..... * *ThisItemp = NewGluon(GT_ConstantString, S); */ *ThisItemp = (t_GluonItem *) emalloc( "new gluon string", sizeof(t_GluonItem) ); (*ThisItemp)->Value.String.Text = S.Text; (*ThisItemp)->Value.String.Length = S.Length; (*ThisItemp)->Type = GT_ConstantString; /* end of inline function */ ThisItemp = &(*ThisItemp)->Next; *ThisItemp = (t_GluonItem *) NULL; } /* wow! Now the constant prefix is done. */ /* parse the Glue and store the resulting optimised tree */ { t_GlueSyntaxError *theMistake; t_Glue *Fragment; theMistake = parseGlue(&p[1], &Fragment); if (theMistake) { error... } add Fragment to result... /* look for some more string.... */ Start = &p[1]; } } TODO: return TODO: add SGML documentation at start of function } parseGlue(string) char *string; { char *p = string; switch ((token = getGlueToken(&p)) { case GlueToken_End: return Result; case GlueToken_If: Handle if; case GlueToken_Name: HandleName default: error! } }