/*
* Copyright 2010 Jean-Paul Balabanian and Yngve Devik Hammersland
*
* This file is part of glsl4idea.
*
* Glsl4idea is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Glsl4idea 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with glsl4idea. If not, see <http://www.gnu.org/licenses/>.
*/
package glslplugin.lang.parser;
import com.intellij.lang.ForeignLeafType;
import com.intellij.lang.PsiBuilder;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import glslplugin.lang.elements.GLSLTokenTypes;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static glslplugin.lang.elements.GLSLElementTypes.*;
import static glslplugin.lang.elements.GLSLTokenTypes.*;
/**
* GLSLParsing does all the parsing. It has methods which reflects the rules of the grammar.
*
* @author Yngve Devik Hammersland
* Date: Jan 19, 2009
* Time: 3:16:56 PM
*/
public final class GLSLParsing extends GLSLParsingBase {
// The general approach for error return and flagging is that an error should only be returned when not flagged.
// So, if an error is encountered; EITHER flag it in the editor OR propagate it down the call stack.
// Traits of all binary operators.
// They must be listed in order of precedence. (low to high)
private final static OperatorLevelTraits[] operatorPrecedence = new OperatorLevelTraits[]{
new OperatorLevelTraits(TokenSet.create(OR_OP), "sub expression", LOGICAL_OR_EXPRESSION),
new OperatorLevelTraits(TokenSet.create(XOR_OP), "sub expression", LOGICAL_XOR_EXPRESSION),
new OperatorLevelTraits(TokenSet.create(AND_OP), "sub expression", LOGICAL_AND_EXPRESSION),
new OperatorLevelTraits(TokenSet.create(VERTICAL_BAR), "sub expression", BINARY_OR_EXPRESSION),
new OperatorLevelTraits(TokenSet.create(CARET), "sub expression", BINARY_XOR_EXPRESSION),
new OperatorLevelTraits(TokenSet.create(AMPERSAND), "sub expression", BINARY_AND_EXPRESSION),
new OperatorLevelTraits(EQUALITY_OPERATORS, "sub expression", EQUALITY_EXPRESSION),
new OperatorLevelTraits(RELATIONAL_OPERATORS, "sub expression", RELATIONAL_EXPRESSION),
new OperatorLevelTraits(BIT_SHIFT_OPERATORS, "sub expression", BIT_SHIFT_EXPRESSION),
new OperatorLevelTraits(ADDITIVE_OPERATORS, "part expression", ADDITIVE_EXPRESSION),
new OperatorLevelTraits(MULTIPLICATIVE_OPERATORS, "factor", MULTIPLICATIVE_EXPRESSION),
};
GLSLParsing(PsiBuilder builder) {
super(builder);
}
//Parsing code
/**
* Parses preprocessor, assuming that the b.getTokenType() is at PREPROCESSOR_BEGIN.
*
* Called automatically on b.advanceLexer(), which means that elements may contain
* some tokens, that are part of preprocessor, not that element.
* This may cause trouble during working with the PSI tree, so be careful.
*/
@Override
protected final void parsePreprocessor() {
// We can't use tryMatch etc. in here because we'll end up
// potentially parsing a preprocessor directive inside this one.
PsiBuilder.Marker preprocessor = b.mark();
b.advanceLexer(false, false); //Get past the PREPROCESSOR_BEGIN ("#")
//advanceLexer(false,false) explanation:
//false -> this is not a valid place for more preprocessor directives
//false -> don't substitute here (makes re"define"ing and "undef"ing impossible)
IElementType directiveType = b.getTokenType();
if(directiveType == PREPROCESSOR_DEFINE){
//Parse define
b.advanceLexer(false, false);//Get past DEFINE
if(isValidDefineIdentifier(b.getTokenText())){
//Valid
final String defineIdentifier = b.getTokenText();
//Can use non-b b.advanceLexer here, to allow "nested" defines
b.advanceLexer();//Get past identifier
List<ForeignLeafType> definition = new ArrayList<ForeignLeafType>();
StringBuilder definitionText = new StringBuilder();
while (b.getTokenType() != PREPROCESSOR_END && !b.eof()) {
//Suppressed warning that getTokenType/Text may be null, because it won't be (.eof() is checked).
//noinspection ConstantConditions
definition.add(new RedefinedTokenType(b.getTokenType(), b.getTokenText(), b.getNamesThroughWhichThisTokenWasRedefined()));
definitionText.append(b.getTokenText()).append(' ');
b.advanceLexer();
}
definitions.put(defineIdentifier, definition);
if(definitionText.length() >= 1){
definitionText.setLength(definitionText.length()-1);
}
definitionTexts.put(defineIdentifier, definitionText.toString());
}else{
//Invalid
b.error("Identifier expected.");
//Eat rest
while (!b.eof()) {
if (b.getTokenType() == PREPROCESSOR_END) {
break;
}
b.advanceLexer();
}
}
}else if(directiveType == PREPROCESSOR_UNDEF){
//Parse undefine
b.advanceLexer(false, false);//Get past UNDEF
if(isValidDefineIdentifier(b.getTokenText())){
//Valid
final String defineIdentifier = b.getTokenText();
definitions.remove(defineIdentifier);
definitionTexts.remove(defineIdentifier);
b.advanceLexer();//Get past IDENTIFIER
}else{
//Invalid
b.error("Identifier expected.");
}
//Eat rest
while (!b.eof()) {
if (b.getTokenType() == PREPROCESSOR_END) {
break;
}else{
b.error("Unexpected token.");
}
b.advanceLexer();
}
}else{
//Some other directive, no work here
while (!b.eof()) {
if (b.getTokenType() == PREPROCESSOR_END) {
break;
}
b.advanceLexer();
}
}
b.advanceLexer(false, false);//Get past PREPROCESSOR_END
//false -> don't check for PREPROCESSOR_BEGIN, we will handle that ourselves
if(directiveType == null || !PREPROCESSOR_DIRECTIVES.contains(directiveType)){
//Happens when typing new directive at the end of the file
//or when malformed directive is created (eg #foo)
preprocessor.done(PREPROCESSOR_OTHER);
}else{
preprocessor.done(directiveType);
}
b.advanceLexer_remapTokens(); //Remap explicitly after advancing without remapping, makes mess otherwise
if (b.getTokenType() == PREPROCESSOR_BEGIN) {
parsePreprocessor();
}
}
private static Pattern IDENTIFIER_REGEX = Pattern.compile("[_a-zA-Z][_a-zA-Z0-9]*");
private boolean isValidDefineIdentifier(String text){
if(text == null)return false;
return IDENTIFIER_REGEX.matcher(text).matches();
}
/**
* Entry for parser. Tries to parse whole shader file.
*/
public void parseTranslationUnit() {
// translation_unit: external_declaration*
// We normally parse preprocessor directives whenever we advance the lexer - which means that if the first
// token is a preprocessor directive we won't catch it, so we just parse them all at the beginning here.
while (b.getTokenType() == PREPROCESSOR_BEGIN) {
parsePreprocessor();
}
while (!b.eof()) {
if (!parseExternalDeclaration()) {
b.advanceLexer();
b.error("Unable to parse external declaration.");
}
}
}
/**
* Parse whatever can be at the top of the file hierarchy
*/
private boolean parseExternalDeclaration() {
// external_declaration: function_definition
// | declaration
// | interface_block
// Expanding the rule to obtain:
// external_declaration: qualifier-list type-specifier prototype [ ';' | compound-statement ]
// | qualifier-list type-specifier declarator-list ';'
// | qualifier-list IDENTIFIER '{' (member'}' [ IDENTIFIER array-specifier? ]';'
// | layout_qualifier interface_qualifier ;
//
// A common prefix for all: qualifier-list - layout_qualifier is included in qualifier_list,
// but can be standalone
//
// Note: after type-specifier, we only need to look up IDENTIfIER '(' to determine
// whether or not it is a prototype or a declarator-list.
PsiBuilder.Marker mark = b.mark();
// This bunch of conditionals are responsible to handle really invalid input.
// Specifically, when b.getTokenType() is not in the first set of external-declaration
// Please add more if found lacking.
// TODO: Add something similar to parseStatement
if (b.getTokenType() == LEFT_PAREN ||
CONSTANT_TOKENS.contains(b.getTokenType()) ||
UNARY_OPERATORS.contains(b.getTokenType())) {
parseExpression();
tryMatch(SEMICOLON);
mark.error("Expression not allowed here.");
return true;
}
String text = b.getTokenText();
if (b.getTokenType() == IF_KEYWORD ||
b.getTokenType() == FOR_KEYWORD ||
b.getTokenType() == WHILE_KEYWORD ||
b.getTokenType() == DO_KEYWORD) {
parseSimpleStatement();
mark.error("'" + text + "' statement not allowed here.");
return true;
}
if (tryMatch(RIGHT_PAREN, RIGHT_BRACE, RIGHT_ANGLE, RIGHT_BRACKET, COMMA)) {
mark.error("Unexpected token '" + text + "'.");
return true;
}
while (tryMatch(OPERATORS)) {
mark.error("Unexpected token '" + text + "'.");
mark = b.mark();
}
if (parsePrecisionStatement()) {
mark.drop();
return true;
}
if (parseLayoutQualifierStatement()){
mark.drop();
return true;
}
parseQualifierList(true);
if (b.getTokenType() == IDENTIFIER && b.lookAhead(1) == LEFT_BRACE) { // interface block
//TODO Make sure that this is preceded by storage_qualifier
parseIdentifier();
match(LEFT_BRACE, "Expected '{'");
if (b.getTokenType() == RIGHT_BRACE) {
b.error("Empty interface block is not allowed.");
}
while (!tryMatch(RIGHT_BRACE) && !eof()) {
final PsiBuilder.Marker member = b.mark();
parseQualifierList(true);
if (!parseTypeSpecifier()) b.advanceLexer();
parseDeclaratorList();
match(SEMICOLON, "Expected ';'");
member.done(STRUCT_DECLARATION);//TODO Should we call interface block members struct members?
}
if (b.getTokenType() == IDENTIFIER) {
parseIdentifier();
if (b.getTokenType() == LEFT_BRACKET) {
parseArrayDeclarator();
}
}
match(SEMICOLON, "Expected ';'");
mark.done(INTERFACE_BLOCK);
return true;
}
parseTypeSpecifier();
PsiBuilder.Marker postType = b.mark();
if (b.getTokenType() == SEMICOLON) {
// Declaration with no declarators.
// (struct definitions will look like this)
postType.drop();
parseDeclaratorList(); // the list will always be empty.
match(SEMICOLON, "Missing ';'");
mark.done(VARIABLE_DECLARATION);
return true;
} else if (b.getTokenType() == IDENTIFIER || b.getTokenType() == LEFT_PAREN) {
// Identifier means either declarators, or function declaration/definition
match(IDENTIFIER, "Missing function name");
if (b.getTokenType() == SEMICOLON ||
b.getTokenType() == COMMA ||
b.getTokenType() == LEFT_BRACKET ||
b.getTokenType() == EQUAL) {
// These are valid operatorTokens after an identifier in a declarator.
// ... try to parse declarator-list!
postType.rollbackTo();
parseDeclaratorList();
match(SEMICOLON, "Missing ';' after variable declaration");
mark.done(VARIABLE_DECLARATION);
return true;
} else if (tryMatch(LEFT_PAREN)) {
// Left parenthesis '('
// This must be a function declaration or definition, parse the prototype first!
postType.rollbackTo();
PsiBuilder.Marker declarator = b.mark();
parseIdentifier();
declarator.done(DECLARATOR);
match(LEFT_PAREN, "Expected '(' after function identifier.");
parseParameterDeclarationList();
match(RIGHT_PAREN, "Missing ')' after function prototype");
// Prototype is now done, so look for ';' or '{'
if (tryMatch(SEMICOLON)) {
mark.done(FUNCTION_DECLARATION);
} else if (b.getTokenType() == LEFT_BRACE) {
parseCompoundStatement();
mark.done(FUNCTION_DEFINITION);
} else {
// Neither ';' nor '{' found, mark as a prototype with missing ';'
mark.done(FUNCTION_DECLARATION);
b.error("Missing ';' after function declaration.");
}
return true;
} else if (TYPE_SPECIFIER_NONARRAY_TOKENS.contains(b.getTokenType())) {
// simulate declarators, and return success to make parsing continue.
postType.done(IDENTIFIER);
postType = postType.precede();
postType.done(DECLARATOR);
postType = postType.precede();
postType.done(DECLARATOR_LIST);
mark.done(VARIABLE_DECLARATION);
b.error("Missing ';' after variable declaration.");
return true;
}
} else if (GLSLTokenTypes.OPERATORS.contains(b.getTokenType()) ||
b.getTokenType() == DOT ||
b.getTokenType() == LEFT_BRACKET) {
// this will handle most expressions
postType.drop();
mark.rollbackTo();
mark = b.mark();
if (!parseExpression()) {
//There is no expression! Consume what triggered me. (Would lead to infinite loop otherwise)
b.advanceLexer();
}
tryMatch(SEMICOLON);
mark.error("Expression not allowed here.");
return true;
} else if (GLSLTokenTypes.FLOW_KEYWORDS.contains(b.getTokenType()) ||
GLSLTokenTypes.CONSTANT_TOKENS.contains(b.getTokenType())) {
postType.drop();
text = b.getTokenText();
b.advanceLexer();
mark.error("Unexpected '" + text + "'");
return true;
} else if (TYPE_SPECIFIER_NONARRAY_TOKENS.contains(b.getTokenType())) {
// simulate declarators, and return success to make parsing continue.
postType.done(DECLARATOR_LIST);
mark.done(VARIABLE_DECLARATION);
b.error("Missing ';' after declaration.");
return true;
}
mark.rollbackTo();
return false;
}
private boolean parsePrecisionStatement() {
// precision_statement: PRECISION precision_qualifier type_specifier_no_precision ;
if (b.getTokenType() == PRECISION_KEYWORD) {
final PsiBuilder.Marker mark = b.mark();
b.advanceLexer();
if (!tryMatch(PRECISION_QUALIFIER_TOKENS)) {
b.error("Expected precision qualifier.");
}
if (!parseTypeSpecifier()) {
b.error("Expected type specifier.");
}
match(SEMICOLON, "Expected ';'");
mark.done(PRECISION_STATEMENT);
return true;
} else return false;
}
private boolean parseQualifiedTypeSpecifier() {
parseQualifierList(true);
boolean result = parseTypeSpecifier();
parseQualifierList(false);
return result;
}
private void parseParameterDeclarationList() {
// parameter_declaration_list: <nothing>
// | VOID
// | parameter_declaration (',' parameter_declaration)*
final PsiBuilder.Marker mark = b.mark();
//noinspection StatementWithEmptyBody
if (tryMatch(VOID_TYPE)) {
// Do nothing.
} else if (b.getTokenType() != RIGHT_PAREN) {
do {
parseParameterDeclaration();
} while (tryMatch(COMMA));
}
mark.done(PARAMETER_DECLARATION_LIST);
}
private void parseParameterDeclaration() {
// parameter_declaration: [parameter_qualifier] [type_qualifier] IDENTIFIER [array_declarator]
final PsiBuilder.Marker mark = b.mark();
parseQualifiedTypeSpecifier();
if (b.getTokenType() == IDENTIFIER) {
parseStructOrParameterDeclarator(PARAMETER_DECLARATOR);
} else {
// Fake a declarator.
PsiBuilder.Marker mark2 = b.mark();
mark2.done(PARAMETER_DECLARATOR);
}
mark.done(PARAMETER_DECLARATION);
}
private void parseCompoundStatement() {
// compound_statement: '{' '}'
// | '{' statement_list '}'
PsiBuilder.Marker mark = b.mark();
match(LEFT_BRACE, "'{' expected.");
if (eof(mark)) return;
if (b.getTokenType() != RIGHT_BRACE) {
parseStatementList();
}
if (eof()) {
mark.drop();
} else {
match(RIGHT_BRACE, "'}' expected.");
mark.done(COMPOUND_STATEMENT);
}
}
private void parseStatementList() {
// statement_list: statement*
// NOTE: terminates with '}', but we check for FirstSet(statement)
// instead for increased robustness
while ((STATEMENT_FIRST_SET.contains(b.getTokenType()) || OPERATORS.contains(b.getTokenType()) || b.getTokenType() == PRECISION_KEYWORD) && !eof()) {
if (!parseStatement()) {
return;
}
}
}
private boolean parseStatement() {
// statement: simple_statement | compound_statement
if (b.getTokenType() == LEFT_BRACE) {
parseCompoundStatement();
return true;
}
if (parseSimpleStatement()) {
return true;
} else {
b.error("Expected a statement.");
return false;
}
}
private static final TokenSet VALID_FIRST_OPERATORS = TokenSet.create(INC_OP, DEC_OP, PLUS, DASH);
private void eatInvalidOperators() {
PsiBuilder.Marker mark = b.mark();
while (OPERATORS.contains(b.getTokenType()) && !VALID_FIRST_OPERATORS.contains(b.getTokenType())) {
String operator = b.getTokenText();
b.advanceLexer();
mark.error("Unexpected operator '" + operator + "'.");
mark = b.mark();
}
mark.drop();
}
private boolean parseSimpleStatement() {
// simple_statement: declaration_statement
// | expression_statement
// | selection_statement
// | switch_statement
// | iteration_statement
// | jump_statement
eatInvalidOperators();
final IElementType type = b.getTokenType();
boolean result;
if (EXPRESSION_FIRST_SET.contains(type) || QUALIFIER_TOKENS.contains(type) || type == PRECISION_KEYWORD) {
// This set also includes the first set of declaration_statement
if (lookaheadDeclarationStatement()) {
result = parseDeclarationStatement();
} else {
result = parseExpressionStatement();
}
} else if (type == IF_KEYWORD) {
result = parseSelectionStatement();
} else if (type == SWITCH_KEYWORD) {
result = parseSwitchStatement();
} else if (type == WHILE_KEYWORD) {
result = parseWhileIterationStatement();
} else if (type == DO_KEYWORD) {
result = parseDoIterationStatement();
} else if (type == FOR_KEYWORD) {
result = parseForStatement();
} else if (type == BREAK_JUMP_STATEMENT) {
result = parseBreakStatement();
} else if (type == DISCARD_JUMP_STATEMENT) {
result = parseDiscardStatement();
} else if (type == RETURN_JUMP_STATEMENT) {
result = parseReturnStatement();
} else if (type == CONTINUE_JUMP_STATEMENT) {
result = parseContinueStatement();
} else if (type == CASE_KEYWORD) {
result = parseCaseStatement();
} else if (type == DEFAULT_KEYWORD) {
result = parseDefaultStatement();
} else {
return false;
}
return result;
}
private boolean parseReturnStatement() {
// return_statement: 'return' [expression] ';'
PsiBuilder.Marker mark = b.mark();
match(RETURN_JUMP_STATEMENT, "Missing 'return'.");
if (b.getTokenType() != SEMICOLON) {
parseExpression();
match(SEMICOLON, "Missing ';' after expression.");
} else {
match(SEMICOLON, "Missing ';' after 'return'.");
}
mark.done(RETURN_STATEMENT);
return true;
}
private boolean parseContinueStatement() {
// discard_statement: 'continue' ';'
PsiBuilder.Marker mark = b.mark();
match(CONTINUE_JUMP_STATEMENT, "Missing 'continue'.");
match(SEMICOLON, "Missing ';' after 'continue'.");
mark.done(CONTINUE_STATEMENT);
return true;
}
private boolean parseDiscardStatement() {
// discard_statement: 'discard' ';'
PsiBuilder.Marker mark = b.mark();
match(DISCARD_JUMP_STATEMENT, "Missing 'discard'.");
match(SEMICOLON, "Missing ';' after 'discard'.");
mark.done(DISCARD_STATEMENT);
return true;
}
private boolean parseBreakStatement() {
// break_statement: 'break' ';'
PsiBuilder.Marker mark = b.mark();
match(BREAK_JUMP_STATEMENT, "Missing 'break'.");
match(SEMICOLON, "Missing ';' after 'break'.");
mark.done(BREAK_STATEMENT);
return true;
}
private boolean parseCaseStatement() {
// case_statement: 'case' constant_expression ':'
PsiBuilder.Marker mark = b.mark();
match(CASE_KEYWORD, "Expected 'case'");
parseConstantExpression();
match(COLON, "Expected ':'");
mark.done(CASE_STATEMENT);
return true;
}
private boolean parseDefaultStatement() {
// default_statement: 'default' ':'
PsiBuilder.Marker mark = b.mark();
match(DEFAULT_KEYWORD, "Expected 'case'");
match(COLON, "Expected ':'");
mark.done(DEFAULT_STATEMENT);
return true;
}
private boolean parseForStatement() {
// for_iteration_statement: 'for' '(' for_init_statement for_rest_statement ')' statement_no_new_scope
// NOTE: refactored to:
// for_iteration_statement: 'for' '(' (expression statement|declaration statement) condition? ';' expression? ')'
// condition:
// expression
// fully_specified_type IDENTIFIER '=' initializer
PsiBuilder.Marker mark = b.mark();
match(FOR_KEYWORD, "Missing 'for'.");
match(LEFT_PAREN, "Missing '(' after 'for'.");
parseForInitStatement();
if (b.getTokenType() != SEMICOLON) {
parseCondition();
}
match(SEMICOLON, "Missing ';' after condition statement.");
if (b.getTokenType() != RIGHT_PAREN) {
// Only parse the expression if it is present.
parseExpression();
}
match(RIGHT_PAREN, "Missing ')' after 'for'.");
parseStatement();
mark.done(FOR_STATEMENT);
return true;
}
private boolean lookaheadConditionDeclaration(){
final PsiBuilder.Marker rollback = b.mark();
try {
if(!parseQualifiedTypeSpecifier()){
return false;
}
if(!tryMatch(IDENTIFIER)){
return false;
}
if(!tryMatch(EQUAL)){
return false;
}
//At this point we can be pretty confident that this is a condition-style declaration
return true;
} finally {
rollback.rollbackTo();
}
}
private void parseCondition() {
// condition: expression
// | fully_specified_type IDENTIFIER '=' initializer
// NOTE: The spec, allows the condition expression in 'for' and 'while' loops
// to declare a single variable.
PsiBuilder.Marker conditionMark = b.mark();
if(lookaheadConditionDeclaration()){
PsiBuilder.Marker mark = b.mark();
parseQualifiedTypeSpecifier();
PsiBuilder.Marker list = b.mark();
PsiBuilder.Marker declarator = b.mark();
parseIdentifier();
match(EQUAL, "Missing '=' in condition initializer.");
parseInitializer();
declarator.done(DECLARATOR);
list.done(DECLARATOR_LIST);
mark.done(VARIABLE_DECLARATION);
}else{
if(!parseExpression()){
conditionMark.error("Expression or single variable declaration expected.");
return;
}
}
conditionMark.done(CONDITION);
}
private void parseForInitStatement() {
// for_init_statement: expression_statement | declaration_statement
if(tryMatch(SEMICOLON)){
//Empty statement, don't need to parse further
return;
}
PsiBuilder.Marker rollback = b.mark();
if(lookaheadDeclarationStatement() && parseDeclarationStatement()){
rollback.drop();
return;
}else{
rollback.rollbackTo();
rollback = b.mark();
}
if(parseExpressionStatement()){
rollback.drop();
return;
}else{
rollback.rollbackTo();
rollback = b.mark();
}
rollback.error("Failed to parse for-init statement.");
}
private boolean parseDoIterationStatement() {
// do_iteration_statement: 'do' statement 'while' '(' expression ')' ';'
PsiBuilder.Marker mark = b.mark();
match(DO_KEYWORD, "Missing 'do'.");
parseStatement();
match(WHILE_KEYWORD, "Missing 'while'.");
match(LEFT_PAREN, "Missing '(' after 'while'.");
parseCondition();
match(RIGHT_PAREN, "Missing ')' after 'while'.");
match(SEMICOLON, "Missing ';' after 'do-while'.");
mark.done(DO_STATEMENT);
return true;
}
private boolean parseWhileIterationStatement() {
// while_iteration_statement: 'while' '(' expression ')' statement
PsiBuilder.Marker mark = b.mark();
match(WHILE_KEYWORD, "Missing 'while'.");
match(LEFT_PAREN, "Missing '(' after 'while'.");
parseCondition();
match(RIGHT_PAREN, "Missing ')' after 'while'.");
parseStatement();
mark.done(WHILE_STATEMENT);
return true;
}
private boolean parseSelectionStatement() {
// selection_statement: 'if' '(' expression ')' statement [ 'else' statement ]
PsiBuilder.Marker mark = b.mark();
match(IF_KEYWORD, "Missing 'if'.");
match(LEFT_PAREN, "Missing '(' after 'if'.");
parseCondition();
match(RIGHT_PAREN, "Missing ')' after 'if'.");
parseStatement();
tryParseElsePart();
mark.done(IF_STATEMENT);
return true;
}
private void tryParseElsePart() {
// else_part: (nothing) | 'else' statement
if (tryMatch(ELSE_KEYWORD)) {
parseStatement();
}
}
private boolean parseSwitchStatement() {
// switch_statement: 'switch' '(' expression ')' '{' statement_list? '}'
PsiBuilder.Marker mark = b.mark();
match(SWITCH_KEYWORD, "Expected 'switch'");
match(LEFT_PAREN, "Expected '('");
parseExpression();
match(RIGHT_PAREN, "Expected ')'");
parseCompoundStatement();
mark.done(SWITCH_STATEMENT);
return true;
}
private boolean parseExpressionStatement() {
// expression_statement: [expression] ';'
PsiBuilder.Marker mark = b.mark();
//noinspection StatementWithEmptyBody
if (tryMatch(SEMICOLON)) {
// empty statement
} else {
if (!parseExpression()) {
mark.drop();
return false;
}
match(SEMICOLON, "Missing ';' after expression.");
}
mark.done(EXPRESSION_STATEMENT);
return true;
}
private boolean parseDeclarationStatement() {
// declaration_statement: declaration
// precision_statement
if(b.getTokenType() == PRECISION_KEYWORD){
return parsePrecisionStatement();
}
PsiBuilder.Marker mark = b.mark();
if (!parseDeclaration()) {
mark.error("Expected declaration.");
return false;
} else {
match(SEMICOLON, "Expected ';' after declaration statement.");
mark.done(DECLARATION_STATEMENT);
return true;
}
}
/**
* Looks ahead to determine whether a simple_statement is a
* declaration_statement or expression_statement.
*
* @return true if it is a declaration statement, false otherwise
*/
private boolean lookaheadDeclarationStatement() {
//Precision statement is a type of declaration statement (GLSL 4.30)
if(b.getTokenType() == PRECISION_KEYWORD)return true;
// they share type_specifier. So if found; look for the following identifier.
PsiBuilder.Marker rollback = b.mark();
try {
if (tryMatch(QUALIFIER_TOKENS)) {
return true;
}
if (!parseTypeSpecifier()) {
return false;
}
//noinspection RedundantIfStatement
if (tryMatch(IDENTIFIER) || tryMatch(SEMICOLON)) {
return true;
}
return false;
} finally {
rollback.rollbackTo();
}
}
private boolean parseDeclaration() {
// declaration: function_prototype SEMICOLON
// | init_declarator_list SEMICOLON
PsiBuilder.Marker mark = b.mark();
if (parseQualifiedTypeSpecifier()) {
parseDeclaratorList();
mark.done(VARIABLE_DECLARATION);
return true;
} else {
mark.error("Qualified type specifier expected.");
return false;
}
}
private void parseDeclaratorList() {
// init_declarator_list: fully_specified_type
// | fully_specified_type declarator ( ',' declarator )*
PsiBuilder.Marker mark = b.mark();
if (b.getTokenType() == IDENTIFIER) {
do {
parseDeclarator();
} while (tryMatch(COMMA));
}
mark.done(DECLARATOR_LIST);
}
private void parseDeclarator() {
// declarator: IDENTIFIER [ '[' [ constant_expression ] ']' ] [ '=' initializer ]
final PsiBuilder.Marker mark = b.mark();
parseIdentifier();
if (b.getTokenType() == LEFT_BRACKET) {
parseArrayDeclarator();
}
if (tryMatch(EQUAL)) {
parseInitializer();
}
mark.done(DECLARATOR);
}
private void parseArrayDeclarator() {
do{
final PsiBuilder.Marker mark = b.mark();
match(LEFT_BRACKET, "Expected '['.");
if (b.getTokenType() != RIGHT_BRACKET) {
parseConstantExpression();
}
match(RIGHT_BRACKET, "Missing closing ']' after array declarator.");
mark.done(ARRAY_DECLARATOR);
}while(b.getTokenType() == LEFT_BRACKET); //Parse all ARRAY_DECLARATOR's if multidimensional array
}
private boolean parseInitializer() {
// initializer: assignment_expression
if (b.getTokenType() == LEFT_BRACE) {
parseInitializerList();
} else {
PsiBuilder.Marker mark = b.mark();
if (!parseAssignmentExpression()) {
mark.error("Expected initializer");
return false;
}
mark.done(INITIALIZER);
}
return true;
}
private void parseInitializerList() {
// initializer_list: '{' initializer (',' initializer)* ','? '}'
PsiBuilder.Marker mark = b.mark();
match(LEFT_BRACE, "Expected '{'");
if (b.getTokenType() != RIGHT_BRACE) parseInitializer();
while (b.getTokenType() != RIGHT_BRACE && !eof()) {
match(COMMA, "Expected '}' or ','");
if (b.getTokenType() == RIGHT_BRACE) break;
if (!parseInitializer()) { b.advanceLexer(); }
}
match(RIGHT_BRACE, "Expected '}'");
mark.done(INITIALIZER_LIST);
}
private boolean parseAssignmentExpression() {
// assignment_expression: conditional_expression
// | unary_expression assignment_operator assignment_expression
// NOTE: both conditional_expression and assignment_expression starts with unary_expression
// CHANGED TO: (to reduce the need for lookahead. use the annotation pass to verify l-values)
// assignment_expression: conditional_expression (assignment_operator conditional_expression)*
PsiBuilder.Marker mark = b.mark();
if (!parseConditionalExpression()) {
mark.drop();
return false;
}
while (tryMatch(ASSIGNMENT_OPERATORS)) {
parseConditionalExpression();
mark.done(ASSIGNMENT_EXPRESSION);
mark = mark.precede();
}
mark.drop();
return true;
}
private boolean parseConditionalExpression() {
// conditional_expression: logical_or_expression
// | logical_or_expression QUESTION expression COLON assignment_expression
PsiBuilder.Marker mark = b.mark();
if (!parseOperatorExpression()) {
mark.drop();
return false;
}
if (tryMatch(QUESTION)) {
parseExpression();
match(COLON, "Missing ':' in ternary operator ?:.");
parseAssignmentExpression();
mark.done(CONDITIONAL_EXPRESSION);
} else {
mark.drop();
}
return true;
}
private boolean parseExpression() {
// experssion: assignment_expression
// | expression COMMA assignment_expression
// transformed to:
// expression: assignment_expression (',' assignment_expression)*
PsiBuilder.Marker mark = b.mark();
if (!parseAssignmentExpression()) {
mark.error("Expected an expression.");
return false;
}
while (tryMatch(COMMA)) {
if (!parseAssignmentExpression()) {
mark.error("Expected an expression.");
return false;
}
mark.done(EXPRESSION);
mark = mark.precede();
}
mark.drop();
return true;
}
private boolean parseOperatorExpression() {
return parseOperatorExpressionLevel(0);
}
private boolean parseOperatorExpression(int level) {
PsiBuilder.Marker mark = b.mark();
if (!parseOperatorExpressionLevel(level + 1)) {
mark.drop();
return false;
}
final OperatorLevelTraits operatorLevel = operatorPrecedence[level];
while (tryMatch(operatorLevel.getOperatorTokens())) {
if (parseOperatorExpressionLevel(level + 1)) {
mark.done(operatorLevel.getElementType());
mark = mark.precede();
} else {
PsiBuilder.Marker operatorMark = b.mark();
outOfPlace:
if (tryMatch(OPERATORS)) {
do {
operatorMark.error("Operator out of place.");
if (parseOperatorExpressionLevel(level + 1)) {
mark.done(operatorLevel.getElementType());
mark = mark.precede();
break outOfPlace;
} else {
operatorMark = b.mark();
}
} while (tryMatch(OPERATORS));
operatorMark.drop();
} else {
operatorMark.drop();
mark.error("Expected a(n) " + operatorLevel.getPartName() + ".");
return false;
}
}
}
mark.drop();
return true;
}
private boolean parseOperatorExpressionLevel(int level) {
if (level == operatorPrecedence.length) {
return parseUnaryExpression();
} else {
return parseOperatorExpression(level);
}
}
private boolean parseUnaryExpression() {
// unary_expression: postfix_expression
// | unary_operator unary_expression
// note: moved INC_OP and DEC_OP to unary_operator
PsiBuilder.Marker mark = b.mark();
if (tryMatch(UNARY_OPERATORS)) {
parseUnaryExpression();
mark.done(PREFIX_OPERATOR_EXPRESSION);
return true;
} else if (parsePostfixExpression()) {
mark.drop();
return true;
} else {
mark.drop();
return false;
}
}
private boolean parsePostfixExpression() {
// postfix_expression: primary_expression
// | postfix_expression '[' expression ']'
// | function_call
// | postfix_expression '.' FIELD_SELECTION
// | postfix_expression INC_OP
// | postfix_expression DEC_OP
// (moved from function_or_method_call:)
// | postfix_expression '.' function_call
PsiBuilder.Marker mark = b.mark();
boolean result;
if (lookAheadFunctionCall(true)) {
result = parseFunctionCall();
} else {
result = parsePrimaryExpression();
}
if (!result) {
mark.drop();
return false;
}
while (true) {
if (tryMatch(LEFT_BRACKET)) {
parseExpression();
match(RIGHT_BRACKET, "Missing ']' after subscript.");
mark.done(SUBSCRIPT_EXPRESSION);
} else if (tryMatch(DOT)) {
if (lookAheadFunctionCall(false)) {
parseFunctionCallImpl(true);
mark.done(METHOD_CALL_EXPRESSION);
} else {
parseFieldIdentifier();
mark.done(FIELD_SELECTION_EXPRESSION);
}
} else if (tryMatch(INC_OP) || tryMatch(DEC_OP)) {
// do nothing as tryMatch consumes the token for us.
mark.done(POSTFIX_OPERATOR_EXPRESSION);
} else {
break;
}
mark = mark.precede();
}
mark.drop();
return true;
}
/**
* Figures out whether the next sequence of tokens denotes a function call and not a field selection.
*
* @return true if the immediately approaching tokens contain a function call, false otherwise
*/
private boolean lookAheadFunctionCall(boolean allowConstructors) {
PsiBuilder.Marker mark = b.mark();
boolean result = false;
if (tryMatch(TYPE_SPECIFIER_NONARRAY_TOKENS)) {
result = true;
} else if (tryMatch(IDENTIFIER)) {
if(allowConstructors && b.getTokenType() == LEFT_BRACKET){
parseArrayDeclarator();
}
if (tryMatch(LEFT_PAREN)) {
result = true;
}
}
mark.rollbackTo();
return result;
}
private boolean parseFunctionCall() {
PsiBuilder.Marker mark = b.mark();
parseFunctionCallImpl(false);
mark.done(FUNCTION_CALL_EXPRESSION);
return true;
}
private void parseFunctionCallImpl(boolean markIdentifierAsMethodIdentifier) {
// parse_function_call : parse_function_call_or_method
// parse_function_call_or_method: function_call_generic
// | postfix_expression '.' function_call_generic
// NOTE: implementing function_call_or_method_directly
// AND: postfix_expression '.' function_call_generic is moved to parsePostfixExpression
parseFunctionIdentifier(markIdentifierAsMethodIdentifier);
match(LEFT_PAREN, "Missing '('.");
parseParameterList();
match(RIGHT_PAREN, "Missing ')'.");
}
/**
* Parses a function, method or constructor identifier.
* If method identifier is requested, METHOD_NAME is always produced.
* If function identifier is requested, either TYPE_SPECIFIER or FUNCTION_NAME is emitted.
* Additionally, in function/constructor mode, one or more ARRAY_DECLARATOR's may be emitted.
* That is because it might be a struct array constructor.
*
* @param markAsMethodIdentifier true -> method mode | false -> function/constructor mode
*/
private boolean parseFunctionIdentifier(boolean markAsMethodIdentifier) {
// function_identifier: IDENTIFIER //function/method call
// | type_name [ array_declarator ] //constructor
if(!markAsMethodIdentifier){
//Methods can't be constructors
PsiBuilder.Marker constructorMark = b.mark();
if(parseTypeSpecifier(true)){//true -> only built-in type specifiers
//Success, it is definitely a constructor
constructorMark.drop();// (parseTypeSpecifier has added a type specifier element)
return true;
}else{
constructorMark.rollbackTo();
}
}
PsiBuilder.Marker mark = b.mark();
if(tryMatch(IDENTIFIER)){
//Function/method call
mark.done(markAsMethodIdentifier ? METHOD_NAME : FUNCTION_NAME);
//Search for "[x]" AFTER marking the IDENTIFIER, because it is not part of the identifier
if(!markAsMethodIdentifier && b.getTokenType() == LEFT_BRACKET){
//If it is a constructor, it may be an array constructor.
parseArrayDeclarator();
}
return true;
}else{
if(markAsMethodIdentifier) mark.error("Expected method identifier.");
else mark.error("Expected function identifier.");
return false;
}
}
private void parseParameterList() {
// parameter_list: VOID | (nothing)
// | assignment_expression (',' assignment_expression)
PsiBuilder.Marker mark = b.mark();
if (b.getTokenType() == VOID_TYPE) {
b.advanceLexer();
} else //noinspection StatementWithEmptyBody
if (b.getTokenType() == RIGHT_PAREN) {
// do nothing
} else if (parseAssignmentExpression()) {
while (tryMatch(COMMA)) {
if (!parseAssignmentExpression()) {
b.error("Assignment expression expected.");
break;
}
}
} else {
mark.error("Expression expected after '('.");
return;
}
mark.done(PARAMETER_LIST);
}
private boolean parsePrimaryExpression() {
// primary_expression: variable_identifier
// | CONSTANT
// | '(' expression ')'
final PsiBuilder.Marker mark = b.mark();
final IElementType type = b.getTokenType();
if (type == IDENTIFIER) {
final PsiBuilder.Marker mark2 = b.mark();
b.advanceLexer();
mark2.done(VARIABLE_NAME);
mark.done(VARIABLE_NAME_EXPRESSION);
return true;
} else if (tryMatch(CONSTANT_TOKENS)) {
mark.done(CONSTANT_EXPRESSION);
return true;
} else if (type == LEFT_PAREN) {
b.advanceLexer();
if (!parseExpression()) {
if (b.getTokenType() == RIGHT_PAREN) {
b.error("Expected expression after '('");
} else {
mark.error("Expected expression after '('");
return false;
}
}
match(RIGHT_PAREN, "Missing ')'");
mark.done(GROUPED_EXPRESSION);
return true;
} else {
mark.error("Expected constant, variable identifier or a '(' ')' group");
return false;
}
}
private String parseIdentifier() {
final PsiBuilder.Marker mark = b.mark();
boolean success = b.getTokenType() == IDENTIFIER;
if (success) {
String name = b.getTokenText();
b.advanceLexer();
mark.done(VARIABLE_NAME);
return name;
} else {
mark.error("Expected an identifier.");
return null;
}
}
private String parseFieldIdentifier() {
final PsiBuilder.Marker mark = b.mark();
boolean success = b.getTokenType() == IDENTIFIER;
if (success) {
String name = b.getTokenText();
b.advanceLexer();
mark.done(FIELD_NAME);
return name;
} else {
mark.error("Expected an identifier.");
return null;
}
}
/**
* Parse all allowed type specifiers
*/
private boolean parseTypeSpecifier(){
return parseTypeSpecifier(false);
}
/**
* Parse type specifier.
* onlyBuildIn can be used to accept as types only tokens in TYPE_SPECIFIER_NONARRAY_TOKENS.
* This can be used in for example parsing constructors.
*
* @param onlyBuiltIn if true, only build-in type specifiers are considered valid
*/
private boolean parseTypeSpecifier(boolean onlyBuiltIn) {
// type_specifier_noarray
// type_specifier_noarray "[" const_expr "]"
final PsiBuilder.Marker mark = b.mark();
if (!parseTypeSpecifierNoArray(onlyBuiltIn)) {
mark.drop();
return false;
}
if (b.getTokenType() == LEFT_BRACKET) {
parseArrayDeclarator();
}
mark.done(TYPE_SPECIFIER);
return true;
}
private boolean parseConstantExpression() {
// constant_expression: conditional_expression
return parseConditionalExpression();
}
private boolean parseTypeSpecifierNoArray(boolean onlyBuiltIn) {
// type_specifier_noarray: all_built_in_types
// | struct_specifier
// | type_name
// todo: implement | INVARIANT IDENTIFIER (vertex only)
// note: This also accepts IDENTIFIERS
final PsiBuilder.Marker mark = b.mark();
if (!onlyBuiltIn && b.getTokenType() == STRUCT) {
parseStructSpecifier();
mark.done(TYPE_SPECIFIER_STRUCT);
} else if (TYPE_SPECIFIER_NONARRAY_TOKENS.contains(b.getTokenType())) {
b.advanceLexer();
mark.done(TYPE_SPECIFIER_PRIMITIVE);
} else if (!onlyBuiltIn && b.getTokenType() == IDENTIFIER) {
parseIdentifier();
mark.done(TYPE_SPECIFIER_STRUCT_REFERENCE);
} else {
mark.error("Expected a type specifier.");
return false;
}
return true;
}
private void parseStructSpecifier() {
// struct_specifier: STRUCT IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE
// | STRUCT LEFT_BRACE struct_delcaration_list RIGHT_BRACE
// note: these are the same except the first is named
match(STRUCT, "Expected 'struct'.");
if (b.getTokenType() == IDENTIFIER) {
parseIdentifier();
}
match(LEFT_BRACE, "'{' expected after 'struct'.");
parseStructDeclarationList();
match(RIGHT_BRACE, "Closing '}' for struct expected.");
}
private void parseStructDeclarationList() {
// struct_declaration_list: struct_declaration (',' struct_declaration)*
// note: we should initially find ',' for a new declarator or '}' at the end of the struct
final PsiBuilder.Marker mark = b.mark();
if (b.getTokenType() == RIGHT_BRACE) {
b.error("Empty struct is not allowed.");
}
while (GLSLTokenTypes.TYPE_SPECIFIER_NONARRAY_TOKENS.contains(b.getTokenType()) ||
b.getTokenType() == GLSLTokenTypes.IDENTIFIER) {
parseStructDeclaration();
}
mark.done(STRUCT_DECLARATION_LIST);
}
private void parseStructDeclaration() {
// type_specifier struct_declarator_list ';'
final PsiBuilder.Marker mark = b.mark();
parseQualifiedTypeSpecifier();
parseStructDeclaratorList();
match(SEMICOLON, "Expected ';' after struct declaration.");
mark.done(STRUCT_DECLARATION);
}
private void parseStructDeclaratorList() {
// struct_declarator_list: struct_declarator (',' struct_declarator)*
final PsiBuilder.Marker mark = b.mark();
do {
if (eof(mark)) return;
parseStructOrParameterDeclarator(STRUCT_DECLARATOR);
} while (tryMatch(COMMA));
mark.done(STRUCT_DECLARATOR_LIST);
}
/**
* Parses a struct declarator or a parameter declarator depending on the argument.
*
* @param type equals either STRUCT_DECLARATOR or PARAMETER_DECLARATOR.
*/
private void parseStructOrParameterDeclarator(IElementType type) {
// struct_declarator: IDENTIFIER [ '[' constant_expression ']' ]
// -OR-
// parameter_declarator: IDENTIFIER [ '[' constant_expression ']' ]
assert type == STRUCT_DECLARATOR || type == PARAMETER_DECLARATOR;
final PsiBuilder.Marker mark = b.mark();
parseIdentifier();
if (b.getTokenType() == LEFT_BRACKET) {
parseArrayDeclarator();
}
PsiBuilder.Marker declaratorEnd = b.mark();
if (tryMatch(EQUAL)) {
parseInitializer();
declaratorEnd.error("Initializer not allowed here.");
} else {
declaratorEnd.drop();
}
mark.done(type);
}
private void parseQualifierList(boolean validPlacement) {
final PsiBuilder.Marker mark = b.mark();
if (!validPlacement && !parseQualifier()) {
mark.drop();
return;
}
//noinspection StatementWithEmptyBody
while (parseQualifier()) {
}
if (validPlacement) {
mark.done(QUALIFIER_LIST);
} else {
mark.error("Qualifier not allowed here.");
}
}
private boolean parseQualifier() {
// qualifier: layout_qualifier
// | subroutine_qualifier
// | qualifier_token
if(parseLayoutQualifier())return true;
if(parseSubroutineQualifier())return true;
if (QUALIFIER_TOKENS.contains(b.getTokenType())) {
final PsiBuilder.Marker mark = b.mark();
b.advanceLexer();
mark.done(QUALIFIER);
return true;
}
return false;
}
private boolean parseLayoutQualifierStatement(){
// layout_qualifier_statement: layout_qualifier interface_qualifier ';'
// (Made up name.) Can be only in global level.
// Since it looks like variable declaration up until ';', it will return true and parse only
// if the semicolon is present.
// NOTE: interface_qualifier is in, out or uniform
final PsiBuilder.Marker mark = b.mark();
if(!parseLayoutQualifier()){
mark.rollbackTo();
return false;
}
if(!tryMatch(INTERFACE_QUALIFIER_TOKENS)){
mark.rollbackTo();
return false;
}
if(!tryMatch(SEMICOLON)){
mark.rollbackTo();
return false;
}
mark.done(LAYOUT_QUALIFIER_STATEMENT);
return true;
}
private boolean parseLayoutQualifier(){
// layout_qualifier: LAYOUT '(' layout_qualifier_id_list ')'
if(b.getTokenType() == LAYOUT_KEYWORD){
final PsiBuilder.Marker mark = b.mark();
b.advanceLexer();
match(LEFT_PAREN, "Expected '('");
parseLayoutQualifierList();
match(RIGHT_PAREN, "Expected ')'");
mark.done(QUALIFIER);
return true;
}else return false;
}
private void parseSubroutineTypeName(){
final PsiBuilder.Marker typeNameMark = b.mark();
if(b.getTokenType() == IDENTIFIER){
b.advanceLexer();
typeNameMark.done(TYPE_SPECIFIER);
}else{
typeNameMark.error("Subroutine type name expected");
}
}
private boolean parseSubroutineQualifier(){
// subroutine_qualifier: SUBROUTINE ['(' TYPE_NAME [',' TYPE_NAME]* ')']?
if(b.getTokenType() == SUBROUTINE_KEYWORD){
final PsiBuilder.Marker mark = b.mark();
b.advanceLexer();
if(tryMatch(LEFT_PAREN)){
parseSubroutineTypeName();
while(b.getTokenType() == COMMA){
b.advanceLexer();
parseSubroutineTypeName();
}
match(RIGHT_PAREN, "Expected ')'");
}
mark.done(QUALIFIER);
return true;
}else return false;
}
private void parseLayoutQualifierList() {
// layout_qualifier_id_list: layout_qualifier_id (COMMA layout_qualifier_id)*
parseLayoutQualifierElement();
while (tryMatch(COMMA)) {
parseLayoutQualifierElement();
}
}
private void parseLayoutQualifierElement() {
// layout_qualifier_id: IDENTIFIER [ EQUAL constant_expression ]
// | SHARED
final PsiBuilder.Marker mark = b.mark();
if (tryMatch(IDENTIFIER)) {
if (tryMatch(EQUAL)) {
if (!parseConstantExpression()) b.error("Expected constant expression");
}
} else if (!tryMatch(SHARED_KEYWORD)) {
mark.error("Expected 'shared' or an identifier");
return;
}
mark.done(LAYOUT_QUALIFIER_ID);
}
private final static class OperatorLevelTraits {
private final TokenSet operatorTokens;
private final String partName;
private final IElementType elementType;
private OperatorLevelTraits(TokenSet operatorTokens, String partName, IElementType elementType) {
this.operatorTokens = operatorTokens;
this.partName = partName;
this.elementType = elementType;
}
public TokenSet getOperatorTokens() {
return operatorTokens;
}
public String getPartName() {
return partName;
}
public IElementType getElementType() {
return elementType;
}
}
}