WALA/com.ibm.wala.cast/source/java/com/ibm/wala/cast/util/CAstPattern.java

499 lines
14 KiB
Java
Raw Normal View History

/******************************************************************************
* Copyright (c) 2002 - 2006 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 - initial API and implementation
*****************************************************************************/
package com.ibm.wala.cast.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.ibm.wala.cast.tree.CAstNode;
import com.ibm.wala.util.debug.Assertions;
import com.ibm.wala.util.debug.Trace;
public class CAstPattern {
private static boolean DEBUG_PARSER = false;
private static boolean DEBUG_MATCH = false;
private final static int CHILD_KIND = -1;
private final static int CHILDREN_KIND = -2;
private final static int REPEATED_PATTERN_KIND = -3;
private final static int ALTERNATIVE_PATTERN_KIND = -4;
private final static int OPTIONAL_PATTERN_KIND = -5;
private final static int REFERENCE_PATTERN_KIND = -6;
private final static int IGNORE_KIND = -99;
private final String name;
private final int kind;
private final String value;
private final CAstPattern[] children;
private final Map<String, CAstPattern> references;
public static class Segments extends TreeMap<String,Object> {
public CAstNode getSingle(String name) {
Assertions._assert(containsKey(name), name);
return (CAstNode) get(name);
}
@SuppressWarnings("unchecked")
public List<Object> getMultiple(String name) {
if (!containsKey(name)) {
return Collections.emptyList();
} else {
Object o = get(name);
if (o instanceof CAstNode) {
return Collections.singletonList(o);
} else {
Assertions._assert(o instanceof List);
return (List<Object>) o;
}
}
}
private void addAll(Segments other) {
for (Iterator xs = other.entrySet().iterator(); xs.hasNext();) {
Map.Entry e = (Map.Entry) xs.next();
String name = (String) e.getKey();
if (e.getValue() instanceof CAstNode) {
add(name, (CAstNode) e.getValue());
} else {
for (Iterator vs = ((List) e.getValue()).iterator(); vs.hasNext();) {
add(name, (CAstNode) vs.next());
}
}
}
}
@SuppressWarnings("unchecked")
private void add(String name, CAstNode result) {
if (containsKey(name)) {
Object o = get(name);
if (o instanceof List) {
((List<CAstNode>) o).add(result);
} else {
Assertions._assert(o instanceof CAstNode);
List<Object> x = new ArrayList<Object>();
x.add(o);
x.add(result);
put(name, x);
}
} else {
put(name, result);
}
}
}
public CAstPattern(String name, int kind, CAstPattern[] children) {
this.name = name;
this.kind = kind;
this.value = null;
this.children = children;
this.references = null;
}
public CAstPattern(String name, String value) {
this.name = name;
this.kind = IGNORE_KIND;
this.value = value;
this.children = null;
this.references = null;
}
public CAstPattern(String patternName, Map<String, CAstPattern> references) {
this.name = null;
this.kind = REFERENCE_PATTERN_KIND;
this.value = patternName;
this.references = references;
this.children = null;
}
public String toString() {
StringBuffer sb = new StringBuffer();
if (name != null) {
sb.append("<").append(name).append(">");
}
if (value != null) {
if (kind == REFERENCE_PATTERN_KIND) {
sb.append("ref:").append(value);
} else {
sb.append("literal:").append(value);
}
} else if (kind == CHILD_KIND) {
sb.append("*");
} else if (kind == CHILDREN_KIND) {
sb.append("**");
} else if (kind == REPEATED_PATTERN_KIND) {
sb.append("@");
} else if (kind == ALTERNATIVE_PATTERN_KIND) {
sb.append("|");
} else if (kind == OPTIONAL_PATTERN_KIND) {
sb.append("?");
} else {
sb.append(CAstPrinter.kindAsString(kind));
}
if (children != null) {
sb.append("(");
for (int i = 0; i < children.length; i++) {
sb.append(children[i].toString());
if (i == children.length - 1) {
sb.append(")");
} else {
sb.append(",");
}
}
}
return sb.toString();
}
private static boolean matchChildren(CAstNode tree, int i, CAstPattern[] cs, int j, Segments s) {
if (i >= tree.getChildCount() && j >= cs.length) {
return true;
} else if (i < tree.getChildCount() && j >= cs.length) {
return false;
} else if (i >= tree.getChildCount() && j < cs.length) {
switch (cs[j].kind) {
case CHILDREN_KIND:
case OPTIONAL_PATTERN_KIND:
case REPEATED_PATTERN_KIND:
return matchChildren(tree, i, cs, j + 1, s);
default:
return false;
}
} else {
if (cs[j].kind == CHILD_KIND) {
if (DEBUG_MATCH) {
Trace.println("* matches " + CAstPrinter.print(tree.getChild(i)));
}
if (s != null && cs[j].name != null) {
s.add(cs[j].name, tree.getChild(i));
}
return matchChildren(tree, i + 1, cs, j + 1, s);
} else if (cs[j].kind == CHILDREN_KIND) {
if (tryMatchChildren(tree, i, cs, j + 1, s)) {
if (DEBUG_MATCH) {
Trace.println("** matches nothing");
}
return true;
} else {
if (DEBUG_MATCH) {
Trace.println("** matches " + CAstPrinter.print(tree.getChild(i)));
}
if (s != null && cs[j].name != null) {
s.add(cs[j].name, tree.getChild(i));
}
return matchChildren(tree, i + 1, cs, j, s);
}
} else if (cs[j].kind == REPEATED_PATTERN_KIND) {
CAstPattern repeatedPattern = cs[j].children[0];
if (repeatedPattern.tryMatch(tree.getChild(i), s)) {
if (s != null && cs[j].name != null) {
s.add(cs[j].name, tree.getChild(i));
}
if (DEBUG_MATCH) {
Trace.println(cs[j] + " matches " + CAstPrinter.print(tree.getChild(i)));
}
return matchChildren(tree, i + 1, cs, j, s);
} else {
if (DEBUG_MATCH) {
Trace.println(cs[j] + " matches nothing");
}
return matchChildren(tree, i, cs, j + 1, s);
}
} else if (cs[j].kind == OPTIONAL_PATTERN_KIND) {
if (tryMatchChildren(tree, i, cs, j + 1, s)) {
if (DEBUG_MATCH) {
Trace.println(cs[j] + " matches nothing");
}
return true;
} else {
CAstPattern optionalPattern = cs[j].children[0];
if (optionalPattern.tryMatch(tree.getChild(i), s)) {
if (DEBUG_MATCH) {
Trace.println(cs[j] + " matches " + CAstPrinter.print(tree.getChild(i)));
}
return matchChildren(tree, i + 1, cs, j + 1, s);
} else {
return false;
}
}
} else {
return cs[j].match(tree.getChild(i), s) && matchChildren(tree, i + 1, cs, j + 1, s);
}
}
}
public boolean match(CAstNode tree, Segments s) {
if (DEBUG_MATCH) {
Trace.println("matching " + this + " against " + CAstPrinter.print(tree));
}
if (kind == REFERENCE_PATTERN_KIND) {
return references.get(value).match(tree, s);
} else if (kind == ALTERNATIVE_PATTERN_KIND) {
for (int i = 0; i < children.length; i++) {
if (children[i].tryMatch(tree, s)) {
if (s != null && name != null)
s.add(name, tree);
return true;
}
}
if (DEBUG_MATCH) {
Trace.println("match failed (a)");
}
return false;
} else {
if ((value == null) ? tree.getKind() != kind : (tree.getKind() != CAstNode.CONSTANT || !value.equals(tree.getValue()
.toString()))) {
if (DEBUG_MATCH) {
Trace.println("match failed (b)");
}
return false;
}
if (s != null && name != null)
s.add(name, tree);
if (children == null || children.length == 0) {
if (DEBUG_MATCH && tree.getChildCount() != 0) {
Trace.println("match failed (c)");
}
return tree.getChildCount() == 0;
} else {
return matchChildren(tree, 0, children, 0, s);
}
}
}
private static boolean tryMatchChildren(CAstNode tree, int i, CAstPattern[] cs, int j, Segments s) {
if (s == null) {
return matchChildren(tree, i, cs, j, s);
} else {
Segments ss = new Segments();
boolean result = matchChildren(tree, i, cs, j, ss);
if (result)
s.addAll(ss);
return result;
}
}
private boolean tryMatch(CAstNode tree, Segments s) {
if (s == null) {
return match(tree, s);
} else {
Segments ss = new Segments();
boolean result = match(tree, ss);
if (result)
s.addAll(ss);
return result;
}
}
public static Segments match(CAstPattern p, CAstNode n) {
Segments s = new Segments();
if (p.match(n, s)) {
return s;
} else {
return null;
}
}
public static CAstPattern parse(String patternString) {
try {
return (new Parser(patternString)).parse();
} catch (NoSuchFieldException e) {
Assertions.UNREACHABLE("no such kind in pattern: " + e.getMessage());
return null;
} catch (IllegalAccessException e) {
Assertions.UNREACHABLE("internal error in CAstPattern" + e);
return null;
}
}
private static class Parser {
private final Map<String, CAstPattern> namedPatterns = new HashMap<String, CAstPattern>();
private final String patternString;
private int start;
private int end;
private Parser(String patternString) {
this.patternString = patternString;
}
// private Parser(String patternString, int start) {
// this(patternString);
// this.start = start;
// }
private String parseName(boolean internal) {
if (patternString.charAt(start) == (internal ? '{' : '<')) {
int nameStart = start + 1;
int nameEnd = patternString.indexOf(internal ? '}' : '>', nameStart);
start = nameEnd + 1;
return patternString.substring(nameStart, nameEnd);
} else {
return null;
}
}
public CAstPattern parse() throws NoSuchFieldException, IllegalAccessException {
if (DEBUG_PARSER) {
Trace.println("parsing " + patternString.substring(start));
}
String internalName = parseName(true);
String name = parseName(false);
CAstPattern result;
if (patternString.charAt(start) == '`') {
int strEnd = patternString.indexOf('`', start + 1);
end = strEnd + 1;
String patternName = patternString.substring(start + 1, strEnd);
Assertions._assert(internalName == null);
result = new CAstPattern(patternName, namedPatterns);
} else if (patternString.charAt(start) == '"') {
int strEnd = patternString.indexOf('"', start + 1);
end = strEnd + 1;
result = new CAstPattern(name, patternString.substring(start + 1, strEnd));
} else if (patternString.startsWith("**", start)) {
end = start + 2;
result = new CAstPattern(name, CHILDREN_KIND, null);
} else if (patternString.startsWith("*", start)) {
end = start + 1;
result = new CAstPattern(name, CHILD_KIND, null);
} else if (patternString.startsWith("|(", start)) {
List<CAstPattern> alternatives = new ArrayList<CAstPattern>();
start += 2;
do {
alternatives.add(parse());
start = end + 2;
} while (patternString.startsWith("||", end));
Assertions._assert(patternString.startsWith(")|", end), patternString);
end += 2;
result = new CAstPattern(name, ALTERNATIVE_PATTERN_KIND, alternatives.toArray(new CAstPattern[alternatives.size()]));
} else if (patternString.startsWith("@(", start)) {
start += 2;
CAstPattern children[] = new CAstPattern[] { parse() };
Assertions._assert(patternString.startsWith(")@", end));
end += 2;
if (DEBUG_PARSER) {
Trace.println("repeated pattern: " + children[0]);
}
result = new CAstPattern(name, REPEATED_PATTERN_KIND, children);
} else if (patternString.startsWith("?(", start)) {
start += 2;
CAstPattern children[] = new CAstPattern[] { parse() };
Assertions._assert(patternString.startsWith(")?", end));
end += 2;
if (DEBUG_PARSER) {
Trace.println("optional pattern: " + children[0]);
}
result = new CAstPattern(name, OPTIONAL_PATTERN_KIND, children);
} else {
int kindEnd = patternString.indexOf('(', start);
String kindStr = patternString.substring(start, kindEnd);
Field kindField = CAstNode.class.getField(kindStr);
int kind = kindField.getInt(null);
if (patternString.charAt(kindEnd + 1) == ')') {
end = kindEnd + 2;
result = new CAstPattern(name, kind, null);
} else {
List<CAstPattern> children = new ArrayList<CAstPattern>();
start = patternString.indexOf('(', start) + 1;
do {
children.add(parse());
start = end + 1;
if (DEBUG_PARSER) {
Trace.println("parsing children: " + patternString.substring(end));
}
} while (patternString.charAt(end) == ',');
Assertions._assert(patternString.charAt(end) == ')');
end++;
result = new CAstPattern(name, kind, children.toArray(new CAstPattern[children.size()]));
}
}
if (internalName != null) {
namedPatterns.put(internalName, result);
}
return result;
}
}
}