package com.laytonsmith.core.compiler; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.constructs.CFunction; import com.laytonsmith.core.constructs.CNull; import com.laytonsmith.core.exceptions.ConfigCompileException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.List; /** * A keyword is a value in code that knows how to transform certain code into other * formats, at a lexer/compiler level. During lexing, if a keyword is registered, the * compiler will mark it as such (as compared to a bare string) and then during the compilation * stage, once the keyword is reached in the stream, the stream (and keyword location) * will be passed to the keyword handler for processing. In order for keywords to * be dynamically introduced, the keyword implementation must be tagged with the * keyword annotation. */ public abstract class Keyword { private static final String __CBRACE__ = new com.laytonsmith.core.functions.Compiler.__cbrace__().getName(); protected Keyword(){ // } /** * Sent upon reaching a keyword in the parse tree. The full list of arguments * at the current stack depth, * as well as the keyword position will be sent, and the keyword is allowed * to make any necessary changes, including throwing a {@link ConfigCompileException} * if necessary. * @param list The argument list at the current depth as it currently exists. Note that the list will * have already been lightly processed. * @param keywordPosition The keyword position * @return The position at which the compiler should continue processing from. Often times this * will just be {@code keywordPosition}, but may be different if need be. * @throws ConfigCompileException If the tree is in an invalid state, and the keyword * needs to cause an exception to be thrown. */ public abstract int process(List<ParseTree> list, int keywordPosition) throws ConfigCompileException; /** * Returns the keyword name, or null, if this class isn't tagged with the @keyword annotation. * @return */ public final String getKeywordName(){ keyword k = this.getClass().getAnnotation(keyword.class); return k == null ? null : k.value(); } /** * Convenience function to allow keywords to more easily check if this is a valid code block. If not, * a {@link ConfigCompileException} is thrown for you. A code block is considered valid if it has * is a cbrace device, and has 0 or 1 arguments. The message is what should be shown if the * node is not a cbrace device at all. If the argument count is wrong, this function creates the * error message. * @param node The node to check * @param message The message to display if the node is not a cbrace device. * @throws ConfigCompileException */ protected void validateCodeBlock(ParseTree node, String message) throws ConfigCompileException{ if(node.getChildren().size() > 1){ throw new ConfigCompileException("Unexpected number of arguments in code block", node.getTarget()); } if(!isCodeBlock(node)){ throw new ConfigCompileException(message, node.getTarget()); } } /** * Returns true if {@link #validateCodeBlock(com.laytonsmith.core.ParseTree, java.lang.String)} would not * cause an exception to be thrown. This is useful for conditionally doing something with the keyword * if a code block exists, which is often the case for functions that are also keywords. * @param node * @return */ protected boolean isValidCodeBlock(ParseTree node){ try { validateCodeBlock(node, ""); return true; } catch(ConfigCompileException ex){ return false; } } /** * Returns true if the node is a code block or not. The argument count to the block * is not considered. * @param node * @return */ protected static boolean isCodeBlock(ParseTree node){ return node.getData() instanceof CFunction && node.getData().val().equals(__CBRACE__); } /** * Returns a CNull, if the node is empty, or the first argument * to the node * @param node * @return */ protected static ParseTree getArgumentOrNull(ParseTree node){ if(node.getChildren().isEmpty()){ return new ParseTree(CNull.NULL, node.getFileOptions()); } else { return node.getChildAt(0); } } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public static @interface keyword { /** * The name of the keyword. * @return */ String value(); } }