package com.ochafik.lang.jnaerator;
import com.ochafik.lang.jnaerator.parser.Declaration;
import com.ochafik.lang.jnaerator.parser.DeclarationsHolder;
import com.ochafik.lang.jnaerator.parser.Element;
import com.ochafik.lang.jnaerator.parser.SourceFile;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.anarres.cpp.LexerException;
/**
* mvn compile exec:java -Dexec.mainClass=com.ochafik.lang.jnaerator.CSlicer
*/
public class CSlicer {
private CharSequence getContent() {
return content;
}
private void pop(BlockType blockType) {
if (blocks.isEmpty()) {
throw new RuntimeException("Empty blocks, got closing " + blockType);
}
if (blocks.pop() != blockType) {
System.out.println("bad pop");
}
}
enum BlockType {
Paren, Curly, Square
}
enum State {
String, Char, Normal, StringEscape, CharEscape,
NormalSlash, SingleLineComment, MultilineComment, StarInMultilineComment
}
State state = State.Normal;
Stack<BlockType> blocks = new Stack<BlockType>();
StringBuilder content = new StringBuilder();
int semiColonCount;
void appendLine(String s) {
for (char c : s.toCharArray()) {
append(c);
}
append('\n');
}
private void append(char c) {
if (state == State.NormalSlash) {
switch (c) {
case '/':
state = State.SingleLineComment;
return;
case '*':
state = State.MultilineComment;
return;
default:
content.append('/');
state = State.Normal;
}
}
switch (state) {
case Char:
switch (c) {
case '\'':
state = State.Normal;
break;
case '\\':
state = State.CharEscape;
break;
}
break;
case String:
switch (c) {
case '"':
state = State.Normal;
break;
case '\\':
state = State.StringEscape;
break;
}
break;
case CharEscape:
state = State.Char;
break;
case StringEscape:
state = State.String;
break;
case SingleLineComment:
if (c == '\n') {
state = State.Normal;
}
break;
case MultilineComment:
if (c == '*') {
state = State.StarInMultilineComment;
}
break;
case StarInMultilineComment:
if (c == '/') {
state = State.Normal;
return;
} else {
state = State.MultilineComment;
}
break;
case Normal:
switch (c) {
case '/':
state = State.NormalSlash;
break;
case '\'':
state = State.Char;
break;
case '"':
state = State.String;
break;
case '(':
blocks.push(BlockType.Paren);
break;
case '{':
blocks.push(BlockType.Curly);
break;
case '[':
blocks.push(BlockType.Square);
break;
case ')':
pop(BlockType.Paren);
break;
case '}':
pop(BlockType.Curly);
break;
case ']':
pop(BlockType.Square);
break;
case ';':
semiColonCount++;
break;
}
break;
default:
throw new RuntimeException("state unhandled : " + state);
}
switch (state) {
case MultilineComment:
case SingleLineComment:
case NormalSlash:
case StarInMultilineComment:
break;
default:
content.append(c);
}
}
boolean isParsable() {
return state == State.Normal && blocks.isEmpty();
}
public int getSemiColonCount() {
return semiColonCount;
}
public static String removeComments(String s) {
CSlicer tester = new CSlicer();
tester.appendLine(s);
return tester.getContent().toString();
//return s.replaceAll("(?m)/(/.*$|\\*(\\*[^/]|[^*])*?\\*/)", " ");
}
// public static boolean[] computeCumulativeParseability(String[] lines) {
// int n = lines.length;
// boolean[] ret = new boolean[n];
// Tester tester = new Tester();
// for (int i = 0; i < n; i++) {
// String line = lines[i];
// tester.append(line);
// ret[i] = tester.isParsable();
// }
// return ret;
// }
public static String[] getLines(String source) {
String[] lines = source.split("\n");
List<String> ret = new ArrayList<String>(lines.length);
for (String line : lines) {
String s = line.trim();
if (s.length() != 0) {
ret.add(s);
}
}
return ret.toArray(new String[ret.size()]);
}
interface Callback {
boolean apply(CharSequence content, int semiColonCount);
}
public static void getParseableIncrements(String source, Callback cb) {
String[] lines = getLines(source);
CSlicer tester = new CSlicer();
for (String line : lines) {
//System.out.print('.');
tester.appendLine(line);
if (tester.isParsable()) {
if (!cb.apply(tester.getContent(), tester.getSemiColonCount())) {
break;
}
} else {
//System.err.println("Not parseable : state = " + tester.state + ", blocks = " + StringUtils.implode(tester.blocks, ","));
}
}
}
public static void main(String[] args) throws IOException, LexerException {
String source = preprocess(new File("test.h"));
source = removeComments(source);
getParseableIncrements(source, new Callback() {
int lastDeclCount, lastSemiCount, lastContentLength;
int nErr;
@Override
public boolean apply(CharSequence content, int semiColonCount) {
try {
//System.out.print('c');
if (semiColonCount == lastSemiCount) {
return true;
}
String source = content.toString() + ";";
List<Declaration> decls = parseDeclarations(source);
int nDecls = decls.size();
int length = content.length();
System.out.println("Parsed " + nDecls + " declarations in " + length + " chars");
if (nDecls == lastDeclCount) {
String diff = content.subSequence(lastContentLength, length).toString();
String errFile = "err-" + (++nErr) + ".h";
System.out.println("Failed to parse new declaration in : \n\t" + diff.replaceAll("\n", "\n\t") + "\nCode was saved in " + errFile);
System.out.println("Last parsed declaration :");
System.out.println("\t" + String.valueOf(decls.get(decls.size() - 1)).replaceAll("\n", "\n\t"));
PrintStream out = new PrintStream(new FileOutputStream(errFile));
out.println(source);
out.println("#if 0 // Here's the diff to the last compilable code :");
out.println(diff);
out.println("#endif");
out.println("#if 0 // Here's the last parsed declaration");
out.println(String.valueOf(decls.get(decls.size() - 1)));
out.println("#endif");
out.close();
//return false;
}
lastContentLength = length;
lastSemiCount = semiColonCount;
lastDeclCount = nDecls;
return true;
} catch (Throwable ex) {
Logger.getLogger(CSlicer.class.getName()).log(Level.SEVERE, null, ex);
return false;
}
}
});
}
static String preprocess(File source) throws IOException, LexerException {
JNAeratorConfig config = new JNAeratorConfig();
JNAeratorConfigUtils.autoConfigure(config);
config.preprocessorConfig.implicitIncludes.add("C:\\program files\\Microsoft SDKs\\Windows\\v7.0A\\Include");
config.preprocessorConfig.implicitIncludes.add("C:\\program files\\Microsoft Visual Studio 10.0\\VC\\include");
//config.preprocessorConfig.includeStrings.add(source);
config.addSourceFile(source, null, false, true, true);
Result result = new Result(config, null, null);
String pre = PreprocessorUtils.preprocessSources(config, Collections.EMPTY_LIST, false, result.typeConverter, null);
return pre;
}
static List<Declaration> parseDeclarations(String source) throws IOException, LexerException, InterruptedException {
JNAeratorConfig config = new JNAeratorConfig();
JNAeratorConfigUtils.autoConfigure(config);
//config.noCPlusPlus = true;
config.preprocessorConfig.includeStrings.add(source);
Result result = new Result(config, null, null);
SourceFiles parse = new JNAeratorParser().parse(config, result.typeConverter, null);
List<Declaration> ret = new ArrayList<Declaration>();
flatten(parse, ret);
return ret;
}
static void flatten(Element element, List<Declaration> out) {
if (element instanceof SourceFiles) {
for (SourceFile f : ((SourceFiles) element).getSourceFiles()) {
flatten(f, out);
}
}
if (element instanceof DeclarationsHolder) {
for (Declaration d : ((DeclarationsHolder) element).getDeclarations()) {
flatten(d, out);
}
} else if (element instanceof Declaration) {
out.add((Declaration) element);
}
}
}