/* * Copyright (C) 2013 João Vicente Reis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package br.pcgl.netbeans.glsl.lexer; import org.netbeans.api.lexer.Token; import org.netbeans.spi.lexer.Lexer; import org.netbeans.spi.lexer.LexerInput; import org.netbeans.spi.lexer.LexerRestartInfo; /** * See http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-lexer/overview-summary.html * and http://platform.netbeans.org/tutorials/nbm-javacc-lexer.html */ public class GlslLexer implements Lexer<GlslTokenId> { private LexerRestartInfo<GlslTokenId> info; private LexerInput input; GlslLexer(LexerRestartInfo<GlslTokenId> info) { this.info = info; this.input = info.input(); } @Override @SuppressWarnings("fallthrough") public Token<GlslTokenId> nextToken() { while (true) { int ch = input.read(); switch (ch) { case ';': return token(GlslTokenId.SEMICOLON); case '{': return token(GlslTokenId.LBRACE); case '}': return token(GlslTokenId.RBRACE); case '(': return token(GlslTokenId.LPAREN); case ')': return token(GlslTokenId.RPAREN); case '+': case '-': case '*': case '%': case '[': case ']': case ',': case '!': case '~': case '<': case '>': case '=': case '&': case '^': case '|': case '?': case ':': case '\\': return token(GlslTokenId.OPERATOR); case '\'': case '\"': int stringStart; if (ch == '\"') { stringStart = '\"'; } else { stringStart = '\''; } while (true) { int lastChar = ch; ch = input.read(); if (ch == stringStart && lastChar != '\\') { break; } if (ch == LexerInput.EOF || ch == '\r' || ch == '\n') { input.backup(1); break; } } return token(GlslTokenId.STRING); case '/': switch (input.read()) { case '/': // in single-line comment while (true) { switch (input.read()) { case '\r': input.consumeNewline(); case '\n': case LexerInput.EOF: return token(GlslTokenId.SL_COMMENT); } } case '*': // in multi-line comment while (true) { ch = input.read(); while (ch == '*') { ch = input.read(); if (ch == '/') { return token(GlslTokenId.ML_COMMENT); } else if (ch == LexerInput.EOF) { return token(GlslTokenId.ML_COMMENT_INCOMPLETE); } } if (ch == LexerInput.EOF) { return token(GlslTokenId.ML_COMMENT_INCOMPLETE); } } } input.backup(1); return token(GlslTokenId.SLASH); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return finishIntOrFloatLiteral(ch); case '.': char nextChar = (char)input.read(); input.backup(1); if (isDigit(nextChar)) { return finishIntOrFloatLiteral(ch); } else { return token(GlslTokenId.OPERATOR); } case LexerInput.EOF: return null; default: if (Character.isWhitespace((char)ch)) { ch = input.read(); while (ch != LexerInput.EOF && Character.isWhitespace((char)ch)) { ch = input.read(); } input.backup(1); return token(GlslTokenId.WHITESPACE); } if (Character.isLetter((char)ch) || (((char)ch) == '#') || (((char)ch) == '_')) { // identifier or keyword while (true) { char cch = (char)ch; if (ch == LexerInput.EOF || ! (Character.isLetter(cch) || isDigit(cch) || (cch == '#') || (cch == '_')) ) { input.backup(1); // backup the extra char (or EOF) // Check for keywords! String text = input.readText().toString(); GlslTokenId id = GlslKeywords.keywords1.get(text); if (id == null) { id = GlslKeywords.keywords2.get(text); } if (id == null) { id = GlslKeywords.keywords3.get(text); } if (id == null) { id = GlslKeywords.keywords4.get(text); } if (id == null) { id = GlslKeywords.keywords5.get(text); } if (id == null) { id = GlslKeywords.keywords6.get(text); } if (id == null) { id = GlslTokenId.IDENTIFIER; } return token(id); } ch = input.read(); // read next char } } return token(GlslTokenId.ERROR); } } } private boolean isDigit(char ch) { switch(ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return true; default: return false; } } @SuppressWarnings("AssignmentToMethodParameter") private Token<GlslTokenId> finishIntOrFloatLiteral(int ch) { boolean floatLiteral = false; boolean inExponent = false; while (true) { switch (ch) { case '.': if (floatLiteral) { return token(GlslTokenId.FLOAT_LITERAL); } else { floatLiteral = true; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': break; case 'e': case 'E': // exponent part if (inExponent) { return token(GlslTokenId.FLOAT_LITERAL); } else { floatLiteral = true; inExponent = true; } break; case 'f': case 'F': if (floatLiteral) { return token(GlslTokenId.FLOAT_LITERAL); } else { input.backup(1); return token(GlslTokenId.INT_LITERAL); } default: input.backup(1); return token(floatLiteral ? GlslTokenId.FLOAT_LITERAL : GlslTokenId.INT_LITERAL); } ch = input.read(); } } private Token<GlslTokenId> token(GlslTokenId id) { return info.tokenFactory().createToken(id); } @Override public Object state() { return null; } @Override public void release() { } }