/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.template; import gw.internal.gosu.parser.SourceCodeTokenizer; import gw.lang.parser.ISourceCodeTokenizer; import gw.lang.parser.IToken; import gw.lang.parser.ITokenizerInstructor; import gw.lang.parser.SourceCodeReader; import gw.util.Stack; /** * An ITokenizerInstructor for Gosu templates. Implemented as a finite state * machine where states correspond to template directives. */ public class TemplateTokenizerInstructor implements ITokenizerInstructor { static final int IGNORE = 0; static final int IGNORE_COMMENT = 50; static final int COMMENT_END_PENDING = 75; static final int COMMENT_END_PENDING2 = 76; static final int ANALYZE_START_PENDING = 100; static final int ANALYZE_PENDING = 200; static final int ANALYZE_SEPARATELY_PENDING = 300; static final int ANALYZE_DIRECTIVE_PENDING = 350; static final int ANALYZE = 400; static final int ANALYZE_SEPARATELY = 500; static final int ANALYZE_DIRECTIVE = 550; static final int ANALYZE_END_PENDING = 600; static final int ANALYZE_SEPARATELY_END_PENDING = 700; static final int ANALYZE_DIRECTIVE_END_PENDING = 750; static final String[] DELIMITERS = { TemplateGenerator.SCRIPTLET_BEGIN, TemplateGenerator.SCRIPTLET_BEGIN + TemplateGenerator.EXPRESSION_SUFFIX, TemplateGenerator.SCRIPTLET_BEGIN + TemplateGenerator.DIRECTIVE_SUFFIX, TemplateGenerator.SCRIPTLET_BEGIN + TemplateGenerator.DECLARATION_SUFFIX, TemplateGenerator.SCRIPTLET_END, TemplateGenerator.ALTERNATE_EXPRESSION_BEGIN, TemplateGenerator.ALTERNATE_EXPRESSION_END, TemplateGenerator.COMMENT_BEGIN, TemplateGenerator.COMMENT_END, }; ISourceCodeTokenizer _tokenizer; int _iState; boolean _bAltTag; boolean _bTag; boolean _bEndTagPending; boolean _bStartTagBuffer; int _iLines; StringBuffer _sbStartTag; char _lastC; public TemplateTokenizerInstructor(ISourceCodeTokenizer tokenizer) { _tokenizer = tokenizer; _iState = IGNORE; _sbStartTag = new StringBuffer(); } /** * Reset state */ public void reset() { _iState = IGNORE; _sbStartTag = new StringBuffer(); _bTag = false; _bEndTagPending = false; _bStartTagBuffer = false; _iLines = 0; _bAltTag = false; } public ITokenizerInstructor createNewInstance(ISourceCodeTokenizer tokenizer) { TemplateTokenizerInstructor copy = new TemplateTokenizerInstructor( tokenizer ); return copy; } @Override public boolean isAtIgnoredPos() { boolean bStartPending = true; switch( getState() ) { case IGNORE: case IGNORE_COMMENT: return true; case COMMENT_END_PENDING: case COMMENT_END_PENDING2: case ANALYZE_END_PENDING: case ANALYZE_SEPARATELY_END_PENDING: case ANALYZE_DIRECTIVE_END_PENDING: bStartPending = false; // fall through... case ANALYZE_START_PENDING: case ANALYZE_PENDING: case ANALYZE_SEPARATELY_PENDING: case ANALYZE_DIRECTIVE_PENDING: { SourceCodeReader reader = _tokenizer.getReader(); String strSource = _tokenizer.getSource(); for( String delim : DELIMITERS ) { int iLen = delim.length(); for( int i = 0; i < iLen; i++ ) { int iOffset = reader.getPosition() - i - 1; if( strSource.length()-i >= iLen ) { if( reader.getSource().startsWith( delim, iOffset ) ) { return true; } } } } // The pending state will ultimately revert back to either ignore or analyze, // thus if we're currently in an End Pending state we'll continue to analyze, // otherwise, if we're in a Start Pending state we'll continue to ignore. return bStartPending; } } return false; } @Override public boolean isAnalyzingDirective() { switch( getState() ) { case ANALYZE_DIRECTIVE: case ANALYZE_DIRECTIVE_END_PENDING: return true; } return false; } @Override public boolean isAnalyzingSeparately() { switch( getState() ) { case ANALYZE_SEPARATELY: case ANALYZE_SEPARATELY_END_PENDING: return true; } return false; } /** */ public void getInstructionFor( int iC ) { char c = (char)iC; char lastC = _lastC; _lastC = c; countLines( c ); switch( getState() ) { case IGNORE: { if( c == '<' && lastC != '\\' ) { setState( ANALYZE_START_PENDING ); startTagBuffer(); } else if( c == '$' && lastC != '\\' ) { setState( ANALYZE_START_PENDING ); startTagBuffer(); _bAltTag = true; } break; } case ANALYZE_START_PENDING: { if( !_bAltTag && c == '%' ) { setState( ANALYZE_PENDING ); } else if( _bAltTag && c == '{' ) { setState( ANALYZE_SEPARATELY_PENDING ); } else { _bAltTag = false; setState( IGNORE ); } break; } case ANALYZE_PENDING: { if( c == '=' ) { setState( ANALYZE_SEPARATELY_PENDING ); break; } else if( c == '!' ) { break; } else if( c == '-' ) // <%-- Comment { if( lastC == '-' ) { setState( IGNORE_COMMENT ); break; } setState( ANALYZE_PENDING ); break; } else if( c == '@' ) // <%@ Page Directive { setState( ANALYZE_DIRECTIVE_PENDING ); break; } setState( ANALYZE ); break; } case ANALYZE_SEPARATELY_PENDING: { setState( ANALYZE_SEPARATELY ); if( !_bAltTag && c == '%' ) { setState( ANALYZE_SEPARATELY_END_PENDING ); break; } else if( _bAltTag && c == '}' ) { if( isAnalyzingSeparatelyWaitingForCloseBrace() ) { setState( ANALYZE_SEPARATELY_END_PENDING ); break; } } break; } case ANALYZE_DIRECTIVE_PENDING: { setState( ANALYZE_DIRECTIVE ); if( c == '%' ) { setState( ANALYZE_DIRECTIVE_END_PENDING ); break; } break; } case IGNORE_COMMENT: { if( c == '-' && lastC == '-' ) { setState( COMMENT_END_PENDING ); } break; } case COMMENT_END_PENDING: { if( c == '%' ) { setState( COMMENT_END_PENDING2 ); break; } setState( IGNORE_COMMENT ); break; } case COMMENT_END_PENDING2: { if( c == '>' ) { setState( IGNORE ); break; } setState( IGNORE_COMMENT ); break; } case ANALYZE: { if( c == '%' ) { setState( ANALYZE_END_PENDING ); break; } break; } case ANALYZE_SEPARATELY: { if( !_bAltTag && c == '%' ) { setState( ANALYZE_SEPARATELY_END_PENDING ); break; } else if( _bAltTag && c == '}' ) { if( isAnalyzingSeparatelyWaitingForCloseBrace() ) { setState( ANALYZE_SEPARATELY_END_PENDING ); break; } } break; } case ANALYZE_DIRECTIVE: { if( c == '%' ) { setState( ANALYZE_DIRECTIVE_END_PENDING ); break; } break; } case ANALYZE_END_PENDING: { if( c == '>' ) { setState( IGNORE ); break; } setState( ANALYZE ); break; } case ANALYZE_SEPARATELY_END_PENDING: { if( _bAltTag ) { _bAltTag = false; if( c == '<' ) { setState( ANALYZE_START_PENDING ); startTagBuffer(); } else if( c == '$' ) { setState( ANALYZE_START_PENDING ); startTagBuffer(); _bAltTag = true; } else { setState( IGNORE ); } break; } else if( c == '>' ) { setState( IGNORE ); break; } setState( ANALYZE_SEPARATELY ); break; } case ANALYZE_DIRECTIVE_END_PENDING: { if( c == '>' ) { setState( IGNORE ); break; } setState( ANALYZE_DIRECTIVE ); break; } default: throw new RuntimeException( "Bad template tokenizer instructor state" ); } } private boolean isAnalyzingSeparatelyWaitingForCloseBrace() { // return true; Stack<IToken> tokens = _tokenizer.getTokens(); if( tokens.isEmpty() ) { return true; } int iStmtBlock = ((SourceCodeTokenizer)_tokenizer).getInternal().getType() == '}' ? 1 : 0; for( int i = tokens.size()-1; i >= 0; i-- ) { IToken token = tokens.get( i ); if( token.getType() == '}' ) { iStmtBlock++; } if( token.getType() == '{' ) { if( iStmtBlock == 0 ) { if( token.isAnalyzingSeparately() ) { return false; } } else { iStmtBlock--; } } } return true; } private void setState( int iState ) { _iState = iState; } private int getState() { return _iState; } private void countLines( char c ) { if( c == '\n' ) { _iLines++; } } private void startTagBuffer() { _bStartTagBuffer = true; } @Override public void setTokenizer( ISourceCodeTokenizer tokenizer ) { _tokenizer = tokenizer; } }