blob: 6f3da6fc26f8b3817d7c4f7d9926893a9c136f99 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2018 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Neil Guzman - perl implementation
*******************************************************************************/
package org.eclipse.linuxtools.internal.rpmstubby.parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.linuxtools.internal.rpmstubby.StubbyLog;
/**
* Class to parse a Perl Makefile.PL to grab specfile properties
*
*/
public class PerlMakefileParser {
/*
* Perl Regular Expressions
*
*/
private static final String WHITE_SPACE = "(?:\\s+)";
private static final String COMMENT = "#.+"; // # comment
private static final String LINE_WITH_COMMENT = "(?:(.*)#.+)"; // line # comment
private static final String VARIABLE_PARAMS = "(?:my|local|our)";
private static final String WORD = "(\\b\\w+\\b)"; // variable
private static final String NON_WHITE_SPACE = "(\\S+)";
private static final String VARIABLE = "(?:" + VARIABLE_PARAMS
+ WHITE_SPACE + ")?(?:\\$|@|%)" + WORD + WHITE_SPACE + "?"; // %variable | my $variable | our @variable
private static final String ASSOCIATIVE_KEY = WHITE_SPACE + "?"
+ NON_WHITE_SPACE + WHITE_SPACE + "?";
private static final String EXCLUDE_SPECIALS = "(?![=~|\\-\\*\\+\\/])";
private static final String NON_CONDITIONAL = "(?!(?:\\s*if|elsif|unless))";
private static final String ASSIGNMENT_OPERATOR = "=";
private static final String ASSOCIATIVE_OPERATOR = "=>";
private static final String SIMPLE_ASSIGNMENT = NON_CONDITIONAL
+ WHITE_SPACE + "?" + VARIABLE + "(?:" + ASSIGNMENT_OPERATOR + ")"
+ EXCLUDE_SPECIALS + "(?:(.+))"; // %var = value || [1] = [2]
private static final String ASSOCIATIVE_ASSIGNMENT = ASSOCIATIVE_KEY
+ ASSOCIATIVE_OPERATOR + "(?:(.+))"; // 'key' => 'value' || [1] => [2]
private static final String FUNCTION = "\\s*" + WORD
+ "*\\s*?\\((.*)\\)\\s*"; // foo(bar) | foo(foo(bar)) | foo() || [1]([2])
private static final String BEGIN_END = "(?:[^#]*<<END)"; // [CS] test<<END | <<END
private static final String END_END = "^END$"; // [CS]
private static final String BEGIN_BC = "^=(?!cut)[a-z]\\S+(\\s)?\\S+"; // [CS] =test | =test test
private static final String END_BC = "^=cut$"; // [CS]
private static final String MAKEFILE_FUNCTION_NAME = "WriteMakefile";
private static final String MAKEFILE_FUNCTION = "^.*"
+ MAKEFILE_FUNCTION_NAME + WHITE_SPACE + "?\\(.*$";
/*
* A few common opening characters and their closing counterparts
*
*/
private static final Map<Character, Character> SURROUNDING_CHARACTER;
static {
Map<Character, Character> aMap = new HashMap<>();
aMap.put('[', ']');
aMap.put('{', '}');
aMap.put('(', ')');
aMap.put(']', '[');
aMap.put('}', '{');
aMap.put(')', '(');
SURROUNDING_CHARACTER = Collections.unmodifiableMap(aMap);
}
private static final char SQUARE_BRACKET = '[';
private static final char CURLY_BRACKET = '{';
private static final char ROUND_BRACKET = '(';
private static final int IN_BRACKETS = 0x00000001;
private static final int MAKE_FUNCTION = 0x00000002;
private IFile file;
private Map<String, String> mVariableDefinitions;
private Map<String, String> mMakefileDefinitions;
/**
* Initialize.
*
* @param file
* The perl Makefile.
* @throws CoreException
* Throws CoreException.
* @throws IOException
* Throws IOException.
*/
public PerlMakefileParser(IFile file) throws IOException, CoreException {
mVariableDefinitions = new HashMap<>();
mMakefileDefinitions = new HashMap<>();
if (file.getContents().available() <= 0) {
return;
}
this.file = file;
parse();
}
/**
* Parse the perl Makefile.
*
*/
public void parse() {
String content = "";
String line = "";
try (Scanner variableScanner = new Scanner(file.getContents())) {
grabSimpleDefinitions(cleanUpContent(variableScanner));
cleanupVariables(mVariableDefinitions);
resolveVariables(mVariableDefinitions);
/*
* Going through the makefile function's attributes
*/
if (!mVariableDefinitions.containsKey(MAKEFILE_FUNCTION_NAME)) {
return;
}
try (Scanner makefileScanner = new Scanner(
mVariableDefinitions.get(MAKEFILE_FUNCTION_NAME))) {
makefileScanner.useDelimiter("(?<=,)");
ArrayList<String> makefileList = new ArrayList<>();
while (makefileScanner.hasNext()) {
line = makefileScanner.next();
if (matchesAssociativeAssignment(line)) {
makefileList.add(line);
} else if (!makefileList.isEmpty()) {
makefileList.set(makefileList.size() - 1, makefileList
.get(makefileList.size() - 1).concat(line));
}
}
for (String str : makefileList) {
content = content.concat(str + '\n');
}
grabAssociativeDefinitions(content);
cleanupVariables(mMakefileDefinitions);
resolveVariables(mMakefileDefinitions);
}
variableScanner.close();
} catch (CoreException e) {
StubbyLog.logError(e);
}
}
/**
* Get the value of the variable.
*
* @param key The variable to get the value of.
* @return The value of the variable.
*/
public String getValue(String key) {
String rc = "";
if (mMakefileDefinitions.containsKey(key)) {
rc = mMakefileDefinitions.get(key);
}
return rc;
}
/**
* Get the list of values from the variable.
*
* @param key The variable to get the value of.
* @return The list of values from the variable.
*/
public List<String> getValueList(String key) {
List<String> rc = new ArrayList<>();
String var = "";
if (mMakefileDefinitions.containsKey(key)) {
var = mMakefileDefinitions.get(key);
String[] tmp = var.split(",");
for (String str : tmp) {
str = cleanUpString(str);
rc.add(str);
}
}
return rc;
}
/**
* Grab the simple key->value pairs from some content.
*
* @param content
* The content to grab the key->value pairs from.
*/
private void grabSimpleDefinitions(String content) {
try (Scanner scanner = new Scanner(content)) {
Stack<Character> brackets = new Stack<>();
String key = "";
String value = "";
String tempVar = "";
String line = "";
String[] tmp;
int flags = 0;
while (scanner.hasNext()) {
line = scanner.nextLine();
if (matchesSimpleAssignment(line)) {
tmp = line.split("=");
key = removeVariableSigils(tmp[0]).toLowerCase()
.replaceAll("\\W", "");
value = tmp[1];
if (containsOpener(value)) {
flags |= IN_BRACKETS;
} else {
mVariableDefinitions.put(key, value);
}
} else if (containsMakefileFunction(line)) {
flags |= MAKE_FUNCTION;
flags |= IN_BRACKETS;
}
if ((flags & IN_BRACKETS) == IN_BRACKETS) {
checkBrackets(brackets, line);
tempVar = tempVar.concat(line.trim());
if (brackets.isEmpty()) {
if ((flags & MAKE_FUNCTION) == MAKE_FUNCTION) {
mVariableDefinitions
.putAll(extractFunction(tempVar));
} else {
tmp = tempVar.split("=[^>]");
key = removeVariableSigils(tmp[0]).toLowerCase()
.replaceAll("\\W", "");
value = tmp[1];
mVariableDefinitions.put(key, value);
}
tempVar = "";
flags &= 0;
}
}
}
}
}
/**
* Grab the associative key=>value pairs from some content.
*
* @param content
* The content to grab the key=>value pairs from.
*/
private void grabAssociativeDefinitions(String content) {
try (Scanner scanner = new Scanner(content)) {
Stack<Character> brackets = new Stack<>();
String key = "";
String value = "";
String tempVar = "";
String line = "";
String[] tmp;
int flags = 0;
while (scanner.hasNext()) {
line = scanner.nextLine();
if (matchesAssociativeAssignment(line)
&& (flags & IN_BRACKETS) != IN_BRACKETS) {
tmp = line.split("=>");
key = removeVariableSigils(tmp[0].toLowerCase().replaceAll(
"\\W", ""));
value = tmp[1];
if (containsOpener(value)) {
flags |= IN_BRACKETS;
} else {
mMakefileDefinitions.put(key, value);
}
}
if ((flags & IN_BRACKETS) == IN_BRACKETS) {
checkBrackets(brackets, line);
tempVar = tempVar.concat(line.trim());
if (brackets.isEmpty()) {
key = removeVariableSigils(tempVar
.substring(0, tempVar.indexOf("=>"))
.toLowerCase().replaceAll("\\W", ""));
value = tempVar.substring(tempVar.indexOf("=>") + 2);
mMakefileDefinitions.put(key, value);
tempVar = "";
flags &= 0;
}
}
}
}
}
/**
* Check if the line is within a set of brackets. Update the stack tracking
* bracket completeness.
*
* @param brackets
* The stack tracking the bracket completeness.
* @param line
* The line to parse for brackets.
*/
public static void checkBrackets(Stack<Character> brackets, String line) {
for (char c : line.toCharArray()) {
if (c == SQUARE_BRACKET || c == CURLY_BRACKET || c == ROUND_BRACKET) {
brackets.push(c);
} else if (c == SURROUNDING_CHARACTER.get(SQUARE_BRACKET)
|| c == SURROUNDING_CHARACTER.get(CURLY_BRACKET)
|| c == SURROUNDING_CHARACTER.get(ROUND_BRACKET)) {
if (brackets.peek() == SURROUNDING_CHARACTER.get(c)
&& !brackets.isEmpty()) {
brackets.pop();
}
}
}
}
/**
* Go through the map of key->value pairings and check and see if a key has
* another key as a value. If so, resolve that.
*
* @param variables
* The key->value pairings.
*/
private void resolveVariables(Map<String, String> variables) {
String tempVal = "";
for (Entry<String,String> entry : variables.entrySet()) {
tempVal = entry.getValue();
if (mVariableDefinitions.containsKey(tempVal)) {
variables.put(entry.getKey(), mVariableDefinitions.get(tempVal));
}
}
}
/**
* Extract the function name and the contents of the function and return it
* as a key->value pairing.
*
* @param line
* The line to extract the function from.
* @return The key->value pairing of function and parameter(s).
*/
public static Map<String, String> extractFunction(String line) {
Map<String, String> rc = new HashMap<>();
Pattern pattern = Pattern.compile(FUNCTION, Pattern.CASE_INSENSITIVE);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.find()) {
rc.put(variableMatcher.group(1), variableMatcher.group(2));
}
return rc;
}
/**
* Extract the key->value pairing from an associative assignment.
*
* @param line
* The line to extract the function from.
* @return The key->value pairing of function and parameter(s).
*/
public static Map<String, String> extractKeyValueAssociation(String line) {
Map<String, String> rc = new HashMap<>();
String key = "";
String value = "";
if (matchesAssociativeAssignment(line)) {
String[] keyValue = line.split("=>");
key = cleanUpString(keyValue[0].toLowerCase());
value = cleanUpString(keyValue[1]);
rc.put(key, value);
}
return rc;
}
/**
* Utility to go through the map of variables to clean up their values.
*
* @param variables
* The map of variables to go through.
*/
private static void cleanupVariables(Map<String, String> variables) {
String val = "";
for (Entry<String,String> entry : variables.entrySet()) {
val = cleanUpString(removeVariableSigils(entry.getValue()))
.trim();
if (val.startsWith("(") || val.startsWith("[")
|| val.startsWith("{")) {
val = val.substring(1);
}
if (val.endsWith(")") || val.endsWith("]") || val.endsWith("}")) {
val = val.substring(0, val.length() - 1);
}
val = cleanUpString(val);
variables.put(entry.getKey(), val);
}
}
/**
* Utility to clean up the line of some unwanted characters.
*
* @param line
* The line to clean up.
* @return The cleaned up version of the line.
*/
private static String cleanUpString(String line) {
line = line.trim().replaceAll("('|\")", "");
if (line.endsWith(";")) {
line = line.substring(0, line.length() - 1);
}
if (line.endsWith(",")) {
line = line.substring(0, line.length() - 1);
}
return line;
}
/**
* Remove the variable sigils.
*
* @param variable
* The variable to remove sigils from.
* @return A word only variable with no sigils.
*/
public static String removeVariableSigils(String variable) {
return variable
.replaceAll(
"(\\bmy\\b|\\bour\\b|\\blocal\\b|(\\\\)?\\$|(\\\\)?@|(\\\\)?%)",
"");
}
/**
* Clean up the contents of the file and remove all the comments, END
* blocks, and block comments.
*
* @param scanner
* The scanner of the file to be read.
* @return The cleaned up content.
*/
private static String cleanUpContent(Scanner scanner) {
String rc = "";
String line = "";
boolean flagEND = true;
boolean flagBC = true;
while (scanner.hasNext()) {
line = scanner.nextLine();
// ignore lines between ENDS
if (containsBeginEND(line)) {
flagEND = false;
} else if (matchesEndEND(line)) {
line = scanner.nextLine();
flagEND = true; // true
}
// ignore lines between =someword and =cut
if (matchesBeginBC(line)) {
flagBC = false;
} else if (matchesEndBC(line)) {
line = scanner.nextLine();
flagBC = true;
}
// remove the comments
if (matchesLineWithComment(line)) {
line = line.replaceAll(COMMENT, "");
}
// if not empty line or within comment/END block
if (flagEND && flagBC && !line.trim().equals("")) {
rc = rc.concat(line + '\n');
}
}
return rc;
}
/**
* Check if a line contains a function.
*
* @param line
* The line to check.
* @return True if the line contains a function.
*/
public static boolean containsFunction(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(FUNCTION, Pattern.CASE_INSENSITIVE);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.find()) {
rc = true;
}
return rc;
}
/**
* Check to see if the line contains the WriteMakefile function.
*
* @param line
* The line to check.
* @return True if the line contains the WriteMakefile function.
*/
public static boolean containsMakefileFunction(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(MAKEFILE_FUNCTION,
Pattern.CASE_INSENSITIVE);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.find()) {
rc = true;
}
return rc;
}
/**
* Check to see if the line contains an opening bracket.
*
* @param line
* The line to check.
* @return True if the line contains an opening bracket.
*/
public static boolean containsOpener(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile("(\\(|\\[|\\{)",
Pattern.CASE_INSENSITIVE);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.find()) {
rc = true;
}
return rc;
}
/**
* Check if a line contains <<END. It is case-sensitive.
*
* @param line
* The line to check.
* @return True if the line contains <<END.
*/
public static boolean containsBeginEND(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(BEGIN_END);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.find()) {
rc = true;
}
return rc;
}
/**
* Check if a line is a simple assignment of a variable.
*
* @param line
* The line to check.
* @return True if the line is a simple assignment of a variable.
*/
public static boolean matchesSimpleAssignment(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(SIMPLE_ASSIGNMENT,
Pattern.CASE_INSENSITIVE);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.matches()) {
rc = true;
}
return rc;
}
/**
* Check if a line is a simple assignment of a variable.
*
* @param line
* The line to check.
* @return True if the line is a simple assignment of a variable.
*/
public static boolean matchesAssociativeAssignment(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(ASSOCIATIVE_ASSIGNMENT,
Pattern.CASE_INSENSITIVE);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.matches()) {
rc = true;
}
return rc;
}
/**
* Check if a line matches the END. It is case-sensitive.
*
* @param line
* The line to check.
* @return True if the line matches END.
*/
public static boolean matchesEndEND(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(END_END);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.matches()) {
rc = true;
}
return rc;
}
/**
* Check if a line matches the beginning of a block comment. It is
* case-sensitive.
*
* @param line
* The line to check.
* @return True if the line matches the beginning of a block comment.
*/
public static boolean matchesBeginBC(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(BEGIN_BC);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.matches()) {
rc = true;
}
return rc;
}
/**
* Check if a line matches the end of a block comment (=cut). It is
* case-sensitive.
*
* @param line
* The line to check.
* @return True if the line matches =cut.
*/
public static boolean matchesEndBC(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(END_BC);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.matches()) {
rc = true;
}
return rc;
}
/**
* Check if a line matches that of a line with a comment.
*
* @param line
* The line to check.
* @return True if the line matches that of a line with a comment.
*/
public static boolean matchesLineWithComment(String line) {
boolean rc = false;
Pattern pattern = Pattern.compile(LINE_WITH_COMMENT,
Pattern.CASE_INSENSITIVE);
Matcher variableMatcher = pattern.matcher(line);
if (variableMatcher.matches()) {
rc = true;
}
return rc;
}
}