/*******************************************************************************
* Copyright (c) 2006,2012 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - Jeff Briggs, Henry Hughes, Ryan Morse
*******************************************************************************/
package org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.tparsers;
import java.text.MessageFormat;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.CommentRemover;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.Messages;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.nodedata.FuncparamNodeData;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.nodedata.FunctionNodeData;
import org.eclipse.linuxtools.systemtap.structures.TreeDefinitionNode;
import org.eclipse.linuxtools.systemtap.structures.TreeNode;
/**
* Runs stap -vp1 in order to get all of the functions
* that are defined in the tapsets. Builds function trees
* with the values obtained from the tapsets.
*
* @author Ryan Morse
* @since 2.0
*/
public final class FunctionParser extends TreeTapsetParser {
private static FunctionParser parser = null;
/**
* The descriptor used for unresolvable types.
*/
public static final String UNKNOWN_TYPE = "unknown"; //$NON-NLS-1$
private static final String FUNC_REGEX = "(?s)(?<!\\w)function\\s+{0}(?:\\s*:\\s*(\\w+))?\\s*\\(([^)]+?)?\\)"; //$NON-NLS-1$
private static final Pattern P_FUNCTION = Pattern.compile("function (?!_)(\\w+) \\(.*?\\)"); //$NON-NLS-1$
private static final Pattern P_PARAM = Pattern.compile("(\\w+)(?:\\s*:\\s*(\\w+))?"); //$NON-NLS-1$
private static final Pattern P_ALL_CAP = Pattern.compile("[A-Z_1-9]*"); //$NON-NLS-1$
private static final Pattern P_RETURN = Pattern.compile("(?<!\\w)return\\W"); //$NON-NLS-1$
public static FunctionParser getInstance() {
if (parser != null) {
return parser;
}
parser = new FunctionParser();
return parser;
}
private FunctionParser() {
super(Messages.FunctionParser_name);
}
/**
* This method is used to build up the list of functions that were found
* during the first pass of stap.
*
* FunctionTree organized as:
* Root->Functions->Parameters
*/
@Override
protected int runAction(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return IStatus.CANCEL;
}
String tapsetContents = SharedParser.getInstance().getTapsetContents();
int result = verifyRunResult(tapsetContents);
if (result != IStatus.OK) {
return result;
}
boolean canceled = false;
try (Scanner scanner = new Scanner(tapsetContents)) {
scanner.useDelimiter("(?=" + SharedParser.TAG_FILE + ")"); //$NON-NLS-1$ //$NON-NLS-2$
while (scanner.hasNext()) {
if (monitor.isCanceled()) {
canceled = true;
break;
}
addFunctionsFromFileContents(scanner.next());
}
}
tree.sortLevel();
return !canceled ? IStatus.OK : IStatus.CANCEL;
}
/**
* Uses the tapset content dump of a single file to collect all
* functions provided by that file.
* @param fileContents The tapset contents of a single file.
*/
private void addFunctionsFromFileContents(String fileContents) {
String filename;
try (Scanner st = new Scanner(fileContents)) {
filename = SharedParser.findFileNameInTag(st.nextLine());
}
Matcher matcher = P_FUNCTION.matcher(fileContents);
String scriptText = null;
while (matcher.find()) {
String functionName = matcher.group(1);
if (P_ALL_CAP.matcher(functionName).matches()) {
// Ignore ALL_CAPS functions, since they are not meant for end-user use.
continue;
}
if (scriptText == null) {
// If this is the first time seeing this file, remove its comments.
scriptText = CommentRemover.execWithFile(filename);
}
addFunctionFromScript(functionName, scriptText, filename);
}
}
/**
* Searches the actual contents of a .stp script file for a specific function, and adds
* @param functionName The name of the function to search for.
* @param scriptText The contents of the script to search, with its comments removed
* (Use {@link CommentRemover} on file contents before passing them here, if necessary).
* @param scriptFilename The name of the script file being searched.
*/
private void addFunctionFromScript(String functionName, String scriptText, String scriptFilename) {
String regex = MessageFormat.format(FUNC_REGEX, functionName);
Matcher mScript = Pattern.compile(regex).matcher(scriptText);
if (mScript.find()) {
String functionLine = mScript.group();
String functionType = mScript.group(1);
// If the function has no return type, look for a "return" statement to check
// if it's really a void function, or if its return type is just unspecified
if (functionType == null && isPatternInScriptBlock(scriptText, mScript.end(), P_RETURN)) {
functionType = UNKNOWN_TYPE;
}
TreeDefinitionNode function = new TreeDefinitionNode(
new FunctionNodeData(functionLine, functionType),
functionName, scriptFilename, true);
tree.add(function);
addParamsFromString(mScript.group(2), function);
}
}
private boolean isPatternInScriptBlock(String scriptText, int start, Pattern p) {
int end, bcount = 1;
start = scriptText.indexOf('{', start) + 1;
for (end = start; end < scriptText.length(); end++) {
char c = scriptText.charAt(end);
if (c == '{') {
bcount++;
} else if (c == '}' && --bcount == 0) {
break;
}
}
return p.matcher(scriptText.substring(start, end)).find();
}
private void addParamsFromString(String params, TreeNode parentFunction) {
if (params != null) {
Matcher mParams = P_PARAM.matcher(params);
while (mParams.find()) {
parentFunction.add(new TreeNode(
new FuncparamNodeData(mParams.group(2)),
mParams.group(1), false));
}
}
parentFunction.sortLevel();
}
@Override
protected int delTapsets(String[] deletions, IProgressMonitor monitor) {
for (int i = 0; i < deletions.length; i++) {
for (int f = 0, fn = tree.getChildCount(); f < fn; f++) {
if (monitor.isCanceled()) {
return IStatus.CANCEL;
}
String definition = ((TreeDefinitionNode) tree.getChildAt(f)).getDefinition();
if (definition != null && definition.startsWith(deletions[i])) {
tree.remove(f--);
fn--;
}
}
}
return IStatus.OK;
}
@Override
protected int addTapsets(String tapsetContents, String[] additions, IProgressMonitor monitor) {
boolean canceled = false;
// Search tapset contents for all files provided by each added directory.
for (int i = 0; i < additions.length; i++) {
int firstTagIndex = 0;
while (true) {
// Get the contents of each file provided by the directory additions[i].
firstTagIndex = tapsetContents.indexOf(
SharedParser.makeFileTag(additions[i]), firstTagIndex);
if (firstTagIndex == -1) {
break;
}
int nextTagIndex = tapsetContents.indexOf(SharedParser.TAG_FILE, firstTagIndex + 1);
String fileContents = nextTagIndex != -1
? tapsetContents.substring(firstTagIndex, nextTagIndex)
: tapsetContents.substring(firstTagIndex);
if (monitor.isCanceled()) {
canceled = true;
break;
}
addFunctionsFromFileContents(fileContents);
// Remove the contents of the file that was just examined from the total contents.
tapsetContents = tapsetContents.substring(0, firstTagIndex).concat(
tapsetContents.substring(firstTagIndex + fileContents.length()));
}
}
tree.sortLevel();
return !canceled ? IStatus.OK : IStatus.CANCEL;
}
}