001package eu.cqse.check.framework.preprocessor.iec61131;
002
003import java.util.ArrayList;
004import java.util.EnumSet;
005import java.util.List;
006
007import org.conqat.lib.commons.collections.CollectionUtils;
008import org.conqat.lib.commons.string.StringUtils;
009
010import eu.cqse.check.framework.preprocessor.IPreprocessor;
011import eu.cqse.check.framework.scanner.ETokenType;
012import eu.cqse.check.framework.scanner.IToken;
013
014/**
015 * Preprocessing of {@link eu.cqse.check.framework.scanner.ELanguage#IEC61131}
016 * files. Most files are returned without change. We need this to fix files
017 * stored by the IDE PLCNext from PhoenixContact.
018 */
019public class Iec61131Preprocessor implements IPreprocessor {
020
021        @Override
022        public List<IToken> preprocess(String uniformPath, List<IToken> tokens) {
023                String dirPath = StringUtils.removeLastPart(uniformPath, '/');
024                // string comparison with == intended since removeLastPart returns the input
025                // object if '/' not found. Next line is the intellij annotation to hide the
026                // warning
027                // noinspection StringEquality
028                if (tokens.isEmpty() || dirPath == uniformPath || !dirPath.endsWith(".pou") || !uniformPath.endsWith(".st")) {
029                        return tokens;
030                }
031                // We assume that this is a file stored by Phoenix Contact's PLCNext IDE.
032                return preprocessPlcNextFile(tokens, uniformPath);
033        }
034
035        /**
036         * Preprocesses an ST-Code file stored by Phoenix Contact's PLCNext IDE. Inserts
037         * FUNCTION_BLOCK, NAME, and FUNCTION_BLOCK_END tokens.
038         * 
039         * The IDE stores ST-Code in xyz.pou/ directories. One directory per POU
040         * (Program organization unit). Each can contain multiple .st files. Somehow
041         * they do not write the FUNCTION_BLOCK NAME / END_FUNCTION_BLOCK tokens. The
042         * files could also be FUNCTION type this is stored in a .meta file in the
043         * directory, but for teamscale the difference between FUNCTION and
044         * FUNCTION_BLOCK does probably not matter.
045         * 
046         * @param tokens
047         *            non-empty list of tokens of an st-code file stored by PLCNext
048         */
049        private static List<IToken> preprocessPlcNextFile(List<IToken> tokens, String uniformPath) {
050                IToken firstToken = tokens.get(0);
051                if (EnumSet.of(ETokenType.FUNCTION_BLOCK, ETokenType.FUNCTION).contains(firstToken.getType())) {
052                        return tokens;
053                }
054                String blockName = StringUtils.stripSuffix(StringUtils.getLastPart(uniformPath, '/'), ".st").replaceAll(" ",
055                                "_");
056                IToken lastToken = CollectionUtils.getLast(tokens);
057                IToken start = lastToken.newToken(ETokenType.FUNCTION_BLOCK, 0, 0, "FUNCTION_BLOCK",
058                                "##preprocessor-generated##");
059                IToken blockNameToken = lastToken.newToken(ETokenType.IDENTIFIER, 0, 0, blockName,
060                                "##preprocessor-generated##");
061                IToken end = lastToken.newToken(ETokenType.END_FUNCTION_BLOCK, lastToken.getOffset(), lastToken.getLineNumber(),
062                                "END_FUNCTION_BLOCK", "##preprocessor-generated##");
063                List<IToken> result = new ArrayList<>(tokens.size() + 2);
064                result.add(start);
065                result.add(blockNameToken);
066                result.addAll(tokens);
067                result.add(end);
068                return result;
069        }
070}