php.smarty/src/org/netbeans/modules/php/smarty/editor/lexer/TplTopLexer.java
author Martin Fousek <marfous@netbeans.org>
Thu, 03 Mar 2011 00:03:37 +0100
changeset 17309 60f4cc6038b3
parent 17288 e1387a5d34c8
child 17329 54d8f8c364b0
permissions -rw-r--r--
removing UI freezing by change of default Smarty delimiters
marfous@16192
     1
/*
marfous@16192
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
marfous@16192
     3
 *
marfous@16192
     4
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
marfous@16192
     5
 *
marfous@16192
     6
 * The contents of this file are subject to the terms of either the GNU
marfous@16192
     7
 * General Public License Version 2 only ("GPL") or the Common
marfous@16192
     8
 * Development and Distribution License("CDDL") (collectively, the
marfous@16192
     9
 * "License"). You may not use this file except in compliance with the
marfous@16192
    10
 * License. You can obtain a copy of the License at
marfous@16192
    11
 * http://www.netbeans.org/cddl-gplv2.html
marfous@16192
    12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
marfous@16192
    13
 * specific language governing permissions and limitations under the
marfous@16192
    14
 * License.  When distributing the software, include this License Header
marfous@16192
    15
 * Notice in each file and include the License file at
marfous@16192
    16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
marfous@16192
    17
 * particular file as subject to the "Classpath" exception as provided
marfous@16192
    18
 * by Sun in the GPL Version 2 section of the License file that
marfous@16192
    19
 * accompanied this code. If applicable, add the following below the
marfous@16192
    20
 * License Header, with the fields enclosed by brackets [] replaced by
marfous@16192
    21
 * your own identifying information:
marfous@16192
    22
 * "Portions Copyrighted [year] [name of copyright owner]"
marfous@16192
    23
 *
marfous@16192
    24
 * If you wish your version of this file to be governed by only the CDDL
marfous@16192
    25
 * or only the GPL Version 2, indicate your decision by adding
marfous@16192
    26
 * "[Contributor] elects to include this software in this distribution
marfous@16192
    27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
marfous@16192
    28
 * single choice of license, a recipient has the option to distribute
marfous@16192
    29
 * your version of this file under either the CDDL, the GPL Version 2 or
marfous@16192
    30
 * to extend the choice of license to its licensees as provided above.
marfous@16192
    31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
marfous@16192
    32
 * Version 2 license, then the option applies only if the new code is
marfous@16192
    33
 * made subject to such option by the copyright holder.
marfous@16192
    34
 *
marfous@16192
    35
 * Contributor(s):
marfous@16192
    36
 *
marfous@16192
    37
 * Portions Copyrighted 2008 Sun Microsystems, Inc.
marfous@16192
    38
 */
marfous@16192
    39
package org.netbeans.modules.php.smarty.editor.lexer;
marfous@16192
    40
marfous@16264
    41
import org.netbeans.api.lexer.InputAttributes;
marfous@16264
    42
import org.netbeans.api.lexer.LanguagePath;
marfous@16192
    43
import org.netbeans.api.lexer.Token;
marfous@17288
    44
import org.netbeans.lib.editor.util.CharSequenceUtilities;
marfous@16264
    45
import org.netbeans.modules.php.smarty.editor.TplMetaData;
marfous@16211
    46
import org.netbeans.modules.php.smarty.editor.utlis.LexerUtils;
marfous@16264
    47
import org.netbeans.modules.php.smarty.editor.utlis.TplUtils;
marfous@16192
    48
import org.netbeans.spi.lexer.Lexer;
marfous@16192
    49
import org.netbeans.spi.lexer.LexerInput;
marfous@16192
    50
import org.netbeans.spi.lexer.LexerRestartInfo;
marfous@16192
    51
import org.netbeans.spi.lexer.TokenFactory;
marfous@16192
    52
marfous@16192
    53
/**
marfous@16192
    54
 *
marfous@16192
    55
 * @author Martin Fousek
marfous@16192
    56
 */
marfous@16192
    57
public class TplTopLexer implements Lexer<TplTopTokenId> {
marfous@16192
    58
marfous@16192
    59
    private final TplTopColoringLexer scanner;
marfous@16192
    60
    private TokenFactory<TplTopTokenId> tokenFactory;
marfous@16264
    61
    private final InputAttributes inputAttributes;
marfous@16264
    62
    private final TplMetaData tplMetaData;
marfous@16192
    63
marfous@16219
    64
    private class CompoundState {
marfous@16219
    65
        private State lexerState;
marfous@16219
    66
        private SubState lexerSubState;
marfous@16219
    67
marfous@16219
    68
        public CompoundState(State lexerState, SubState lexerSubState) {
marfous@16219
    69
            this.lexerState = lexerState;
marfous@16219
    70
            this.lexerSubState = lexerSubState;
marfous@16219
    71
        }
marfous@16219
    72
marfous@16219
    73
        @Override
marfous@16219
    74
        public boolean equals(Object obj) {
marfous@16219
    75
            if (obj == null) {
marfous@16219
    76
                return false;
marfous@16219
    77
            }
marfous@16219
    78
            if (getClass() != obj.getClass()) {
marfous@16219
    79
                return false;
marfous@16219
    80
            }
marfous@16219
    81
            final CompoundState other = (CompoundState) obj;
marfous@16219
    82
            if (this.lexerState != other.lexerState) {
marfous@16219
    83
                return false;
marfous@16219
    84
            }
marfous@16219
    85
            if (this.lexerSubState != other.lexerSubState) {
marfous@16219
    86
                return false;
marfous@16219
    87
            }
marfous@16219
    88
            return true;
marfous@16219
    89
        }
marfous@16219
    90
marfous@16219
    91
        @Override
marfous@16219
    92
        public int hashCode() {
marfous@16219
    93
            int hash = 3;
marfous@16219
    94
            hash = 17 * hash + this.lexerState.ordinal();
marfous@16219
    95
            hash = 17 * hash + this.lexerSubState.ordinal();
marfous@16219
    96
            return hash;
marfous@16219
    97
        }
marfous@16219
    98
marfous@16219
    99
        @Override
marfous@16219
   100
        public String toString() {
marfous@16219
   101
            return "State(hash=" + hashCode() + ",s=" + lexerState + ",ss=" + lexerSubState + ")"; //NOI18N
marfous@16219
   102
        }
marfous@16219
   103
marfous@16219
   104
    }
marfous@16211
   105
marfous@16192
   106
    private TplTopLexer(LexerRestartInfo<TplTopTokenId> info) {
marfous@16219
   107
        CompoundState state = null;
marfous@16219
   108
        if (info.state() == null) {
marfous@16219
   109
            state = new CompoundState(State.INIT, SubState.NO_SUB_STATE);
marfous@16219
   110
        } else {
marfous@16219
   111
            state = (CompoundState)info.state();
marfous@16219
   112
        }
marfous@16192
   113
        this.tokenFactory = info.tokenFactory();
marfous@16264
   114
        this.inputAttributes = info.inputAttributes();
marfous@16264
   115
        if (inputAttributes != null) {
marfous@16264
   116
            this.tplMetaData = (TplMetaData) inputAttributes.getValue(LanguagePath.get(TplTopTokenId.language()), TplMetaData.class);
marfous@16264
   117
        } else {
marfous@16264
   118
            this.tplMetaData = TplUtils.getProjectPropertiesForFileObject(null);
marfous@16264
   119
        }
marfous@16264
   120
        scanner = new TplTopColoringLexer(info, state, tplMetaData);
marfous@16192
   121
    }
marfous@16192
   122
marfous@16192
   123
    /**
marfous@16192
   124
     * Create new top lexer.
marfous@16192
   125
     * @param info where was the parsing started
marfous@16192
   126
     * @return new lexer for additional parsing
marfous@16192
   127
     */
marfous@16192
   128
    public static synchronized TplTopLexer create(LexerRestartInfo<TplTopTokenId> info) {
marfous@16192
   129
        return new TplTopLexer(info);
marfous@16192
   130
    }
marfous@16192
   131
marfous@16192
   132
    public Token<TplTopTokenId> nextToken() {
marfous@16192
   133
        TplTopTokenId tokenId = scanner.nextToken();
marfous@16192
   134
        Token<TplTopTokenId> token = null;
marfous@16192
   135
        if (tokenId != null) {
marfous@16192
   136
            token = tokenFactory.createToken(tokenId);
marfous@16192
   137
        }
marfous@16192
   138
        return token;
marfous@16192
   139
    }
marfous@16192
   140
marfous@16192
   141
    public Object state() {
marfous@16192
   142
        return scanner.getState();
marfous@16192
   143
    }
marfous@16192
   144
marfous@16192
   145
    public void release() {
marfous@16192
   146
    }
marfous@16192
   147
marfous@16192
   148
    private enum State {
marfous@16192
   149
        INIT,
marfous@16192
   150
        OUTER,
marfous@16211
   151
        AFTER_DELIMITER,
marfous@16194
   152
        OPEN_DELIMITER,
marfous@16194
   153
        CLOSE_DELIMITER,
marfous@16211
   154
        IN_COMMENT,
marfous@16211
   155
        IN_SMARTY,
marfous@16211
   156
        IN_PHP,
marfous@16219
   157
        AFTER_SUBSTATE,
marfous@16219
   158
        IN_LITERAL
marfous@16219
   159
    }
marfous@16219
   160
marfous@16219
   161
    private enum SubState {
marfous@16219
   162
        NO_SUB_STATE,
marfous@16219
   163
        PHP_CODE,
marfous@16219
   164
        LITERAL
marfous@16192
   165
    }
marfous@16192
   166
marfous@16192
   167
    private class TplTopColoringLexer {
marfous@16192
   168
marfous@16192
   169
        private State state;
marfous@16192
   170
        private final LexerInput input;
marfous@16219
   171
        private SubState subState;
marfous@16264
   172
        private final TplMetaData metadata;
marfous@16192
   173
marfous@16264
   174
        public TplTopColoringLexer(LexerRestartInfo<TplTopTokenId> info, CompoundState state, TplMetaData metadata) {
marfous@16192
   175
            this.input = info.input();
marfous@16219
   176
            this.state = state.lexerState;
marfous@16219
   177
            this.subState = state.lexerSubState;
marfous@16264
   178
            if (metadata != null)
marfous@16264
   179
                this.metadata = metadata;
marfous@16264
   180
            else
marfous@16264
   181
                this.metadata = TplUtils.getProjectPropertiesForFileObject(null);
marfous@16192
   182
        }
marfous@16192
   183
marfous@16192
   184
        public TplTopTokenId nextToken() {
marfous@16192
   185
            int c = input.read();
marfous@16192
   186
            CharSequence text;
marfous@16194
   187
            int textLength;
marfous@16219
   188
            int openDelimiterLength = getOpenDelimiterLength();
marfous@16219
   189
            int closeDelimiterLength = getCloseDelimiterLength();
marfous@16192
   190
            if (c == LexerInput.EOF) {
marfous@16192
   191
                return null;
marfous@16192
   192
            }
marfous@16192
   193
            while (c != LexerInput.EOF) {
marfous@16192
   194
                char cc = (char) c;
marfous@16192
   195
                text = input.readText();
marfous@16194
   196
                textLength = text.length();
marfous@17309
   197
               switch (state) {
marfous@16219
   198
                case INIT:
marfous@16219
   199
                case OUTER:
marfous@16219
   200
                    if (isSmartyOpenDelimiter(text)) {
marfous@16219
   201
                        state = State.OPEN_DELIMITER;
marfous@16219
   202
                        input.backup(openDelimiterLength);
marfous@16219
   203
                        if (textLength > openDelimiterLength) {
marfous@16219
   204
                            return TplTopTokenId.T_HTML;
marfous@16219
   205
                        }
marfous@16219
   206
                    }
marfous@16332
   207
//                    if (cc == '\n') {
marfous@16332
   208
//                        return TplTopTokenId.T_HTML;
marfous@16332
   209
//                    }
marfous@16219
   210
                    break;
marfous@16219
   211
marfous@16219
   212
                case OPEN_DELIMITER:
marfous@16219
   213
                    if (textLength < openDelimiterLength) {
marfous@16219
   214
                        break;
marfous@16219
   215
                    }
marfous@16219
   216
                    state = State.AFTER_DELIMITER;
marfous@16219
   217
                    if (subState == subState.NO_SUB_STATE) {
marfous@16219
   218
                        return TplTopTokenId.T_SMARTY_OPEN_DELIMITER;
marfous@16219
   219
                    } else {
marfous@16219
   220
                        if (input.readLength() > openDelimiterLength) {
marfous@16219
   221
                            input.backup(input.readLength() - openDelimiterLength);
marfous@16219
   222
                            if (subState == subState.LITERAL)
marfous@16194
   223
                                return TplTopTokenId.T_HTML;
marfous@16219
   224
                            else
marfous@16219
   225
                                return TplTopTokenId.T_PHP;
marfous@16219
   226
                        }
marfous@16219
   227
                        break;
marfous@16219
   228
                    }
marfous@16219
   229
marfous@16219
   230
                case AFTER_DELIMITER:
marfous@16219
   231
                    if (LexerUtils.isWS(c)) {
marfous@16219
   232
                        if (subState == subState.NO_SUB_STATE) {
marfous@16219
   233
                            return TplTopTokenId.T_SMARTY;
marfous@16219
   234
                        } else {
marfous@16219
   235
                            break;
marfous@16219
   236
                        }
marfous@16219
   237
                    }
marfous@16219
   238
                    else {
marfous@16219
   239
                        String lastWord = readNextWord(c);
marfous@16219
   240
                        switch(subState){
marfous@16219
   241
                            case LITERAL:
marfous@16219
   242
                                if (lastWord.startsWith("/literal")) {
marfous@16219
   243
                                    subState = SubState.NO_SUB_STATE;
marfous@16219
   244
                                    state = State.OPEN_DELIMITER;
marfous@16219
   245
                                    input.backup(input.readLength());
marfous@16219
   246
                                    break;
marfous@16219
   247
                                } else {
marfous@16225
   248
                                    input.backup(input.readLength()-1);
marfous@16219
   249
                                    state = State.IN_LITERAL;
marfous@16219
   250
                                }
marfous@16219
   251
                                return TplTopTokenId.T_HTML;
marfous@16219
   252
                            case PHP_CODE:
marfous@16219
   253
                                if (lastWord.startsWith("/php")) {
marfous@16219
   254
                                    subState = SubState.NO_SUB_STATE;
marfous@16219
   255
                                    state = State.OPEN_DELIMITER;
marfous@16219
   256
                                    input.backup(input.readLength());
marfous@16219
   257
                                    break;
marfous@16219
   258
                                } else {
marfous@16219
   259
                                    state = State.IN_PHP;
marfous@16219
   260
                                }
marfous@16219
   261
                                return TplTopTokenId.T_PHP;
marfous@16219
   262
                            default:
marfous@16219
   263
                               if (lastWord.charAt(0) == '*') {
marfous@16219
   264
                                    state = State.IN_COMMENT;
marfous@16219
   265
                                    input.backup(lastWord.length()-1);
marfous@16219
   266
                                    return TplTopTokenId.T_COMMENT;
marfous@16219
   267
                                }
marfous@16219
   268
                                else if (lastWord.startsWith("literal")) {
marfous@16219
   269
                                    subState = SubState.LITERAL;
marfous@16219
   270
                                    state = State.AFTER_SUBSTATE;
marfous@16219
   271
                                    input.backup(lastWord.length()-7);
marfous@16219
   272
                                    return TplTopTokenId.T_SMARTY;
marfous@16219
   273
                                }
marfous@16219
   274
                                else if (lastWord.startsWith("php")) {
marfous@16219
   275
                                    subState = SubState.PHP_CODE;
marfous@16219
   276
                                    state = State.AFTER_SUBSTATE;
marfous@16219
   277
                                    input.backup(lastWord.length()-3);
marfous@16219
   278
                                    return TplTopTokenId.T_SMARTY;
marfous@16219
   279
                                }
marfous@16219
   280
                                else {
marfous@16219
   281
                                    state = State.IN_SMARTY;
marfous@16219
   282
                                    input.backup(lastWord.length());
marfous@16219
   283
                                }
marfous@16219
   284
                        }
marfous@16219
   285
                    }
marfous@16219
   286
                    break;
marfous@16219
   287
marfous@16219
   288
                case IN_COMMENT:
marfous@16219
   289
                    if (cc == '*') {
marfous@16219
   290
                        state = State.AFTER_SUBSTATE;
marfous@16219
   291
                        return TplTopTokenId.T_COMMENT;
marfous@16219
   292
                    }
marfous@16219
   293
                    return TplTopTokenId.T_COMMENT;
marfous@16219
   294
marfous@16219
   295
                case AFTER_SUBSTATE:
marfous@16219
   296
                    if (LexerUtils.isWS(c)) {
marfous@16219
   297
                        return TplTopTokenId.T_SMARTY;
marfous@16219
   298
                    }
marfous@16219
   299
                    else if (isSmartyCloseDelimiter(text)) {
marfous@16219
   300
                        state = State.CLOSE_DELIMITER;
marfous@16219
   301
                        input.backup(closeDelimiterLength);
marfous@16219
   302
                        break;
marfous@16219
   303
                    } else {
marfous@16221
   304
                        break;
marfous@16219
   305
                    }
marfous@16219
   306
marfous@16219
   307
                case CLOSE_DELIMITER:
marfous@16219
   308
                    if (textLength < closeDelimiterLength) {
marfous@16219
   309
                        break;
marfous@16219
   310
                    }
marfous@16219
   311
                    switch(subState){
marfous@16219
   312
                        case LITERAL:
marfous@16219
   313
                            state = State.IN_LITERAL;
marfous@16219
   314
                            break;
marfous@16219
   315
                        case PHP_CODE:
marfous@16219
   316
                            state = State.IN_PHP;
marfous@16219
   317
                            break;
marfous@16219
   318
                        default:
marfous@16219
   319
                            state = State.OUTER;
marfous@16219
   320
                            break;
marfous@16219
   321
                    }
marfous@16219
   322
                    return TplTopTokenId.T_SMARTY_CLOSE_DELIMITER;
marfous@16219
   323
marfous@16219
   324
                case IN_PHP:
marfous@16219
   325
                    if (isSmartyOpenDelimiter(text)) {
marfous@16219
   326
                        state = State.OPEN_DELIMITER;
marfous@16225
   327
                        input.backup(openDelimiterLength);
marfous@16225
   328
                        if (input.readLength() > 0)
marfous@16225
   329
                            return TplTopTokenId.T_PHP;
marfous@16219
   330
                    }
marfous@16225
   331
                    if (input.readLength() > 1) {
marfous@16219
   332
                        return TplTopTokenId.T_PHP;
marfous@16219
   333
                    }
marfous@16219
   334
                    break;
marfous@16219
   335
marfous@16219
   336
                case IN_LITERAL:
marfous@16219
   337
                    if (isSmartyOpenDelimiter(text)) {
marfous@16219
   338
                        state = State.OPEN_DELIMITER;
marfous@16225
   339
                        input.backup(openDelimiterLength);
marfous@16307
   340
                        if (input.readLength() > 0) {
marfous@16307
   341
                            return TplTopTokenId.T_HTML;
marfous@16307
   342
                        }
marfous@16219
   343
                    }
marfous@16286
   344
                    if (LexerUtils.isWS(c)) {
marfous@16219
   345
                        return TplTopTokenId.T_HTML;
marfous@16219
   346
                    }
marfous@16219
   347
                    break;
marfous@16219
   348
marfous@16219
   349
                case IN_SMARTY:
marfous@16219
   350
                    if (isSmartyCloseDelimiter(text)) {
marfous@16219
   351
                        if (textLength == closeDelimiterLength) {
marfous@16219
   352
                            state = State.OUTER;
marfous@16219
   353
                            return TplTopTokenId.T_SMARTY_CLOSE_DELIMITER;
marfous@16219
   354
                        }
marfous@16219
   355
                        else {
marfous@16219
   356
                            state = State.CLOSE_DELIMITER;
marfous@16221
   357
                            input.backup(closeDelimiterLength);
marfous@16219
   358
                            if (input.readLength() != 0) {
marfous@16219
   359
                                return TplTopTokenId.T_SMARTY;
marfous@16194
   360
                            }
marfous@16192
   361
                        }
marfous@16232
   362
                    }
marfous@16232
   363
                    switch(c) {
marfous@16232
   364
                        case '\n':
marfous@16232
   365
                           return TplTopTokenId.T_SMARTY;
marfous@16232
   366
                        case LexerInput.EOF:
marfous@16232
   367
                           return TplTopTokenId.T_SMARTY;
marfous@16306
   368
//                        case '<':
marfous@16306
   369
//                           state = State.OUTER;
marfous@16306
   370
//                           input.backup(1);
marfous@16306
   371
//                           if (input.readLength() > 1) {
marfous@16306
   372
//                                return TplTopTokenId.T_SMARTY;
marfous@16306
   373
//                           }
marfous@16219
   374
                    }
marfous@16219
   375
                    break;
marfous@16192
   376
                }
marfous@16192
   377
                c = input.read();
marfous@16192
   378
            }
marfous@16192
   379
marfous@16195
   380
            return getTokenId(state);
marfous@16195
   381
        }
marfous@16195
   382
marfous@16195
   383
        private TplTopTokenId getTokenId(State state) {
marfous@16192
   384
            switch (state) {
marfous@16194
   385
                case IN_SMARTY:
marfous@16194
   386
                    return TplTopTokenId.T_SMARTY;
marfous@16194
   387
                case OPEN_DELIMITER:
marfous@16194
   388
                    return TplTopTokenId.T_SMARTY_OPEN_DELIMITER;
marfous@16194
   389
                case CLOSE_DELIMITER:
marfous@16194
   390
                    return TplTopTokenId.T_SMARTY_CLOSE_DELIMITER;
marfous@16192
   391
                default:
marfous@16192
   392
                    return TplTopTokenId.T_HTML;
marfous@16192
   393
            }
marfous@16192
   394
        }
marfous@16192
   395
marfous@16192
   396
        Object getState() {
marfous@16219
   397
            return new CompoundState(state, subState);
marfous@16219
   398
        }
marfous@16219
   399
marfous@16219
   400
        private boolean isSmartyOpenDelimiter(CharSequence text) {
marfous@17288
   401
            return CharSequenceUtilities.endsWith(text, metadata.getOpenDelimiter());
marfous@16219
   402
        }
marfous@16219
   403
marfous@16219
   404
        private boolean isSmartyCloseDelimiter(CharSequence text) {
marfous@17288
   405
            return CharSequenceUtilities.endsWith(text, metadata.getCloseDelimiter());
marfous@16219
   406
        }
marfous@16221
   407
        
marfous@16219
   408
        private int getOpenDelimiterLength() {
marfous@16264
   409
            return metadata.getOpenDelimiter().length();
marfous@16219
   410
        }
marfous@16219
   411
marfous@16219
   412
        private int getCloseDelimiterLength() {
marfous@16264
   413
            return metadata.getCloseDelimiter().length();
marfous@16219
   414
        }
marfous@16219
   415
marfous@16219
   416
        private String readNextWord(int lastChar) {
marfous@16219
   417
            String word = Character.toString((char)lastChar);
marfous@16219
   418
            int c;
marfous@16219
   419
            while (!LexerUtils.isWS(c = input.read()) && c != LexerInput.EOF) {
marfous@16219
   420
                word += Character.toString((char)c);
marfous@16219
   421
            }
marfous@16219
   422
            input.backup(1);
marfous@16219
   423
            return word;
marfous@16192
   424
        }
marfous@16192
   425
    }
marfous@16192
   426
}