/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.fandev.lang.fan.parsing.statements.typeDefinitions.members;
import com.intellij.lang.PsiBuilder;
import com.intellij.openapi.util.Key;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.fandev.lang.fan.FanBundle;
import org.fandev.lang.fan.FanElementTypes;
import static org.fandev.lang.fan.FanElementTypes.*;
import static org.fandev.lang.fan.FanTokenTypes.*;
import org.fandev.lang.fan.parsing.auxiliary.facets.Facet;
import org.fandev.lang.fan.parsing.auxiliary.modifiers.Modifiers;
import org.fandev.lang.fan.parsing.expression.Expression;
import org.fandev.lang.fan.parsing.statements.Block;
import org.fandev.lang.fan.parsing.statements.declaration.DeclarationType;
import static org.fandev.lang.fan.parsing.statements.declaration.DeclarationType.INNER_SET;
import org.fandev.lang.fan.parsing.types.TypeSpec;
import org.fandev.lang.fan.parsing.util.ParserUtils;
import static org.fandev.lang.fan.parsing.util.ParserUtils.*;
/**
* <p>Grammar Definition:<ul>
* <li><fieldDef> := <facets> <fieldFlags> [<type>] <id> [":=" <expr>]
* [ "{" [<fieldGetter>] [<fieldSetter>] "}" ] <eos></li>
* <li><fieldFlags> := [<protection>] ["readonly"] ["static"]</li>
* <li><fieldGetter> := "get" (<eos> | <block>)</li>
* <li><fieldSetter> := <protection> "set" (<eos> | <block>)</li>
* </ul></p>
*
* @author Fred Simon
* @date Jan 17, 2009
*/
public class FieldDefinition {
public static final Key<String> FIELD_NAME = new Key<String>("fan.parser.fieldName");
private static final TokenSet FIELD_DEF_STOPPER = TokenSet.create(SEMICOLON, NLS, LBRACE);
public static boolean parse(final PsiBuilder builder) {
final PsiBuilder.Marker declMarker = builder.mark();
Facet.parse(builder);
final TokenSet modifiers = Modifiers.parse(builder, DeclarationType.FIELD);
final boolean modifiersParsed = modifiers.getTypes().length > 0;
final PsiBuilder.Marker beforeType = builder.mark();
if (!TypeSpec.parse(builder)) {
declMarker.drop();
return false;
}
if (!IDENTIFIER_TOKENS_SET.contains(builder.getTokenType())) {
// May be the type took it
beforeType.rollbackTo();
} else {
beforeType.drop();
}
if (!parseName(builder)) {
declMarker.drop();
return false;
}
boolean hasInitValue = false;
// := and { get set } are allowed after the new line. Need a look ahead
IElementType firstTokenAfter = ParserUtils.firstAfter(builder, NLS);
// Default value
if (COLON_EQ.equals(firstTokenAfter)) {
removeNls(builder);
advanceNoNls(builder);
builder.putUserData(FIELD_NAME, "on");
Expression.parseExpr(builder, FIELD_DEF_STOPPER, FIELD_DEFAULT);
builder.putUserData(FIELD_NAME, null);
firstTokenAfter = ParserUtils.firstAfter(builder, NLS);
hasInitValue = true;
}
// Getter Setter blocks
if (LBRACE.equals(firstTokenAfter)) {
removeNls(builder);
if (hasInitValue) {
// If no get, set found inside block, it is for the init value
}
final PsiBuilder.Marker getterSetter = builder.mark();
// NLS after { does not count
advanceNoNls(builder);
parseGetterSetter(builder, getterSetter);
}
if (builder.eof() || !EOS.contains(builder.getTokenType())) {
declMarker.error(FanBundle.message("separator.expected"));
return false;
}
// Need to remove SEPARATOR
ParserUtils.removeStoppers(builder, SEPARATOR, SEPARATOR);
declMarker.done(FIELD_DEFINITION);
return true;
}
private static void parseGetterSetter(final PsiBuilder builder, final PsiBuilder.Marker getterSetter) {
if (testEndGetterSetter(builder, getterSetter)) {
return;
}
PropertyBlock blockType = findPropertyBlockType(builder);
if (blockType == PropertyBlock.GETTER) {
parseGetBlock(builder);
if (testEndGetterSetter(builder, getterSetter)) {
return;
}
blockType = findPropertyBlockType(builder);
}
if (blockType == PropertyBlock.SETTER) {
removeNls(builder);
final PsiBuilder.Marker defMark = builder.mark();
final TokenSet modifiers = Modifiers.parse(builder, INNER_SET);
// TODO: Enforce name is "set"
if (parseName(builder)) {
parseGetSetBlock(builder);
defMark.done(FanElementTypes.SETTER_FIELD_DEFINITION);
} else {
if (modifiers.getTypes().length > 0) {
defMark.error("Found modifiers for setter but no set");
} else {
defMark.error("set block error");
}
}
}
if (!testEndGetterSetter(builder, getterSetter)) {
getterSetter.error("Did not find } or <eos>");
}
}
public static PropertyBlock findPropertyBlockType(final PsiBuilder builder) {
// Look all the following IDENTIFIER or MODIFIERS until you find get or set
// Will stop if anything else pops in {} or else
PropertyBlock blockType = PropertyBlock.NONE;
final PsiBuilder.Marker rb = builder.mark();
removeNls(builder);
// If getting { removing it to read inside the block
if (LBRACE == builder.getTokenType()) {
advanceNoNls(builder);
}
while (!builder.eof()) {
if (IDENTIFIER_TOKENS_SET.contains(builder.getTokenType())) {
final String anId = builder.getTokenText();
if ("get".equals(anId)) {
blockType = PropertyBlock.GETTER;
break;
}
if ("set".equals(anId)) {
blockType = PropertyBlock.SETTER;
break;
}
// Just eat the IDENTIFIER
advanceNoNls(builder);
} else if (ALL_MODIFIERS.contains(builder.getTokenType()) || NLS == builder.getTokenType()) {
// Just eat the MODIFIER and NLS
advanceNoNls(builder);
} else {
// Something else than modifier or identifier.. We stop
break;
}
}
rb.rollbackTo();
return blockType;
}
private static boolean parseGetSetBlock(final PsiBuilder builder) {
removeNls(builder);
if (LBRACE.equals(builder.getTokenType())) {
return Block.parse(builder, METHOD_BODY);
} else if (SEMICOLON.equals(builder.getTokenType())) {
// Just eat it
builder.advanceLexer();
}
return true;
}
/**
* @param builder
* @param getterSetter
* @return true if end of getter setter block reached
*/
private static boolean testEndGetterSetter(final PsiBuilder builder, final PsiBuilder.Marker getterSetter) {
final IElementType firstAfter = ParserUtils.firstAfter(builder, NLS);
if (RBRACE.equals(firstAfter)) {
removeNls(builder);
// finished
builder.advanceLexer();
getterSetter.done(GETTER_SETTER_FIELD_DEFINITION);
return true;
}
return false;
}
private static boolean parseGetBlock(final PsiBuilder builder) {
boolean res;
final PsiBuilder.Marker defMark = builder.mark();
res = parseName(builder);
if (res) {
res = parseGetSetBlock(builder);
}
defMark.done(FanElementTypes.GETTER_FIELD_DEFINITION);
if (!res) {
builder.error("Expected get block");
}
return false;
}
}