/* * Copyright 1995, 1996 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ /* * specparse.cc - tokenizer for ascii 'specifications' * * This is a scanner whose job it is to return elements from a * change/client/branch/label/etc form. It doesn't recognize any * of the keywords, but uses indentation, punctuation (:), and * newlines to tokenize. The scanner is embodied in a simple state * transition table. * * This scanner returns one token for each call. If it isn't an * error or EOS (end of string), the value is returned in the provided * StrBuf buffer. * * If isTextBlock is set, the scanner works a little differently: * instead of returning individual lines, it returns all lines as * one big clump. In this case #comments are ignored, unless they * begin the line. * * This scanner accepts the following input: * * # comments at the beginning of lines are always stripped * * Tag1: word ... * * Tag2: * word ... #ending comment is stripped * word ... #comment is stripped * * Tag3: * Text block spanning * multiple lines * # left comment is a comment * #indented comment is part of text block */ # include <stdhdrs.h> # include <error.h> # include <strbuf.h> # include <debug.h> # include "specchar.h" # include <msgdb.h> # include "specparse.h" # define DEBUG_PARSE ( p4debug.GetLevel( DT_SPEC ) >= 5 ) enum SpecParseState { sS, // START - no input sT, // TAG - in tag, seeking : stU, // TAGV - after :, skipping whitespace stV, // VAL - in value, seeking newline stX, // LOOK - is this indent or tag? stY, // IND - in indent, seeking value sQ, // QUOTE - in quote, seeking endquote sR, // QUOTE2 - 2nd+ line of quote, seeking endquote sbU, // TAGV - looking for start of text block sbV, // VAL - in text block, seeking newline sbX, // LOOK - is this indent or tag? sbY // IND - in indent, seeking text block } ; enum SpecParseActions { a0, // return EOS (no advance) aA, // advance past char in input aB, // advance past blank in input aC, // comment - advance until past EOL aD, // return value (no advance) aE, // return error (no advance) aG, // return error "no endquote" aN, // advance & count newline aR, // advance, save start aQ, // save end of line aS, // save start, advance aT, // return tag, advance aV, // return value at newline, advance until past EOL aW, // append line to textBlock aX // append line & newline to textBlock } ; static const struct transition { SpecParseState state; SpecParseActions act; } trans[12][7] = { /* cSPACE cNL cCOLON cPOUND cQUOTE cMISC cEOS */ /*------------------------------------------------------------------ */ /* S START */ { sS,aA, sS,aA, sS,aE, sS,aC, sT,aS, sT,aS, sS,a0 }, /* T TAG */ { sS,aE, sS,aE, stU,aT, sS,aE, sT,aA, sT,aA, sS,aE }, /* tU TAGV */ { stU,aA, stX,aA, stV,aS, stU,aC, sQ,aS, stV,aS, sS,aE }, /* tV VAL */ { stV,aB, stX,aV, stV,aA, stX,aV, sQ,aA, stV,aA, stX,aV }, /* tX LOOK */ { stY,aR, stX,aN, sS,aE, stX,aC, sS,aD, sS,aD, sS,aD }, /* tY IND */ { stY,aR, stX,aN, stV,aA, stX,aC, sQ,aA, stV,aA, sS,aE }, /* Q QUOTE */ { sQ,aA, sR,aQ, sQ,aA, sQ,aA, stV,aA, sQ,aA, sR,aQ }, /* R QUOTE2*/ { sR,aA, sR,aA, sR,aA, sR,aA, stV,aA, sR,aA, sQ,aG }, /* bU TAGV */ { sbU,aA, sbX,aA, sbV,aS, sbV,aS, sbV,aS, sbV,aS, sS,aE }, /* bV VAL */ { sbV,aA, sbX,aW, sbV,aA, sbV,aA, sbV,aA, sbV,aA, sbX,aX }, /* bX LOOK */ { sbY,aR, sbX,aN, sS,aE, sbX,aC, sS,aD, sS,aD, sS,aD }, /* bY IND */ { sbY,aA, sbX,aN, sbV,aA, sbV,aA, sbV,aA, sbV,aA, sS,aE }, }; const char *const stateNames[] = { "START", "TAG", "tTAGV", "tVAL", "tLOOK", "tIND", "QUOTE", "QUOTE2", "bTAGV", "bVAL", "bLOOK", "bIND", }; const char *const actNames[] = { "EOS", "advance", "advance blank", "comment", "done values", "error", "no endquote", "add newline", "save start after advance", "save eol", "save start", "return tag", "return value", "append line", "append line + nl", }; SpecParse::SpecParse( const char *buffer ) { state = sS; c.Set( buffer ); } SpecParseReturn SpecParse::GetToken( int isTextBlock, StrBuf *value, Error *e ) { const char *start = c.p, *end = c.p; const char *eol = 0; addNewLine = 0; if( isTextBlock ) { value->Set( "",0 ); savedBlankLines = 0; } for(;;) { // Start of ':', comments are single line if( state == stU ) ++addNewLine; // For text blocks, skip into block mode if( isTextBlock && state == stU ) state = sbU; // Do state transition. const struct transition *t = &trans[ state ][ c.cc ]; if( DEBUG_PARSE ) p4debug.printf( "x[%s][%s] -> %s\n", stateNames[ state ], c.CharName(), actNames[ t->act ] ); state = t->state; // Do state table's action. switch( t->act ) { case a0: // EOS return SR_EOS; case aA: // advance c.Advance(); end = c.p; break; case aB: // advance blank c.Advance(); break; case aC: // comment if( c.cc == cEOS || c.cc == cNL ) break; c.Advance(); if( c.cc == cPOUND ) { while( c.cc != cEOS && c.cc != cNL ) c.Advance(); end = c.p; value->Set( start, end - start ); return addNewLine ? SR_COMMENT_NL : SR_COMMENT; } while( c.cc != cEOS && c.cc != cNL ) c.Advance(); break; case aD: // done with values return isTextBlock ? SR_VALUE : SR_DONEV; case aE: // error value->Set( start, end - start ); e->Set( MsgDb::Syntax ) << *value; return SR_EOS; case aG: // no endquote value->Set( start, eol - start ); e->Set( MsgDb::NoEndQuote ) << *value; return SR_EOS; case aN: // add newline c.Advance(); ++addNewLine; if( isTextBlock ) ++savedBlankLines; continue; case aQ: // save end of line eol = c.p; break; case aR: // save start after advance c.Advance(); start = end = c.p; break; case aS: // save start start = c.p; c.Advance(); end = c.p; break; case aT: // return tag value->Set( start, end - start ); c.Advance(); return SR_TAG; case aV: // return value value->Set( start, end - start ); return SR_VALUE; case aW: // append line to textBlock // Use c.p to include whitespace. c.Advance(); for( ; savedBlankLines; --savedBlankLines ) value->Append( "\n", 1 ); value->Append( start, c.p - start ); continue; case aX: // append line and newline to textBlock for( ; savedBlankLines; --savedBlankLines ) value->Append( "\n", 1 ); value->Append( start, c.p - start ); value->Append( "\n", 1 ); continue; } } } void SpecParse::ErrorLine( Error *e ) { e->Set( MsgDb::LineNo ) << c.line; }