/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.coldFusion.model.parsers; import com.intellij.coldFusion.CfmlBundle; import com.intellij.coldFusion.model.CfmlUtil; import com.intellij.coldFusion.model.lexer.CfscriptTokenTypes; import com.intellij.lang.ASTNode; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.tree.IElementType; import com.intellij.util.containers.Stack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import static com.intellij.coldFusion.model.lexer.CfmlTokenTypes.*; /** * Created by Lera Nikolaenko * Date: 13.10.2008 */ public class CfmlParser implements PsiParser { private static final Logger LOG = Logger.getInstance("#com.intellij.coldFusion.model.parsers.CfmlParser"); private static class Tag { public String myTagName; public PsiBuilder.Marker myMarkerOfBegin; public PsiBuilder.Marker myMarkerOfContent; public Tag(String string, PsiBuilder.Marker marker, PsiBuilder.Marker content) { myTagName = string; myMarkerOfBegin = marker; myMarkerOfContent = content; } } public static IElementType getElementTypeForTag(@NotNull String tagName) { if ("cfcomponent".equals(tagName.toLowerCase()) || "cfinterface".equals(tagName.toLowerCase())) { return CfmlElementTypes.COMPONENT_TAG; } else if ("cffunction".equals(tagName.toLowerCase())) { return CfmlElementTypes.FUNCTION_TAG; } else if ("cfinvoke".equals(tagName.toLowerCase())) { return CfmlElementTypes.INVOKE_TAG; } else if ("cfargument".equals(tagName.toLowerCase())) { return CfmlElementTypes.ARGUMENT_TAG; } else if ("cfscript".equals(tagName.toLowerCase())) { return CfmlElementTypes.SCRIPT_TAG; } else if ("cfproperty".equals(tagName.toLowerCase())) { return CfmlElementTypes.PROPERTY_TAG; } else if ("cfimport".equals(tagName.toLowerCase())) { return CfmlElementTypes.TAG_IMPORT; } else if ("cfloop".endsWith(tagName.toLowerCase())) { return CfmlElementTypes.FORTAGEXPRESSION; } return CfmlElementTypes.TAG; } @NotNull public ASTNode parse(final IElementType root, final PsiBuilder builder) { Stack<Tag> tagNamesStack = new Stack<>(); // builder.setDebugMode(true); final PsiBuilder.Marker marker = builder.mark(); // parse component if (builder.getTokenType() == CfscriptTokenTypes.COMMENT || builder.getTokenType() == CfscriptTokenTypes.COMPONENT_KEYWORD || builder.getTokenType() == CfscriptTokenTypes.INTERFACE_KEYWORD || builder.getTokenType() == CfscriptTokenTypes.IMPORT_KEYWORD) { (new CfscriptParser()).parseScript(builder, true); if (!builder.eof()) { builder.error(CfmlBundle.message("cfml.parsing.unexpected.token")); } while (!builder.eof()) { builder.advanceLexer(); } } else { while (!builder.eof()) { if (builder.getTokenType() == OPENER) { parseOpenTag(builder, tagNamesStack); } else if (builder.getTokenType() == LSLASH_ANGLEBRACKET) { parseCloseTag(builder, tagNamesStack); } else { builder.advanceLexer(); } } while (!tagNamesStack.isEmpty()) { Tag tag = tagNamesStack.pop(); // tag.myMarkerOfBegin.drop(); if (CfmlUtil.isUserDefined(tag.myTagName) || !CfmlUtil.isEndTagRequired(tag.myTagName, builder.getProject())) { tag.myMarkerOfBegin.doneBefore(getElementTypeForTag(tag.myTagName), tag.myMarkerOfContent); } else { tag.myMarkerOfBegin.doneBefore(getElementTypeForTag(tag.myTagName), tag.myMarkerOfContent, CfmlBundle.message("cfml.parsing.element.is.not.closed", tag.myTagName)); } tag.myMarkerOfContent.drop(); } } marker.done(root); return builder.getTreeBuilt(); } private static void parseExpression(PsiBuilder builder) { if (builder.getTokenType() == START_EXPRESSION) { builder.advanceLexer(); } else { return; } if (!(new CfmlExpressionParser(builder)).parseStructureDefinition()) { if (!(new CfmlExpressionParser(builder)).parseArrayDefinition()) { (new CfmlExpressionParser(builder)).parseExpression(); } } if (builder.getTokenType() != END_EXPRESSION) { builder.error(CfmlBundle.message("cfml.parsing.expression.unclosed")); } else if (!builder.eof()) { builder.advanceLexer(); } } private static void readValue(PsiBuilder builder, IElementType typeOfValue) { // reading string if (typeOfValue != null) { if (builder.getTokenType() == SINGLE_QUOTE || builder.getTokenType() == DOUBLE_QUOTE) { builder.advanceLexer(); PsiBuilder.Marker marker = builder.mark(); int valueStartOffset = builder.getCurrentOffset(); while (!builder.eof() && builder.getTokenType() != SINGLE_QUOTE_CLOSER && builder.getTokenType() != DOUBLE_QUOTE_CLOSER && !CfmlUtil.isControlToken(builder.getTokenType())) { if (builder.getTokenType() == START_EXPRESSION) { parseExpression(builder); } else { builder.advanceLexer(); } } if (builder.getCurrentOffset() != valueStartOffset) { marker.done(typeOfValue); } else { marker.drop(); } if (builder.getTokenType() != SINGLE_QUOTE_CLOSER && builder.getTokenType() != DOUBLE_QUOTE_CLOSER) { builder.error(CfmlBundle.message("cfml.parsing.unexpected.token")); } else { builder.advanceLexer(); } return; } else if (!CfmlUtil.isControlToken(builder.getTokenType()) && builder.getTokenType() != ATTRIBUTE) { (new CfmlExpressionParser(builder)).parseExpression(); return; } } // reading what is comming up to the next control token while (!builder.eof() && !CfmlUtil.isControlToken(builder.getTokenType()) && builder.getTokenType() != ATTRIBUTE) { if (builder.getTokenType() == START_EXPRESSION) { parseExpression(builder); } else { builder.advanceLexer(); } } } private static boolean doNeedNamedAttribute(String tagName) { if (tagName == null) { return false; } return !(tagName.toLowerCase().equals("cffunction") || tagName.toLowerCase().equals("cfargument")); } public static void parseAttributes(PsiBuilder builder, String tagName, IElementType attributeType, boolean strict) { if (tagName.equals("cfset")) { // parsing statement int cfset tag (new CfmlExpressionParser(builder)).parseStatement(); return; } else if (tagName.equals("cfif") || tagName.equals("cfelseif") || tagName.equals("cfreturn")) { // parsin expression in condition tags (new CfmlExpressionParser(builder)).parseExpression(); return; } while (!builder.eof() && !CfmlUtil.isControlToken(builder.getTokenType())) { if (builder.getTokenType() == attributeType || builder.getTokenType() == CfscriptTokenTypes.DEFAULT_KEYWORD || (tagName.equalsIgnoreCase("cfproperty") && builder.getTokenType() == CfscriptTokenTypes.ABORT_KEYWORD)) { @SuppressWarnings({"ConstantConditions"}) String attributeName = builder.getTokenText().toLowerCase(); PsiBuilder.Marker attrMarker = builder.mark(); // PsiBuilder.Marker attrNameMarker = myBuilder.mark(); builder.advanceLexer(); /* if (!CfmlUtil.isAttribute(tagName, attributeName)) { attrNameMarker.error(CfmlBundle.message("cfml.parsing.no.such.attribute", tagName)); } else { attrNameMarker.drop(); } */ if (builder.getTokenType() != ASSIGN) { attrMarker.done(CfmlElementTypes.ATTRIBUTE); builder.error(CfmlBundle.message("cfml.parsing.no.value")); continue; } builder.advanceLexer(); if ("name".equals(attributeName)) { readValue(builder, CfmlElementTypes.ATTRIBUTE_VALUE); if (doNeedNamedAttribute(tagName)) { attrMarker.done(CfmlElementTypes.NAMED_ATTRIBUTE); } else { attrMarker.done(CfmlElementTypes.ATTRIBUTE_NAME); } } else if ("method".equals(attributeName) && "cfinvoke".equals(tagName)) { readValue(builder, CfmlElementTypes.REFERENCE_EXPRESSION); attrMarker.done(CfmlElementTypes.TAG_FUNCTION_CALL); } else if ("index".equals(attributeName) && "cfloop".equals(tagName)) { readValue(builder, CfmlElementTypes.ATTRIBUTE_VALUE); attrMarker.done(CfmlElementTypes.FORTAGINDEXATTRIBUTE); } else { readValue(builder, CfmlElementTypes.ATTRIBUTE_VALUE); attrMarker.done(CfmlElementTypes.ATTRIBUTE); } } /*else if (myBuilder.getTokenType() == IDENTIFIER && hasAttrs) { PsiBuilder.Marker attrMarker = myBuilder.mark(); advance(); attrMarker.error(CfmlBundle.message("cfml.parsing.no.such.attribute", tagName)); } */ else { if (strict) { return; } builder.advanceLexer(); } } } private static boolean parseCloser(PsiBuilder builder) { if (!builder.eof() && !CfmlUtil.isControlToken(builder.getTokenType())) { builder.error(CfmlBundle.message("cfml.parsing.unexpected.token")); builder.advanceLexer(); while (!builder.eof() && !CfmlUtil.isControlToken(builder.getTokenType())) { builder.advanceLexer(); } } if (builder.getTokenType() == CLOSER) { builder.advanceLexer(); return true; } builder.error(CfmlBundle.message("cfml.parsing.tag.is.not.done")); return false; } private static boolean parseCloseTag(PsiBuilder builder, Stack<Tag> tagNamesStack) { builder.advanceLexer(); if (builder.getTokenType() == CF_TAG_NAME) { @SuppressWarnings({"ConstantConditions"}) String closeTagName = builder.getTokenText().toLowerCase(); // eating tag name builder.advanceLexer(); // canParse = if in the stack somewhere (not necessary on the top) there is the same tag name boolean canParse = false; for (Tag t : tagNamesStack) { if (t.myTagName.equals(closeTagName)) { canParse = true; break; } } // drop all markers with unclosed tags if (canParse) { Tag tag = null; while (!tagNamesStack.empty() && !((tag = tagNamesStack.pop()).myTagName.equals(closeTagName))) { if (CfmlUtil.isUserDefined(tag.myTagName) || !CfmlUtil.isEndTagRequired(tag.myTagName, builder.getProject())) { tag.myMarkerOfBegin.doneBefore(getElementTypeForTag(tag.myTagName), tag.myMarkerOfContent); } else { tag.myMarkerOfBegin.doneBefore(getElementTypeForTag(tag.myTagName), tag.myMarkerOfContent, CfmlBundle.message("cfml.parsing.element.is.not.closed", tag.myTagName)); } tag.myMarkerOfContent.drop(); } parseCloser(builder); if (tag != null) { tag.myMarkerOfContent.drop(); tag.myMarkerOfBegin.done(getElementTypeForTag(tag.myTagName)); } return true; } else { builder.error(CfmlBundle.message("cfml.parsing.closing.tag.matches.nothing")); parseCloser(builder); return false; } } return false; } private static void parseOpenTag(PsiBuilder builder, Stack<Tag> tagNamesStack) { PsiBuilder.Marker marker; String currentTagName; while (!builder.eof()) { marker = builder.mark(); builder.advanceLexer(); // parsing tag name if (builder.getTokenType() == CF_TAG_NAME) { currentTagName = builder.getTokenText().toLowerCase(); builder.advanceLexer(); } else { builder.error(CfmlBundle.message("cfml.parsing.unexpected.token")); marker.drop(); continue; } parseAttributes(builder, currentTagName, ATTRIBUTE, false); // if CLOSER found than the check, if tag can be single performed, // if closing tag found, check if it matches the opening one, otherwise continue cicle if (builder.eof()) { builder.error(CfmlBundle.message("cfml.parsing.tag.is.not.done")); marker.done(getElementTypeForTag(currentTagName)); return; } if (builder.getTokenType() == CLOSER) { builder.advanceLexer(); marker.done(getElementTypeForTag(currentTagName)); } else if (builder.getTokenType() == R_ANGLEBRACKET) { builder.advanceLexer(); // ignore cfscript tag for psi tree depth decreasing //if (!"cfscript".equals(currentTagName.toLowerCase())) { tagNamesStack.push(new Tag(currentTagName, marker, builder.mark())); //} } else { /* PsiBuilder.Marker contentMarker = myBuilder.mark(); marker.doneBefore(CF_TAG_NAME, contentMarker, CfmlBundle.message("cfml.parsing.element.is.not.closed", currentTagName)); contentMarker.drop(); */ builder.error(CfmlBundle.message("cfml.parsing.tag.is.not.done")); tagNamesStack.push(new Tag(currentTagName, marker, builder.mark())); // marker.error(CfmlBundle.message("cfml.parsing.tag.is.not.done")); } if (currentTagName.toLowerCase().equals("cfscript")) { (new CfscriptParser()).parseScript(builder, true); /* final String closing = swallowClosing(builder); if (closing == null || !"cfscript".equals(closing.toLowerCase())) { builder.error(CfmlBundle.message("cfml.parsing.element.is.not.closed", "cfscript")); } marker.drop(); */ } while (!builder.eof() && builder.getTokenType() != OPENER) { if (builder.getTokenType() == LSLASH_ANGLEBRACKET) { parseCloseTag(builder, tagNamesStack); } else if (builder.getTokenType() == START_EXPRESSION) { parseExpression(builder); } else { builder.advanceLexer(); } } } } @Nullable private static String swallowClosing(PsiBuilder builder) { if (compareAndEat(builder, LSLASH_ANGLEBRACKET)) { String tagName = builder.getTokenText(); if (compareAndEat(builder, CF_TAG_NAME) && compareAndEat(builder, CLOSER)) { return tagName; } } return null; } private static boolean compareAndEat(PsiBuilder builder, IElementType type) { if (builder.getTokenType() != type) { return false; } builder.advanceLexer(); return true; } /* protected final void advance() { builder.advanceLexer(); } private void error(final String message) { builder.error(message); } private PsiBuilder.Marker mark() { return builder.mark(); } private IElementType token() { return builder.getTokenType(); } */ public String toString() { return "CfmlParser"; } }