/*
* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
* Copyright (c) 2010 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
* MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
* ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
* DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for use
* in the design, construction, operation or maintenance of any nuclear
* facility.
*
* Sun gratefully acknowledges that this software was originally authored
* and developed by Kenneth Bradley Russell and Christopher John Kline.
*/
package com.jogamp.gluegen;
import com.jogamp.common.GlueGenVersion;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import antlr.*;
import com.jogamp.gluegen.cgram.*;
import com.jogamp.gluegen.cgram.types.*;
import com.jogamp.gluegen.jcpp.JCPP;
import static java.lang.System.*;
/**
* Glue code generator for C functions and data structures.
*/
public class GlueGen implements GlueEmitterControls {
static{
Logging.init();
}
private final List forcedStructNames = new ArrayList();
private GenericCPP preprocessor;
// State for SymbolFilters
private List allConstants;
private List allFunctions;
private static boolean debug = false;
private static Level logLevel = null;
public static void setDebug(final boolean v) { debug=v; }
public static void setLogLevel(final Level l) { logLevel=l; }
public static boolean debug() { return debug; }
@Override
public void forceStructEmission(final String typedefName) {
forcedStructNames.add(typedefName);
}
@Override
public String findHeaderFile(final String headerFileName) {
return preprocessor.findFile(headerFileName);
}
@Override
public void runSymbolFilter(final SymbolFilter filter) {
filter.filterSymbols(allConstants, allFunctions);
final List newConstants = filter.getConstants();
final List newFunctions = filter.getFunctions();
if (newConstants != null) {
allConstants = newConstants;
}
if (newFunctions != null) {
allFunctions = newFunctions;
}
}
/** GlueGen's build in macro name {@value}, when compiling w/ GlueGen. */
public static final String __GLUEGEN__ = "__GLUEGEN__";
@SuppressWarnings("unchecked")
public void run(final Reader reader, final String filename, final Class> emitterClass, final List includePaths, final List cfgFiles, final String outputRootDir,
final boolean copyCPPOutput2Stderr, final boolean enablePragmaOnce)
{
try {
if(debug) {
Logging.getLogger().setLevel(Level.ALL);
System.err.println("GlueGen.run: filename: "+filename+", emitter: "+emitterClass.getName()+", outputRootDir "+outputRootDir+
", copyCPPOutput2Stderr "+copyCPPOutput2Stderr+", enablePragmaOnce "+enablePragmaOnce);
System.err.println("GlueGen.run: includePaths "+includePaths);
System.err.println("GlueGen.run: cfgFiles "+cfgFiles);
} else if( null != logLevel ) {
Logging.getLogger().setLevel(logLevel);
}
final GlueEmitter emit;
if (emitterClass == null) {
emit = new JavaEmitter();
} else {
try {
emit = (GlueEmitter) emitterClass.newInstance();
} catch (final Exception e) {
throw new RuntimeException("Exception occurred while instantiating emitter class.", e);
}
}
for (final String config : cfgFiles) {
emit.readConfigurationFile(config);
}
final JavaConfiguration cfg = emit.getConfiguration();
final File out = File.createTempFile("CPPTemp", ".cpp");
final FileOutputStream outStream = new FileOutputStream(out);
// preprocessor = new PCPP(includePaths, debug, copyCPPOutput2Stderr, enablePragmaOnce);
preprocessor = new JCPP(includePaths, debug, copyCPPOutput2Stderr, enablePragmaOnce);
final String cppName = preprocessor.getClass().getSimpleName();
if(debug) {
System.err.println("CPP <"+cppName+"> output at (persistent): " + out.getAbsolutePath());
} else {
out.deleteOnExit();
}
preprocessor.addDefine(__GLUEGEN__, "2");
preprocessor.setOut(outStream);
preprocessor.run(reader, filename);
outStream.flush();
outStream.close();
if(debug) {
System.err.println("CPP <"+cppName+"> done");
}
final FileInputStream inStream = new FileInputStream(out);
final DataInputStream dis = new DataInputStream(inStream);
final GnuCLexer lexer = new GnuCLexer(dis);
lexer.setTokenObjectClass(CToken.class.getName());
lexer.initialize();
// Parse the input expression.
final GnuCParser parser = new GnuCParser(lexer);
// set AST node type to TNode or get nasty cast class errors
parser.setASTNodeClass(TNode.class.getName());
TNode.setTokenVocabulary(GNUCTokenTypes.class.getName());
// invoke parser
try {
parser.translationUnit();
} catch (final RecognitionException e) {
throw new RuntimeException("Fatal IO error", e);
} catch (final TokenStreamException e) {
throw new RuntimeException("Fatal IO error", e);
}
final HeaderParser headerParser = new HeaderParser();
headerParser.setDebug(debug);
headerParser.setJavaConfiguration(cfg);
final TypeDictionary td = new TypeDictionary();
headerParser.setTypedefDictionary(td);
final TypeDictionary sd = new TypeDictionary();
headerParser.setStructDictionary(sd);
// set AST node type to TNode or get nasty cast class errors
headerParser.setASTNodeClass(TNode.class.getName());
// walk that tree
headerParser.translationUnit(parser.getAST());
dis.close();
inStream.close();
/**
// For debugging: Dump type dictionary and struct dictionary to System.err
if(debug) {
td.dumpDictionary(err, "All Types");
sd.dumpDictionary(err, "All Structs");
} */
// At this point we have all of the pieces we need in order to
// generate glue code: the #defines to constants, the set of
// typedefs, and the set of functions.
if (null != outputRootDir && outputRootDir.trim().length() > 0) {
if (emit instanceof JavaEmitter) {
// FIXME: hack to interfere with the *Configuration setting via commandlines
final JavaEmitter jemit = (JavaEmitter) emit;
if (null != jemit.getConfig()) {
jemit.getConfig().setOutputRootDir(outputRootDir);
}
}
}
// Repackage the enum and #define statements from the parser into a common format
// so that SymbolFilters can operate upon both identically
allConstants = new ArrayList();
for (final EnumType enumeration : headerParser.getEnums()) {
String enumName = enumeration.getName();
if (enumName.equals("")) {
enumName = null;
}
// iterate over all values in the enumeration
for (int i = 0; i < enumeration.getNumEnumerates(); ++i) {
final EnumType.Enumerator enumerate = enumeration.getEnum(i);
final ConstantDefinition def =
new ConstantDefinition(enumerate.getName(), enumerate.getExpr(),
enumerate.getNumber(),
enumName, enumeration.getASTLocusTag());
allConstants.add(def);
}
}
for (final Object elem : lexer.getDefines()) {
final Define def = (Define) elem;
allConstants.add(new ConstantDefinition(def.getName(), def.getValue(), null, def.getASTLocusTag()));
}
allConstants.addAll(preprocessor.getConstantDefinitions());
allFunctions = headerParser.getParsedFunctions();
// begin emission of glue code,
// incl. firing up 'runSymbolFilter(SymbolFilter)' calls, which:
// - filters all ConstantDefinition
// - filters all FunctionSymbol
emit.beginEmission(this);
if( debug() ) {
int i=0;
System.err.println("Filtered Constants: "+allConstants.size());
for (final ConstantDefinition def : allConstants) {
if( debug() ) {
System.err.println("Filtered ["+i+"]: "+def.getAliasedString());
i++;
}
}
i=0;
System.err.println("Filtered Functions: "+allFunctions.size());
for (final FunctionSymbol cFunc : allFunctions) {
System.err.println("Filtered ["+i+"]: "+cFunc.getAliasedString());
i++;
}
}
if ( !cfg.structsOnly() ) {
emit.beginDefines();
final Set emittedDefines = new HashSet(100);
// emit java equivalent of enum { ... } statements
final StringBuilder comment = new StringBuilder();
for (final ConstantDefinition def : allConstants) {
if (!emittedDefines.contains(def.getName())) {
emittedDefines.add(def.getName());
final Set aliases = cfg.getAliasedDocNames(def);
if (aliases != null && aliases.size() > 0 ) {
int i=0;
comment.append("Alias for: ");
for (final String alias : aliases) {
if(0 < i) {
comment.append("
, ");
}
comment.append(alias);
i++;
}
comment.append("
");
}
if (def.getEnumName() != null) {
if (comment.length() > 0)
comment.append("
\n");
comment.append("Defined as part of enum type \"");
comment.append(def.getEnumName());
comment.append("\"");
}
if (comment.length() > 0) {
emit.emitDefine(def, comment.toString());
comment.setLength(0);
}
else {
emit.emitDefine(def, null);
}
}
}
emit.endDefines();
}
// Iterate through the functions finding structs that are referenced in
// the function signatures; these will be remembered for later emission
final ReferencedStructs referencedStructs = new ReferencedStructs();
for (final FunctionSymbol sym : allFunctions) {
// FIXME: this doesn't take into account the possibility that some of
// the functions we send to emitMethodBindings() might not actually be
// emitted (e.g., if an Ignore directive in the JavaEmitter causes it to be skipped).
sym.getType().visit(referencedStructs);
}
// Normally only referenced types will be emitted. The user can force a
// type to be emitted via a .cfg file directive. Those directives are
// processed here.
for (final String name : forcedStructNames) {
final Type type = td.get(name);
if (type == null) {
err.println("WARNING: during forced struct emission: struct \"" + name + "\" not found");
} else if (!type.isCompound()) {
err.println("WARNING: during forced struct emission: type \"" + name + "\" was not a struct");
} else {
type.visit(referencedStructs);
}
}
// Lay out structs
emit.beginStructLayout();
for (final Iterator iter = referencedStructs.layouts(); iter.hasNext();) {
final CompoundType c = iter.next();
if( !c.isLayouted() ) {
emit.layoutStruct(c);
}
}
emit.endStructLayout();
// Emit structs
emit.beginStructs(td, sd, headerParser.getCanonMap());
for (final Iterator iter = referencedStructs.results(); iter.hasNext();) {
final Type t = iter.next();
if (t.isCompound()) {
assert t.isTypedef() && t.getName() == null : "ReferencedStructs incorrectly recorded compound type " + t;
emit.emitStruct(t.asCompound(), null);
} else if (t.isPointer()) {
final PointerType p = t.asPointer();
final CompoundType c = p.getTargetType().asCompound();
assert p.isTypedef() && c.getName() == null : "ReferencedStructs incorrectly recorded pointer type " + p;
emit.emitStruct(c, p);
}
}
emit.endStructs();
if ( !cfg.structsOnly() ) {
// emit java and C code to interface with the native functions
emit.beginFunctions(td, sd, headerParser.getCanonMap());
emit.emitFunctions(allFunctions);
emit.endFunctions();
}
// end emission of glue code
emit.endEmission();
} catch (final Exception e) {
throw new RuntimeException("Exception occurred while generating glue code.", e);
}
}
public static void main(final String... args) {
if (args.length == 0) {
System.err.println(GlueGenVersion.getInstance());
usage();
}
Reader reader = null;
String filename = null;
String emitterFQN = null;
String outputRootDir = null;
final List cfgFiles = new ArrayList();
boolean copyCPPOutput2Stderr = false;
boolean enablePragmaOnce = true;
final List includePaths = new ArrayList();
for (int i = 0; i < args.length; i++) {
if (i < args.length - 1) {
final String arg = args[i];
if (arg.startsWith("-I")) {
final String[] paths = arg.substring(2).split(getProperty("path.separator"));
includePaths.addAll(Arrays.asList(paths));
} else if (arg.startsWith("-O")) {
outputRootDir = arg.substring(2);
} else if (arg.startsWith("-E")) {
emitterFQN = arg.substring(2);
} else if (arg.startsWith("-C")) {
cfgFiles.add(arg.substring(2));
} else if (arg.equals("--logLevel")) {
i++;
logLevel = Level.parse(args[i]);
} else if (arg.equals("--debug")) {
debug=true;
} else if (arg.equals("--dumpCPP")) {
copyCPPOutput2Stderr=true;
} else if (arg.equals("--enablePragmaOnce")) {
enablePragmaOnce=true;
} else if (arg.equals("--disablePragmaOnce")) {
enablePragmaOnce=false;
} else {
usage();
}
} else {
final String arg = args[i];
if (arg.equals("-")) {
reader = new InputStreamReader(in);
filename = "standard input";
} else {
if (arg.startsWith("-")) {
usage();
}
filename = arg;
try {
reader = new BufferedReader(new FileReader(filename));
} catch (final FileNotFoundException ex) {
throw new RuntimeException("input file not found", ex);
}
}
}
}
try {
final Class> emitterClass = emitterFQN == null ? null : Class.forName(emitterFQN);
new GlueGen().run(reader, filename, emitterClass, includePaths, cfgFiles, outputRootDir, copyCPPOutput2Stderr, enablePragmaOnce);
} catch (final ClassNotFoundException ex) {
throw new RuntimeException("specified emitter class was not in the classpath", ex);
}
}
//----------------------------------------------------------------------
// Internals only below this point
//
private static void usage() {
out.println("Usage: java GlueGen [-I...] [-Eemitter_class_name] [-Ccfg_file_name...] ");
out.println();
out.println("Runs C header parser on input file or standard input, first");
out.println("passing input through minimal pseudo-C-preprocessor. Use -I");
out.println("command-line arguments to specify the search path for #includes.");
out.println("Emitter class name can be specified with -E option: i.e.,");
out.println("-Ecom.jogamp.gluegen.JavaEmitter (the default). Use");
out.println("-Ecom.jogamp.gluegen.DebugEmitter to print recognized entities");
out.println("(#define directives to constant numbers, typedefs, and function");
out.println("declarations) to standard output. Emitter-specific configuration");
out.println("file or files can be specified with -C option; e.g,");
out.println("-Cjava-emitter.cfg.");
out.println(" --debug enables debug mode");
out.println(" --dumpCPP directs CPP to dump all output to stderr as well");
out.println(" --enablePragmaOnce allow handle of #pragma once directive during parsing (default)");
out.println(" --disablePragmaOnce disable handling of #pragma once directive during parsing");
exit(1);
}
}