diff options
author | Sven Gothel <[email protected]> | 2015-03-23 22:20:11 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2015-03-23 22:20:11 +0100 |
commit | 0bb7cf5586f08c00de051036427de36e2b1601f4 (patch) | |
tree | 52e807d3c452d619a942379e3ef8768a2407b020 /src/main/java/com/jogamp | |
parent | e3b1d17a2f2b4070c1080fa6f78cfc1bcd28c78c (diff) |
Relocate JCPP package: org/anarres/cpp -> com/jogamp/gluegen/jcpp
Diffstat (limited to 'src/main/java/com/jogamp')
33 files changed, 6429 insertions, 0 deletions
diff --git a/src/main/java/com/jogamp/gluegen/jcpp/Argument.java b/src/main/java/com/jogamp/gluegen/jcpp/Argument.java new file mode 100644 index 0000000..bccddcb --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/Argument.java @@ -0,0 +1,75 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * A macro argument. + * + * This encapsulates a raw and preprocessed token stream. + */ +/* pp */ class Argument extends ArrayList<Token> { + + private List<Token> expansion; + + public Argument() { + this.expansion = null; + } + + public void addToken(@Nonnull Token tok) { + add(tok); + } + + /* pp */ void expand(@Nonnull Preprocessor p) + throws IOException, + LexerException { + /* Cache expansion. */ + if (expansion == null) { + this.expansion = p.expand(this); + // System.out.println("Expanded arg " + this); + } + } + + @Nonnull + public Iterator<Token> expansion() { + return expansion.iterator(); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("Argument("); + // buf.append(super.toString()); + buf.append("raw=[ "); + for (int i = 0; i < size(); i++) + buf.append(get(i).getText()); + buf.append(" ];expansion=[ "); + if (expansion == null) + buf.append("null"); + else + for (Token token : expansion) + buf.append(token.getText()); + buf.append(" ])"); + return buf.toString(); + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/BuildMetadata.java b/src/main/java/com/jogamp/gluegen/jcpp/BuildMetadata.java new file mode 100644 index 0000000..f22d853 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/BuildMetadata.java @@ -0,0 +1,72 @@ +package com.jogamp.gluegen.jcpp; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import javax.annotation.Nonnull; + +/** + * Returns information about the build. + * + * @author shevek + */ +public class BuildMetadata { + + public static final String RESOURCE = "/META-INF/jcpp.properties"; + private static BuildMetadata INSTANCE; + + /** @throws RuntimeException if the properties file cannot be found on the classpath. */ + @Nonnull + public static synchronized BuildMetadata getInstance() { + try { + if (INSTANCE == null) + INSTANCE = new BuildMetadata(); + return INSTANCE; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private final Properties properties = new Properties(); + + private BuildMetadata() throws IOException { + URL url = BuildMetadata.class.getResource(RESOURCE); + InputStream in = url.openStream(); + try { + properties.load(in); + } finally { + in.close(); + } + } + + @Nonnull + public Map<? extends String, ? extends String> asMap() { + Map<String, String> out = new HashMap<String, String>(); + for (Map.Entry<Object, Object> e : properties.entrySet()) + out.put(String.valueOf(e.getKey()), String.valueOf(e.getValue())); + return out; + } + + @Nonnull + public com.github.zafarkhaja.semver.Version getVersion() { + return com.github.zafarkhaja.semver.Version.valueOf(properties.getProperty("Implementation-Version")); + } + + @Nonnull + public Date getBuildDate() throws ParseException { + // Build-Date=2015-01-01_10:09:09 + String text = properties.getProperty("Build-Date"); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss"); + return format.parse(text); + } + + public String getChangeId() { + return properties.getProperty("Change"); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/ChrootFileSystem.java b/src/main/java/com/jogamp/gluegen/jcpp/ChrootFileSystem.java new file mode 100644 index 0000000..f3806fa --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/ChrootFileSystem.java @@ -0,0 +1,84 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.File; +import java.io.IOException; + +/** + * A virtual filesystem implementation using java.io in a virtual + * chroot. + */ +public class ChrootFileSystem implements VirtualFileSystem { + + private File root; + + public ChrootFileSystem(File root) { + this.root = root; + } + + @Override + public VirtualFile getFile(String path) { + return new ChrootFile(path); + } + + @Override + public VirtualFile getFile(String dir, String name) { + return new ChrootFile(dir, name); + } + + private class ChrootFile extends File implements VirtualFile { + + private File rfile; + + public ChrootFile(String path) { + super(path); + } + + public ChrootFile(String dir, String name) { + super(dir, name); + } + + /* private */ + public ChrootFile(File dir, String name) { + super(dir, name); + } + + @Override + public ChrootFile getParentFile() { + return new ChrootFile(getParent()); + } + + @Override + public ChrootFile getChildFile(String name) { + return new ChrootFile(this, name); + } + + @Override + public boolean isFile() { + File real = new File(root, getPath()); + return real.isFile(); + } + + @Override + public Source getSource() throws IOException { + return new FileLexerSource(new File(root, getPath()), + getPath()); + } + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/CppReader.java b/src/main/java/com/jogamp/gluegen/jcpp/CppReader.java new file mode 100644 index 0000000..82ef2fe --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/CppReader.java @@ -0,0 +1,149 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; + +import javax.annotation.Nonnull; + +import static com.jogamp.gluegen.jcpp.Token.CCOMMENT; +import static com.jogamp.gluegen.jcpp.Token.CPPCOMMENT; +import static com.jogamp.gluegen.jcpp.Token.EOF; + +/** + * A Reader wrapper around the Preprocessor. + * + * This is a utility class to provide a transparent {@link Reader} + * which preprocesses the input text. + * + * @see Preprocessor + * @see Reader + */ +public class CppReader extends Reader implements Closeable { + + private final Preprocessor cpp; + private String token; + private int idx; + + public CppReader(@Nonnull final Reader r) { + cpp = new Preprocessor(new LexerSource(r, true) { + @Override + public String getName() { + return "<CppReader Input@" + + System.identityHashCode(r) + ">"; + } + }); + token = ""; + idx = 0; + } + + public CppReader(@Nonnull Preprocessor p) { + cpp = p; + token = ""; + idx = 0; + } + + /** + * Returns the Preprocessor used by this CppReader. + */ + @Nonnull + public Preprocessor getPreprocessor() { + return cpp; + } + + /** + * Defines the given name as a macro. + * + * This is a convnience method. + */ + public void addMacro(@Nonnull String name) + throws LexerException { + cpp.addMacro(name); + } + + /** + * Defines the given name as a macro. + * + * This is a convnience method. + */ + public void addMacro(@Nonnull String name, @Nonnull String value) + throws LexerException { + cpp.addMacro(name, value); + } + + private boolean refill() + throws IOException { + try { + assert cpp != null : "cpp is null : was it closed?"; + if (token == null) + return false; + while (idx >= token.length()) { + Token tok = cpp.token(); + switch (tok.getType()) { + case EOF: + token = null; + return false; + case CCOMMENT: + case CPPCOMMENT: + if (!cpp.getFeature(Feature.KEEPCOMMENTS)) { + token = " "; + break; + } + default: + token = tok.getText(); + break; + } + idx = 0; + } + return true; + } catch (LexerException e) { + throw new IOException(String.valueOf(e), e); + } + } + + @Override + public int read() + throws IOException { + if (!refill()) + return -1; + return token.charAt(idx++); + } + + /* XXX Very slow and inefficient. */ + public int read(char cbuf[], int off, int len) + throws IOException { + if (token == null) + return -1; + for (int i = 0; i < len; i++) { + int ch = read(); + if (ch == -1) + return i; + cbuf[off + i] = (char) ch; + } + return len; + } + + @Override + public void close() + throws IOException { + cpp.close(); + token = null; + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/CppTask.java b/src/main/java/com/jogamp/gluegen/jcpp/CppTask.java new file mode 100644 index 0000000..e3be324 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/CppTask.java @@ -0,0 +1,215 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Copy; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.types.Path; + +/** + * An ant task for jcpp. + */ +public class CppTask extends Copy { + + private class Listener extends DefaultPreprocessorListener { + + @Override + protected void print(String msg) { + log(msg); + } + } + + public static class Macro { + + private String name; + private String value; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setValue(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + private final Listener listener = new Listener(); + private final List<Macro> macros = new ArrayList<Macro>(); + private Path systemincludepath; + private Path localincludepath; + + public void addMacro(Macro macro) { + macros.add(macro); + } + + public void addSystemincludepath(Path path) { + if (systemincludepath == null) + systemincludepath = new Path(getProject()); + systemincludepath.add(path); + } + + public void addLocalincludepath(Path path) { + if (localincludepath == null) + localincludepath = new Path(getProject()); + localincludepath.add(path); + } + + /* + public void execute() { + FileWriter writer = null; + try { + if (input == null) + throw new BuildException("Input not specified"); + if (output == null) + throw new BuildException("Output not specified"); + cpp.addInput(this.input); + writer = new FileWriter(this.output); + for (;;) { + Token tok = cpp.token(); + if (tok != null && tok.getType() == Token.EOF) + break; + writer.write(tok.getText()); + } + } + catch (Exception e) { + throw new BuildException(e); + } + finally { + if (writer != null) { + try { + writer.close(); + } + catch (IOException e) { + } + } + } + } + */ + private void preprocess(File input, File output) throws Exception { + Preprocessor cpp = new Preprocessor(); + cpp.setListener(listener); + for (Macro macro : macros) + cpp.addMacro(macro.getName(), macro.getValue()); + if (systemincludepath != null) + cpp.setSystemIncludePath(Arrays.asList(systemincludepath.list())); + if (localincludepath != null) + cpp.setQuoteIncludePath(Arrays.asList(localincludepath.list())); + + File dir = output.getParentFile(); + if (!dir.exists()) { + if (!dir.mkdirs()) + throw new BuildException("Failed to make parent directory " + dir); + } else if (!dir.isDirectory()) { + throw new BuildException("Parent directory of output file " + output + " exists, but is not a directory."); + } + FileWriter writer = null; + try { + if (input == null) + throw new BuildException("Input not specified"); + if (output == null) + throw new BuildException("Output not specified"); + cpp.addInput(input); + writer = new FileWriter(output); + for (;;) { + Token tok = cpp.token(); + if (tok == null) + break; + if (tok.getType() == Token.EOF) + break; + writer.write(tok.getText()); + } + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + } + } + } + } + + @Override + protected void doFileOperations() { + if (fileCopyMap.size() > 0) { + log("Copying " + fileCopyMap.size() + + " file" + (fileCopyMap.size() == 1 ? "" : "s") + + " to " + destDir.getAbsolutePath()); + + Enumeration<String> e = fileCopyMap.keys(); + + while (e.hasMoreElements()) { + String fromFile = e.nextElement(); + String[] toFiles = (String[]) fileCopyMap.get(fromFile); + + for (String toFile : toFiles) { + if (fromFile.equals(toFile)) { + log("Skipping self-copy of " + fromFile, verbosity); + continue; + } + + try { + log("Copying " + fromFile + " to " + toFile, verbosity); + + FilterSetCollection executionFilters + = new FilterSetCollection(); + if (filtering) { + executionFilters + .addFilterSet(getProject().getGlobalFilterSet()); + } + for (Enumeration filterEnum = getFilterSets().elements(); + filterEnum.hasMoreElements();) { + executionFilters + .addFilterSet((FilterSet) filterEnum.nextElement()); + } + + File srcFile = new File(fromFile); + File dstFile = new File(toFile); + preprocess(srcFile, dstFile); + } catch (Exception ioe) { + // ioe.printStackTrace(); + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + File targetFile = new File(toFile); + if (targetFile.exists() && !targetFile.delete()) { + msg += " and I couldn't delete the corrupt " + toFile; + } + throw new BuildException(msg, ioe, getLocation()); + } + } + } + } + + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/DefaultPreprocessorListener.java b/src/main/java/com/jogamp/gluegen/jcpp/DefaultPreprocessorListener.java new file mode 100644 index 0000000..bcb3000 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/DefaultPreprocessorListener.java @@ -0,0 +1,98 @@ +package com.jogamp.gluegen.jcpp; + +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; + +import com.jogamp.gluegen.Logging; +import com.jogamp.gluegen.Logging.LoggerIf; + +/** + * A handler for preprocessor events, primarily errors and warnings. + * + * If no PreprocessorListener is installed in a Preprocessor, all + * error and warning events will throw an exception. Installing a + * listener allows more intelligent handling of these events. + */ +public class DefaultPreprocessorListener implements PreprocessorListener { + + private static final LoggerIf LOG = Logging.getLogger(DefaultPreprocessorListener.class); + + private int errors; + private int warnings; + + public DefaultPreprocessorListener() { + clear(); + } + + public void clear() { + errors = 0; + warnings = 0; + } + + @Nonnegative + public int getErrors() { + return errors; + } + + @Nonnegative + public int getWarnings() { + return warnings; + } + + protected void print(@Nonnull final String msg) { + LOG.info(msg); + } + + /** + * Handles a warning. + * + * The behaviour of this method is defined by the + * implementation. It may simply record the error message, or + * it may throw an exception. + */ + @Override + public void handleWarning(final Source source, final int line, final int column, + final String msg) + throws LexerException { + warnings++; + print(source.getName() + ":" + line + ":" + column + + ": warning: " + msg); + } + + /** + * Handles an error. + * + * The behaviour of this method is defined by the + * implementation. It may simply record the error message, or + * it may throw an exception. + */ + @Override + public void handleError(final Source source, final int line, final int column, + final String msg) + throws LexerException { + errors++; + print(source.getName() + ":" + line + ":" + column + + ": error: " + msg); + } + + @Override + public void handleSourceChange(final Source source, final SourceChangeEvent event) { + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/Feature.java b/src/main/java/com/jogamp/gluegen/jcpp/Feature.java new file mode 100644 index 0000000..e2feefc --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/Feature.java @@ -0,0 +1,42 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +/** + * Features of the Preprocessor, which may be enabled or disabled. + */ +public enum Feature { + + /** Supports ANSI digraphs. */ + DIGRAPHS, + /** Supports ANSI trigraphs. */ + TRIGRAPHS, + /** Outputs linemarker tokens. */ + LINEMARKERS, + /** Reports tokens of type INVALID as errors. */ + CSYNTAX, + /** Preserves comments in the lexed output. */ + KEEPCOMMENTS, + /** Preserves comments in the lexed output, even when inactive. */ + KEEPALLCOMMENTS, + DEBUG, + /** Supports lexing of objective-C. */ + OBJCSYNTAX, + INCLUDENEXT, + /** Random extensions. */ + PRAGMA_ONCE +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/FileLexerSource.java b/src/main/java/com/jogamp/gluegen/jcpp/FileLexerSource.java new file mode 100644 index 0000000..6d7e4c5 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/FileLexerSource.java @@ -0,0 +1,89 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import javax.annotation.Nonnull; + +/** + * A {@link Source} which lexes a file. + * + * The input is buffered. + * + * @see Source + */ +public class FileLexerSource extends LexerSource { + + private final String path; + private final File file; + + /** + * Creates a new Source for lexing the given File. + * + * Preprocessor directives are honoured within the file. + */ + public FileLexerSource(@Nonnull File file, String path) + throws IOException { + super( + new BufferedReader( + new FileReader( + file + ) + ), + true + ); + + this.file = file; + this.path = path; + } + + public FileLexerSource(@Nonnull File file) + throws IOException { + this(file, file.getPath()); + } + + public FileLexerSource(@Nonnull String path) + throws IOException { + this(new File(path), path); + } + + @Nonnull + public File getFile() { + return file; + } + + /** + * This is not necessarily the same as getFile().getPath() in case we are in a chroot. + */ + @Override + public String getPath() { + return path; + } + + @Override + public String getName() { + return getPath(); + } + + @Override + public String toString() { + return "file " + getPath(); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/FixedTokenSource.java b/src/main/java/com/jogamp/gluegen/jcpp/FixedTokenSource.java new file mode 100644 index 0000000..d084932 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/FixedTokenSource.java @@ -0,0 +1,59 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +/* pp */ class FixedTokenSource extends Source { + + private static final Token EOF + = new Token(Token.EOF, "<ts-eof>"); + + private final List<Token> tokens; + private int idx; + + /* pp */ FixedTokenSource(Token... tokens) { + this.tokens = Arrays.asList(tokens); + this.idx = 0; + } + + /* pp */ FixedTokenSource(List<Token> tokens) { + this.tokens = tokens; + this.idx = 0; + } + + @Override + public Token token() + throws IOException, + LexerException { + if (idx >= tokens.size()) + return EOF; + return tokens.get(idx++); + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("constant token stream ").append(tokens); + Source parent = getParent(); + if (parent != null) + buf.append(" in ").append(String.valueOf(parent)); + return buf.toString(); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/InputLexerSource.java b/src/main/java/com/jogamp/gluegen/jcpp/InputLexerSource.java new file mode 100644 index 0000000..00cb01a --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/InputLexerSource.java @@ -0,0 +1,64 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * A {@link Source} which lexes a file. + * + * The input is buffered. + * + * @see Source + */ +public class InputLexerSource extends LexerSource { + + /** + * Creates a new Source for lexing the given Reader. + * + * Preprocessor directives are honoured within the file. + */ + public InputLexerSource(InputStream input) + throws IOException { + super( + new BufferedReader( + new InputStreamReader( + input + ) + ), + true + ); + } + + @Override + public String getPath() { + return "<standard-input>"; + } + + @Override + public String getName() { + return "standard input"; + } + + @Override + public String toString() { + return String.valueOf(getPath()); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/InternalException.java b/src/main/java/com/jogamp/gluegen/jcpp/InternalException.java new file mode 100644 index 0000000..72abaf9 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/InternalException.java @@ -0,0 +1,31 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +/** + * An internal exception. + * + * This exception is thrown when an internal state violation is + * encountered. This should never happen. If it ever happens, please + * report it as a bug. + */ +public class InternalException extends RuntimeException { + + public InternalException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/JavaFileSystem.java b/src/main/java/com/jogamp/gluegen/jcpp/JavaFileSystem.java new file mode 100644 index 0000000..f62ba53 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/JavaFileSystem.java @@ -0,0 +1,84 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.File; +import java.io.IOException; + +/** + * A virtual filesystem implementation using java.io. + */ +public class JavaFileSystem implements VirtualFileSystem { + + @Override + public VirtualFile getFile(String path) { + return new JavaFile(path); + } + + @Override + public VirtualFile getFile(String dir, String name) { + return new JavaFile(dir, name); + } + + private class JavaFile extends File implements VirtualFile { + + public JavaFile(String path) { + super(path); + } + + public JavaFile(String dir, String name) { + super(dir, name); + } + + /* private */ + public JavaFile(File dir, String name) { + super(dir, name); + } + + /* + @Override + public String getPath() { + return getCanonicalPath(); + } + */ + @Override + public JavaFile getParentFile() { + String parent = getParent(); + if (parent != null) + return new JavaFile(parent); + File absolute = getAbsoluteFile(); + parent = absolute.getParent(); + /* + if (parent == null) + return null; + */ + return new JavaFile(parent); + } + + @Override + public JavaFile getChildFile(String name) { + return new JavaFile(this, name); + } + + @Override + public Source getSource() throws IOException { + return new FileLexerSource(this); + } + + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/JoinReader.java b/src/main/java/com/jogamp/gluegen/jcpp/JoinReader.java new file mode 100644 index 0000000..e44a7e6 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/JoinReader.java @@ -0,0 +1,218 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; + +/* pp */ class JoinReader /* extends Reader */ implements Closeable { + + private final Reader in; + + private PreprocessorListener listener; + private LexerSource source; + private boolean trigraphs; + private boolean warnings; + + private int newlines; + private boolean flushnl; + private int[] unget; + private int uptr; + + public JoinReader(Reader in, boolean trigraphs) { + this.in = in; + this.trigraphs = trigraphs; + this.newlines = 0; + this.flushnl = false; + this.unget = new int[2]; + this.uptr = 0; + } + + public JoinReader(Reader in) { + this(in, false); + } + + public void setTrigraphs(boolean enable, boolean warnings) { + this.trigraphs = enable; + this.warnings = warnings; + } + + /* pp */ void init(Preprocessor pp, LexerSource s) { + this.listener = pp.getListener(); + this.source = s; + setTrigraphs(pp.getFeature(Feature.TRIGRAPHS), + pp.getWarning(Warning.TRIGRAPHS)); + } + + private int __read() throws IOException { + if (uptr > 0) + return unget[--uptr]; + return in.read(); + } + + private void _unread(int c) { + if (c != -1) + unget[uptr++] = c; + assert uptr <= unget.length : + "JoinReader ungets too many characters"; + } + + protected void warning(String msg) + throws LexerException { + if (source != null) + source.warning(msg); + else + throw new LexerException(msg); + } + + private char trigraph(char raw, char repl) + throws IOException, LexerException { + if (trigraphs) { + if (warnings) + warning("trigraph ??" + raw + " converted to " + repl); + return repl; + } else { + if (warnings) + warning("trigraph ??" + raw + " ignored"); + _unread(raw); + _unread('?'); + return '?'; + } + } + + private int _read() + throws IOException, LexerException { + int c = __read(); + if (c == '?' && (trigraphs || warnings)) { + int d = __read(); + if (d == '?') { + int e = __read(); + switch (e) { + case '(': + return trigraph('(', '['); + case ')': + return trigraph(')', ']'); + case '<': + return trigraph('<', '{'); + case '>': + return trigraph('>', '}'); + case '=': + return trigraph('=', '#'); + case '/': + return trigraph('/', '\\'); + case '\'': + return trigraph('\'', '^'); + case '!': + return trigraph('!', '|'); + case '-': + return trigraph('-', '~'); + } + _unread(e); + } + _unread(d); + } + return c; + } + + public int read() + throws IOException, LexerException { + if (flushnl) { + if (newlines > 0) { + newlines--; + return '\n'; + } + flushnl = false; + } + + for (;;) { + int c = _read(); + switch (c) { + case '\\': + int d = _read(); + switch (d) { + case '\n': + newlines++; + continue; + case '\r': + newlines++; + int e = _read(); + if (e != '\n') + _unread(e); + continue; + default: + _unread(d); + return c; + } + case '\r': + case '\n': + case '\u2028': + case '\u2029': + case '\u000B': + case '\u000C': + case '\u0085': + flushnl = true; + return c; + case -1: + if (newlines > 0) { + newlines--; + return '\n'; + } + default: + return c; + } + } + } + + public int read(char cbuf[], int off, int len) + throws IOException, LexerException { + for (int i = 0; i < len; i++) { + int ch = read(); + if (ch == -1) + return i; + cbuf[off + i] = (char) ch; + } + return len; + } + + @Override + public void close() + throws IOException { + in.close(); + } + + @Override + public String toString() { + return "JoinReader(nl=" + newlines + ")"; + } + + /* + public static void main(String[] args) throws IOException { + FileReader f = new FileReader(new File(args[0])); + BufferedReader b = new BufferedReader(f); + JoinReader r = new JoinReader(b); + BufferedWriter w = new BufferedWriter( + new java.io.OutputStreamWriter(System.out) + ); + int c; + while ((c = r.read()) != -1) { + w.write((char)c); + } + w.close(); + } + */ +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/LexerException.java b/src/main/java/com/jogamp/gluegen/jcpp/LexerException.java new file mode 100644 index 0000000..6a9bf5b --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/LexerException.java @@ -0,0 +1,33 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +/** + * A preprocessor exception. + * + * Note to users: I don't really like the name of this class. S. + */ +public class LexerException extends Exception { + + public LexerException(String msg) { + super(msg); + } + + public LexerException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/LexerSource.java b/src/main/java/com/jogamp/gluegen/jcpp/LexerSource.java new file mode 100644 index 0000000..798e0a9 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/LexerSource.java @@ -0,0 +1,987 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.IOException; +import java.io.Reader; + +import javax.annotation.Nonnull; + +import static com.jogamp.gluegen.jcpp.Token.*; + +/** Does not handle digraphs. */ +public class LexerSource extends Source { + + private static final boolean DEBUG = false; + + private JoinReader reader; + private final boolean ppvalid; + private boolean bol; + private boolean include; + + private boolean digraphs; + + /* Unread. */ + private int u0, u1; + private int ucount; + + private int line; + private int column; + private int lastcolumn; + private boolean cr; + + /* ppvalid is: + * false in StringLexerSource, + * true in FileLexerSource */ + public LexerSource(Reader r, boolean ppvalid) { + this.reader = new JoinReader(r); + this.ppvalid = ppvalid; + this.bol = true; + this.include = false; + + this.digraphs = true; + + this.ucount = 0; + + this.line = 1; + this.column = 0; + this.lastcolumn = -1; + this.cr = false; + } + + @Override + /* pp */ void init(Preprocessor pp) { + super.init(pp); + this.digraphs = pp.getFeature(Feature.DIGRAPHS); + this.reader.init(pp, this); + } + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return column; + } + + @Override + /* pp */ boolean isNumbered() { + return true; + } + + /* Error handling. */ + private void _error(String msg, boolean error) + throws LexerException { + int _l = line; + int _c = column; + if (_c == 0) { + _c = lastcolumn; + _l--; + } else { + _c--; + } + if (error) + super.error(_l, _c, msg); + else + super.warning(_l, _c, msg); + } + + /* Allow JoinReader to call this. */ + /* pp */ final void error(String msg) + throws LexerException { + _error(msg, true); + } + + /* Allow JoinReader to call this. */ + /* pp */ final void warning(String msg) + throws LexerException { + _error(msg, false); + } + + /* A flag for string handling. */ + + /* pp */ void setInclude(boolean b) { + this.include = b; + } + + /* + * private boolean _isLineSeparator(int c) { + * return Character.getType(c) == Character.LINE_SEPARATOR + * || c == -1; + * } + */ + + /* XXX Move to JoinReader and canonicalise newlines. */ + private static boolean isLineSeparator(int c) { + switch ((char) c) { + case '\r': + case '\n': + case '\u2028': + case '\u2029': + case '\u000B': + case '\u000C': + case '\u0085': + return true; + default: + return (c == -1); + } + } + + private int read() + throws IOException, + LexerException { + int c; + assert ucount <= 2 : "Illegal ucount: " + ucount; + switch (ucount) { + case 2: + ucount = 1; + c = u1; + break; + case 1: + ucount = 0; + c = u0; + break; + default: + if (reader == null) + c = -1; + else + c = reader.read(); + break; + } + + switch (c) { + case '\r': + cr = true; + line++; + lastcolumn = column; + column = 0; + break; + case '\n': + if (cr) { + cr = false; + break; + } + /* fallthrough */ + case '\u2028': + case '\u2029': + case '\u000B': + case '\u000C': + case '\u0085': + cr = false; + line++; + lastcolumn = column; + column = 0; + break; + case -1: + cr = false; + break; + default: + cr = false; + column++; + break; + } + + /* + * if (isLineSeparator(c)) { + * line++; + * lastcolumn = column; + * column = 0; + * } + * else { + * column++; + * } + */ + return c; + } + + /* You can unget AT MOST one newline. */ + private void unread(int c) + throws IOException { + /* XXX Must unread newlines. */ + if (c != -1) { + if (isLineSeparator(c)) { + line--; + column = lastcolumn; + cr = false; + } else { + column--; + } + switch (ucount) { + case 0: + u0 = c; + ucount = 1; + break; + case 1: + u1 = c; + ucount = 2; + break; + default: + throw new IllegalStateException( + "Cannot unget another character!" + ); + } + // reader.unread(c); + } + } + + /* Consumes the rest of the current line into an invalid. */ + @Nonnull + private Token invalid(StringBuilder text, String reason) + throws IOException, + LexerException { + int d = read(); + while (!isLineSeparator(d)) { + text.append((char) d); + d = read(); + } + unread(d); + return new Token(INVALID, text.toString(), reason); + } + + @Nonnull + private Token ccomment() + throws IOException, + LexerException { + StringBuilder text = new StringBuilder("/*"); + int d; + do { + do { + d = read(); + if (d == -1) + return new Token(INVALID, text.toString(), + "Unterminated comment"); + text.append((char) d); + } while (d != '*'); + do { + d = read(); + if (d == -1) + return new Token(INVALID, text.toString(), + "Unterminated comment"); + text.append((char) d); + } while (d == '*'); + } while (d != '/'); + return new Token(CCOMMENT, text.toString()); + } + + @Nonnull + private Token cppcomment() + throws IOException, + LexerException { + StringBuilder text = new StringBuilder("//"); + int d = read(); + while (!isLineSeparator(d)) { + text.append((char) d); + d = read(); + } + unread(d); + return new Token(CPPCOMMENT, text.toString()); + } + + private int escape(StringBuilder text) + throws IOException, + LexerException { + int d = read(); + switch (d) { + case 'a': + text.append('a'); + return 0x07; + case 'b': + text.append('b'); + return '\b'; + case 'f': + text.append('f'); + return '\f'; + case 'n': + text.append('n'); + return '\n'; + case 'r': + text.append('r'); + return '\r'; + case 't': + text.append('t'); + return '\t'; + case 'v': + text.append('v'); + return 0x0b; + case '\\': + text.append('\\'); + return '\\'; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + int len = 0; + int val = 0; + do { + val = (val << 3) + Character.digit(d, 8); + text.append((char) d); + d = read(); + } while (++len < 3 && Character.digit(d, 8) != -1); + unread(d); + return val; + + case 'x': + text.append((char) d); + len = 0; + val = 0; + while (len++ < 2) { + d = read(); + if (Character.digit(d, 16) == -1) { + unread(d); + break; + } + val = (val << 4) + Character.digit(d, 16); + text.append((char) d); + } + return val; + + /* Exclude two cases from the warning. */ + case '"': + text.append('"'); + return '"'; + case '\'': + text.append('\''); + return '\''; + + default: + warning("Unnecessary escape character " + (char) d); + text.append((char) d); + return d; + } + } + + @Nonnull + private Token character() + throws IOException, + LexerException { + StringBuilder text = new StringBuilder("'"); + int d = read(); + if (d == '\\') { + text.append('\\'); + d = escape(text); + } else if (isLineSeparator(d)) { + unread(d); + return new Token(INVALID, text.toString(), + "Unterminated character literal"); + } else if (d == '\'') { + text.append('\''); + return new Token(INVALID, text.toString(), + "Empty character literal"); + } else if (!Character.isDefined(d)) { + text.append('?'); + return invalid(text, "Illegal unicode character literal"); + } else { + text.append((char) d); + } + + int e = read(); + if (e != '\'') { + // error("Illegal character constant"); + /* We consume up to the next ' or the rest of the line. */ + for (;;) { + if (isLineSeparator(e)) { + unread(e); + break; + } + text.append((char) e); + if (e == '\'') + break; + e = read(); + } + return new Token(INVALID, text.toString(), + "Illegal character constant " + text); + } + text.append('\''); + /* XXX It this a bad cast? */ + return new Token(CHARACTER, + text.toString(), Character.valueOf((char) d)); + } + + @Nonnull + private Token string(char open, char close) + throws IOException, + LexerException { + StringBuilder text = new StringBuilder(); + text.append(open); + + StringBuilder buf = new StringBuilder(); + + for (;;) { + int c = read(); + if (c == close) { + break; + } else if (c == '\\') { + text.append('\\'); + if (!include) { + char d = (char) escape(text); + buf.append(d); + } + } else if (c == -1) { + unread(c); + // error("End of file in string literal after " + buf); + return new Token(INVALID, text.toString(), + "End of file in string literal after " + buf); + } else if (isLineSeparator(c)) { + unread(c); + // error("Unterminated string literal after " + buf); + return new Token(INVALID, text.toString(), + "Unterminated string literal after " + buf); + } else { + text.append((char) c); + buf.append((char) c); + } + } + text.append(close); + switch (close) { + case '"': + return new Token(STRING, + text.toString(), buf.toString()); + case '>': + return new Token(HEADER, + text.toString(), buf.toString()); + case '\'': + if (buf.length() == 1) + return new Token(CHARACTER, + text.toString(), buf.toString()); + return new Token(SQSTRING, + text.toString(), buf.toString()); + default: + throw new IllegalStateException( + "Unknown closing character " + String.valueOf(close)); + } + } + + @Nonnull + private Token _number_suffix(StringBuilder text, NumericValue value, int d) + throws IOException, + LexerException { + int flags = 0; // U, I, L, LL, F, D, MSB + for (;;) { + if (d == 'U' || d == 'u') { + if ((flags & NumericValue.F_UNSIGNED) != 0) + warning("Duplicate unsigned suffix " + d); + flags |= NumericValue.F_UNSIGNED; + text.append((char) d); + d = read(); + } else if (d == 'L' || d == 'l') { + if ((flags & NumericValue.FF_SIZE) != 0) + warning("Multiple length suffixes after " + text); + text.append((char) d); + int e = read(); + if (e == d) { // Case must match. Ll is Welsh. + flags |= NumericValue.F_LONGLONG; + text.append((char) e); + d = read(); + } else { + flags |= NumericValue.F_LONG; + d = e; + } + } else if (d == 'I' || d == 'i') { + if ((flags & NumericValue.FF_SIZE) != 0) + warning("Multiple length suffixes after " + text); + flags |= NumericValue.F_INT; + text.append((char) d); + d = read(); + } else if (d == 'F' || d == 'f') { + if ((flags & NumericValue.FF_SIZE) != 0) + warning("Multiple length suffixes after " + text); + flags |= NumericValue.F_FLOAT; + text.append((char) d); + d = read(); + } else if (d == 'D' || d == 'd') { + if ((flags & NumericValue.FF_SIZE) != 0) + warning("Multiple length suffixes after " + text); + flags |= NumericValue.F_DOUBLE; + text.append((char) d); + d = read(); + } + else if (Character.isUnicodeIdentifierPart(d)) { + String reason = "Invalid suffix \"" + (char) d + "\" on numeric constant"; + // We've encountered something initially identified as a number. + // Read in the rest of this token as an identifer but return it as an invalid. + while (Character.isUnicodeIdentifierPart(d)) { + text.append((char) d); + d = read(); + } + unread(d); + return new Token(INVALID, text.toString(), reason); + } else { + unread(d); + value.setFlags(flags); + return new Token(NUMBER, + text.toString(), value); + } + } + } + + /* Either a decimal part, or a hex exponent. */ + @Nonnull + private String _number_part(StringBuilder text, int base, boolean sign) + throws IOException, + LexerException { + StringBuilder part = new StringBuilder(); + int d = read(); + if (sign && d == '-') { + text.append((char) d); + part.append((char) d); + d = read(); + } + while (Character.digit(d, base) != -1) { + text.append((char) d); + part.append((char) d); + d = read(); + } + unread(d); + return part.toString(); + } + + /* We do not know whether know the first digit is valid. */ + @Nonnull + private Token number_hex(char x) + throws IOException, + LexerException { + StringBuilder text = new StringBuilder("0"); + text.append(x); + String integer = _number_part(text, 16, false); + NumericValue value = new NumericValue(16, integer); + int d = read(); + if (d == '.') { + text.append((char) d); + String fraction = _number_part(text, 16, false); + value.setFractionalPart(fraction); + d = read(); + } + if (d == 'P' || d == 'p') { + text.append((char) d); + String exponent = _number_part(text, 10, true); + value.setExponent(2, exponent); + d = read(); + } + // XXX Make sure it's got enough parts + return _number_suffix(text, value, d); + } + + private static boolean is_octal(@Nonnull String text) { + if (!text.startsWith("0")) + return false; + for (int i = 0; i < text.length(); i++) + if (Character.digit(text.charAt(i), 8) == -1) + return false; + return true; + } + + /* We know we have at least one valid digit, but empty is not + * fine. */ + @Nonnull + private Token number_decimal() + throws IOException, + LexerException { + StringBuilder text = new StringBuilder(); + String integer = _number_part(text, 10, false); + String fraction = null; + String exponent = null; + int d = read(); + if (d == '.') { + text.append((char) d); + fraction = _number_part(text, 10, false); + d = read(); + } + if (d == 'E' || d == 'e') { + text.append((char) d); + exponent = _number_part(text, 10, true); + d = read(); + } + int base = 10; + if (fraction == null && exponent == null && integer.startsWith("0")) { + if (!is_octal(integer)) + warning("Decimal constant starts with 0, but not octal: " + integer); + else + base = 8; + } + NumericValue value = new NumericValue(base, integer); + if (fraction != null) + value.setFractionalPart(fraction); + if (exponent != null) + value.setExponent(10, exponent); + // XXX Make sure it's got enough parts + return _number_suffix(text, value, d); + } + + /** + * Section 6.4.4.1 of C99 + * + * (Not pasted here, but says that the initial negation is a separate token.) + * + * Section 6.4.4.2 of C99 + * + * A floating constant has a significand part that may be followed + * by an exponent part and a suffix that specifies its type. The + * components of the significand part may include a digit sequence + * representing the whole-number part, followed by a period (.), + * followed by a digit sequence representing the fraction part. + * + * The components of the exponent part are an e, E, p, or P + * followed by an exponent consisting of an optionally signed digit + * sequence. Either the whole-number part or the fraction part has to + * be present; for decimal floating constants, either the period or + * the exponent part has to be present. + * + * The significand part is interpreted as a (decimal or hexadecimal) + * rational number; the digit sequence in the exponent part is + * interpreted as a decimal integer. For decimal floating constants, + * the exponent indicates the power of 10 by which the significand + * part is to be scaled. For hexadecimal floating constants, the + * exponent indicates the power of 2 by which the significand part is + * to be scaled. + * + * For decimal floating constants, and also for hexadecimal + * floating constants when FLT_RADIX is not a power of 2, the result + * is either the nearest representable value, or the larger or smaller + * representable value immediately adjacent to the nearest representable + * value, chosen in an implementation-defined manner. For hexadecimal + * floating constants when FLT_RADIX is a power of 2, the result is + * correctly rounded. + */ + @Nonnull + private Token number() + throws IOException, + LexerException { + Token tok; + int c = read(); + if (c == '0') { + int d = read(); + if (d == 'x' || d == 'X') { + tok = number_hex((char) d); + } else { + unread(d); + unread(c); + tok = number_decimal(); + } + } else if (Character.isDigit(c) || c == '.') { + unread(c); + tok = number_decimal(); + } else { + throw new LexerException("Asked to parse something as a number which isn't: " + (char) c); + } + return tok; + } + + @Nonnull + private Token identifier(int c) + throws IOException, + LexerException { + StringBuilder text = new StringBuilder(); + int d; + text.append((char) c); + for (;;) { + d = read(); + if (Character.isIdentifierIgnorable(d)) + ; else if (Character.isJavaIdentifierPart(d)) + text.append((char) d); + else + break; + } + unread(d); + return new Token(IDENTIFIER, text.toString()); + } + + @Nonnull + private Token whitespace(int c) + throws IOException, + LexerException { + StringBuilder text = new StringBuilder(); + int d; + text.append((char) c); + for (;;) { + d = read(); + if (ppvalid && isLineSeparator(d)) /* XXX Ugly. */ + + break; + if (Character.isWhitespace(d)) + text.append((char) d); + else + break; + } + unread(d); + return new Token(WHITESPACE, text.toString()); + } + + /* No token processed by cond() contains a newline. */ + @Nonnull + private Token cond(char c, int yes, int no) + throws IOException, + LexerException { + int d = read(); + if (c == d) + return new Token(yes); + unread(d); + return new Token(no); + } + + @Override + public Token token() + throws IOException, + LexerException { + Token tok = null; + + int _l = line; + int _c = column; + + int c = read(); + int d; + + switch (c) { + case '\n': + if (ppvalid) { + bol = true; + if (include) { + tok = new Token(NL, _l, _c, "\n"); + } else { + int nls = 0; + do { + nls++; + d = read(); + } while (d == '\n'); + unread(d); + char[] text = new char[nls]; + for (int i = 0; i < text.length; i++) + text[i] = '\n'; + // Skip the bol = false below. + tok = new Token(NL, _l, _c, new String(text)); + } + if (DEBUG) + System.out.println("lx: Returning NL: " + tok); + return tok; + } + /* Let it be handled as whitespace. */ + break; + + case '!': + tok = cond('=', NE, '!'); + break; + + case '#': + if (bol) + tok = new Token(HASH); + else + tok = cond('#', PASTE, '#'); + break; + + case '+': + d = read(); + if (d == '+') + tok = new Token(INC); + else if (d == '=') + tok = new Token(PLUS_EQ); + else + unread(d); + break; + case '-': + d = read(); + if (d == '-') + tok = new Token(DEC); + else if (d == '=') + tok = new Token(SUB_EQ); + else if (d == '>') + tok = new Token(ARROW); + else + unread(d); + break; + + case '*': + tok = cond('=', MULT_EQ, '*'); + break; + case '/': + d = read(); + if (d == '*') + tok = ccomment(); + else if (d == '/') + tok = cppcomment(); + else if (d == '=') + tok = new Token(DIV_EQ); + else + unread(d); + break; + + case '%': + d = read(); + if (d == '=') + tok = new Token(MOD_EQ); + else if (digraphs && d == '>') + tok = new Token('}'); // digraph + else if (digraphs && d == ':') + PASTE: + { + d = read(); + if (d != '%') { + unread(d); + tok = new Token('#'); // digraph + break PASTE; + } + d = read(); + if (d != ':') { + unread(d); // Unread 2 chars here. + unread('%'); + tok = new Token('#'); // digraph + break PASTE; + } + tok = new Token(PASTE); // digraph + } + else + unread(d); + break; + + case ':': + /* :: */ + d = read(); + if (digraphs && d == '>') + tok = new Token(']'); // digraph + else + unread(d); + break; + + case '<': + if (include) { + tok = string('<', '>'); + } else { + d = read(); + if (d == '=') + tok = new Token(LE); + else if (d == '<') + tok = cond('=', LSH_EQ, LSH); + else if (digraphs && d == ':') + tok = new Token('['); // digraph + else if (digraphs && d == '%') + tok = new Token('{'); // digraph + else + unread(d); + } + break; + + case '=': + tok = cond('=', EQ, '='); + break; + + case '>': + d = read(); + if (d == '=') + tok = new Token(GE); + else if (d == '>') + tok = cond('=', RSH_EQ, RSH); + else + unread(d); + break; + + case '^': + tok = cond('=', XOR_EQ, '^'); + break; + + case '|': + d = read(); + if (d == '=') + tok = new Token(OR_EQ); + else if (d == '|') + tok = cond('=', LOR_EQ, LOR); + else + unread(d); + break; + case '&': + d = read(); + if (d == '&') + tok = cond('=', LAND_EQ, LAND); + else if (d == '=') + tok = new Token(AND_EQ); + else + unread(d); + break; + + case '.': + d = read(); + if (d == '.') + tok = cond('.', ELLIPSIS, RANGE); + else + unread(d); + if (Character.isDigit(d)) { + unread('.'); + tok = number(); + } + /* XXX decimal fraction */ + break; + + case '\'': + tok = string('\'', '\''); + break; + + case '"': + tok = string('"', '"'); + break; + + case -1: + close(); + tok = new Token(EOF, _l, _c, "<eof>"); + break; + } + + if (tok == null) { + if (Character.isWhitespace(c)) { + tok = whitespace(c); + } else if (Character.isDigit(c)) { + unread(c); + tok = number(); + } else if (Character.isJavaIdentifierStart(c)) { + tok = identifier(c); + } else { + tok = new Token(c); + } + } + + if (bol) { + switch (tok.getType()) { + case WHITESPACE: + case CCOMMENT: + break; + default: + bol = false; + break; + } + } + + tok.setLocation(_l, _c); + if (DEBUG) + System.out.println("lx: Returning " + tok); + // (new Exception("here")).printStackTrace(System.out); + return tok; + } + + @Override + public void close() + throws IOException { + if (reader != null) { + reader.close(); + reader = null; + } + super.close(); + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/Macro.java b/src/main/java/com/jogamp/gluegen/jcpp/Macro.java new file mode 100644 index 0000000..e41273e --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/Macro.java @@ -0,0 +1,190 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A macro object. + * + * This encapsulates a name, an argument count, and a token stream + * for replacement. The replacement token stream may contain the + * extra tokens {@link Token#M_ARG} and {@link Token#M_STRING}. + */ +public class Macro { + + private Source source; + private final String name; + /* It's an explicit decision to keep these around here. We don't + * need to; the argument token type is M_ARG and the value + * is the index. The strings themselves are only used in + * stringification of the macro, for debugging. */ + private List<String> args; + private boolean variadic; + private List<Token> tokens; + + public Macro(final Source source, final String name) { + this.source = source; + this.name = name; + this.args = null; + this.variadic = false; + this.tokens = new ArrayList<Token>(); + } + + public Macro(final String name) { + this(null, name); + } + + /** + * Sets the Source from which this macro was parsed. + */ + public void setSource(final Source s) { + this.source = s; + } + + /** + * Returns the Source from which this macro was parsed. + * + * This method may return null if the macro was not parsed + * from a regular file. + */ + public Source getSource() { + return source; + } + + /** + * Returns the name of this macro. + */ + public String getName() { + return name; + } + + /** + * Sets the arguments to this macro. + */ + public void setArgs(final List<String> args) { + this.args = args; + } + + /** + * Returns true if this is a function-like macro. + */ + public boolean isFunctionLike() { + return args != null; + } + + /** + * Returns the number of arguments to this macro. + */ + public int getArgs() { + return args.size(); + } + + /** + * Sets the variadic flag on this Macro. + */ + public void setVariadic(final boolean b) { + this.variadic = b; + } + + /** + * Returns true if this is a variadic function-like macro. + */ + public boolean isVariadic() { + return variadic; + } + + /** + * Adds a token to the expansion of this macro. + */ + public void addToken(final Token tok) { + this.tokens.add(tok); + } + + /** + * Adds a "paste" operator to the expansion of this macro. + * + * A paste operator causes the next token added to be pasted + * to the previous token when the macro is expanded. + * It is an error for a macro to end with a paste token. + */ + public void addPaste(final Token tok) { + /* + * Given: tok0 ## tok1 + * We generate: M_PASTE, tok0, tok1 + * This extends as per a stack language: + * tok0 ## tok1 ## tok2 -> + * M_PASTE, tok0, M_PASTE, tok1, tok2 + */ + this.tokens.add(tokens.size() - 1, tok); + } + + /* pp */ List<Token> getTokens() { + return tokens; + } + /* pp */ void setTokens(final List<Token> tokens) { + this.tokens = tokens; + } + + /* Paste tokens are inserted before the first of the two pasted + * tokens, so it's a kind of bytecode notation. This method + * swaps them around again. We know that there will never be two + * sequential paste tokens, so a boolean is sufficient. */ + public String getText() { + final StringBuilder buf = new StringBuilder(); + boolean paste = false; + for (final Token tok : tokens) { + if (tok.getType() == Token.M_PASTE) { + assert paste == false : "Two sequential pastes."; + paste = true; + continue; + } else { + buf.append(tok.getText()); + } + if (paste) { + buf.append(" #" + "# "); + paste = false; + } + // buf.append(tokens.get(i)); + } + return buf.toString(); + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(name); + if (args != null) { + buf.append('('); + final Iterator<String> it = args.iterator(); + while (it.hasNext()) { + buf.append(it.next()); + if (it.hasNext()) + buf.append(", "); + else if (isVariadic()) + buf.append("..."); + } + buf.append(')'); + } + if (!tokens.isEmpty()) { + buf.append(" => ").append(getText()); + } + return buf.toString(); + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/MacroTokenSource.java b/src/main/java/com/jogamp/gluegen/jcpp/MacroTokenSource.java new file mode 100644 index 0000000..fbb2428 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/MacroTokenSource.java @@ -0,0 +1,209 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.Nonnull; + +import static com.jogamp.gluegen.jcpp.Token.*; + +/* This source should always be active, since we don't expand macros + * in any inactive context. */ +/* pp */ class MacroTokenSource extends Source { + + private final Macro macro; + private final Iterator<Token> tokens; /* Pointer into the macro. */ + + private final List<Argument> args; /* { unexpanded, expanded } */ + + private Iterator<Token> arg; /* "current expansion" */ + + /* pp */ MacroTokenSource(@Nonnull Macro m, @Nonnull List<Argument> args) { + this.macro = m; + this.tokens = m.getTokens().iterator(); + this.args = args; + this.arg = null; + } + + @Override + /* pp */ boolean isExpanding(@Nonnull Macro m) { + /* When we are expanding an arg, 'this' macro is not + * being expanded, and thus we may re-expand it. */ + if (/* XXX this.arg == null && */this.macro == m) + return true; + return super.isExpanding(m); + } + + /* XXX Called from Preprocessor [ugly]. */ + /* pp */ static void escape(@Nonnull StringBuilder buf, @Nonnull CharSequence cs) { + if (buf == null) + throw new NullPointerException("Buffer was null."); + if (cs == null) + throw new NullPointerException("CharSequence was null."); + for (int i = 0; i < cs.length(); i++) { + char c = cs.charAt(i); + switch (c) { + case '\\': + buf.append("\\\\"); + break; + case '"': + buf.append("\\\""); + break; + case '\n': + buf.append("\\n"); + break; + case '\r': + buf.append("\\r"); + break; + default: + buf.append(c); + } + } + } + + private void concat(@Nonnull StringBuilder buf, @Nonnull Argument arg) { + for (Token tok : arg) { + buf.append(tok.getText()); + } + } + + @Nonnull + private Token stringify(@Nonnull Token pos, @Nonnull Argument arg) { + StringBuilder buf = new StringBuilder(); + concat(buf, arg); + // System.out.println("Concat: " + arg + " -> " + buf); + StringBuilder str = new StringBuilder("\""); + escape(str, buf); + str.append("\""); + // System.out.println("Escape: " + buf + " -> " + str); + return new Token(STRING, + pos.getLine(), pos.getColumn(), + str.toString(), buf.toString()); + } + + + /* At this point, we have consumed the first M_PASTE. + * @see Macro#addPaste(Token) */ + private void paste(@Nonnull Token ptok) + throws IOException, + LexerException { + StringBuilder buf = new StringBuilder(); + // Token err = null; + /* We know here that arg is null or expired, + * since we cannot paste an expanded arg. */ + + int count = 2; + for (int i = 0; i < count; i++) { + if (!tokens.hasNext()) { + /* XXX This one really should throw. */ + error(ptok.getLine(), ptok.getColumn(), + "Paste at end of expansion"); + buf.append(' ').append(ptok.getText()); + break; + } + Token tok = tokens.next(); + // System.out.println("Paste " + tok); + switch (tok.getType()) { + case M_PASTE: + /* One extra to paste, plus one because the + * paste token didn't count. */ + count += 2; + ptok = tok; + break; + case M_ARG: + int idx = ((Integer) tok.getValue()).intValue(); + concat(buf, args.get(idx)); + break; + /* XXX Test this. */ + case CCOMMENT: + case CPPCOMMENT: + break; + default: + buf.append(tok.getText()); + break; + } + } + + /* Push and re-lex. */ + /* + StringBuilder src = new StringBuilder(); + escape(src, buf); + StringLexerSource sl = new StringLexerSource(src.toString()); + */ + StringLexerSource sl = new StringLexerSource(buf.toString()); + + /* XXX Check that concatenation produces a valid token. */ + arg = new SourceIterator(sl); + } + + @Override + public Token token() + throws IOException, + LexerException { + for (;;) { + /* Deal with lexed tokens first. */ + + if (arg != null) { + if (arg.hasNext()) { + Token tok = arg.next(); + /* XXX PASTE -> INVALID. */ + assert tok.getType() != M_PASTE : + "Unexpected paste token"; + return tok; + } + arg = null; + } + + if (!tokens.hasNext()) + return new Token(EOF, -1, -1, ""); /* End of macro. */ + + Token tok = tokens.next(); + int idx; + switch (tok.getType()) { + case M_STRING: + /* Use the nonexpanded arg. */ + idx = ((Integer) tok.getValue()).intValue(); + return stringify(tok, args.get(idx)); + case M_ARG: + /* Expand the arg. */ + idx = ((Integer) tok.getValue()).intValue(); + // System.out.println("Pushing arg " + args.get(idx)); + arg = args.get(idx).expansion(); + break; + case M_PASTE: + paste(tok); + break; + default: + return tok; + } + } /* for */ + + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("expansion of ").append(macro.getName()); + Source parent = getParent(); + if (parent != null) + buf.append(" in ").append(String.valueOf(parent)); + return buf.toString(); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/Main.java b/src/main/java/com/jogamp/gluegen/jcpp/Main.java new file mode 100644 index 0000000..9f06fb0 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/Main.java @@ -0,0 +1,195 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.File; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import javax.annotation.Nonnull; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * (Currently a simple test class). + */ +public class Main { + + private static final Logger LOG = LoggerFactory.getLogger(Main.class); + + @Nonnull + private static CharSequence getWarnings() { + StringBuilder buf = new StringBuilder(); + for (Warning w : Warning.values()) { + if (buf.length() > 0) + buf.append(", "); + String name = w.name().toLowerCase(); + buf.append(name.replace('_', '-')); + } + return buf; + } + + public static void main(String[] args) throws Exception { + (new Main()).run(args); + } + + public void run(String[] args) throws Exception { + + OptionParser parser = new OptionParser(); + OptionSpec<?> helpOption = parser.accepts("help", + "Displays command-line help.") + .forHelp(); + OptionSpec<?> versionOption = parser.acceptsAll(Arrays.asList("version"), + "Displays the product version (" + BuildMetadata.getInstance().getVersion() + ") and exits.") + .forHelp(); + + OptionSpec<?> debugOption = parser.acceptsAll(Arrays.asList("debug"), + "Enables debug output."); + + OptionSpec<String> defineOption = parser.acceptsAll(Arrays.asList("define", "D"), + "Defines the given macro.") + .withRequiredArg().ofType(String.class).describedAs("name[=definition]"); + OptionSpec<String> undefineOption = parser.acceptsAll(Arrays.asList("undefine", "U"), + "Undefines the given macro, previously either builtin or defined using -D.") + .withRequiredArg().describedAs("name"); + OptionSpec<File> includeOption = parser.accepts("include", + "Process file as if \"#" + "include \"file\"\" appeared as the first line of the primary source file.") + .withRequiredArg().ofType(File.class).describedAs("file"); + OptionSpec<File> incdirOption = parser.acceptsAll(Arrays.asList("incdir", "I"), + "Adds the directory dir to the list of directories to be searched for header files.") + .withRequiredArg().ofType(File.class).describedAs("dir"); + OptionSpec<File> iquoteOption = parser.acceptsAll(Arrays.asList("iquote"), + "Adds the directory dir to the list of directories to be searched for header files included using \"\".") + .withRequiredArg().ofType(File.class).describedAs("dir"); + OptionSpec<String> warningOption = parser.acceptsAll(Arrays.asList("warning", "W"), + "Enables the named warning class (" + getWarnings() + ").") + .withRequiredArg().ofType(String.class).describedAs("warning"); + OptionSpec<Void> noWarningOption = parser.acceptsAll(Arrays.asList("no-warnings", "w"), + "Disables ALL warnings."); + OptionSpec<File> inputsOption = parser.nonOptions() + .ofType(File.class).describedAs("Files to process."); + + OptionSet options = parser.parse(args); + + if (options.has(helpOption)) { + parser.printHelpOn(System.out); + return; + } + + if (options.has(versionOption)) { + version(System.out); + return; + } + + Preprocessor pp = new Preprocessor(); + pp.addFeature(Feature.DIGRAPHS); + pp.addFeature(Feature.TRIGRAPHS); + pp.addFeature(Feature.LINEMARKERS); + pp.addWarning(Warning.IMPORT); + pp.setListener(new DefaultPreprocessorListener()); + pp.addMacro("__JCPP__"); + pp.getSystemIncludePath().add("/usr/local/include"); + pp.getSystemIncludePath().add("/usr/include"); + pp.getFrameworksPath().add("/System/Library/Frameworks"); + pp.getFrameworksPath().add("/Library/Frameworks"); + pp.getFrameworksPath().add("/Local/Library/Frameworks"); + + if (options.has(debugOption)) + pp.addFeature(Feature.DEBUG); + + if (options.has(noWarningOption)) + pp.getWarnings().clear(); + + for (String warning : options.valuesOf(warningOption)) { + warning = warning.toUpperCase(); + warning = warning.replace('-', '_'); + if (warning.equals("ALL")) + pp.addWarnings(EnumSet.allOf(Warning.class)); + else + pp.addWarning(Enum.valueOf(Warning.class, warning)); + } + + for (String arg : options.valuesOf(defineOption)) { + int idx = arg.indexOf('='); + if (idx == -1) + pp.addMacro(arg); + else + pp.addMacro(arg.substring(0, idx), arg.substring(idx + 1)); + } + for (String arg : options.valuesOf(undefineOption)) { + pp.getMacros().remove(arg); + } + + for (File dir : options.valuesOf(incdirOption)) + pp.getSystemIncludePath().add(dir.getAbsolutePath()); + for (File dir : options.valuesOf(iquoteOption)) + pp.getQuoteIncludePath().add(dir.getAbsolutePath()); + for (File file : options.valuesOf(includeOption)) + // Comply exactly with spec. + pp.addInput(new StringLexerSource("#" + "include \"" + file + "\"\n")); + + List<File> inputs = options.valuesOf(inputsOption); + if (inputs.isEmpty()) { + pp.addInput(new InputLexerSource(System.in)); + } else { + for (File input : inputs) + pp.addInput(new FileLexerSource(input)); + } + + if (pp.getFeature(Feature.DEBUG)) { + LOG.info("#" + "include \"...\" search starts here:"); + for (String dir : pp.getQuoteIncludePath()) + LOG.info(" " + dir); + LOG.info("#" + "include <...> search starts here:"); + for (String dir : pp.getSystemIncludePath()) + LOG.info(" " + dir); + LOG.info("End of search list."); + } + + try { + for (;;) { + Token tok = pp.token(); + if (tok == null) + break; + if (tok.getType() == Token.EOF) + break; + System.out.print(tok.getText()); + } + } catch (Exception e) { + StringBuilder buf = new StringBuilder("Preprocessor failed:\n"); + Source s = pp.getSource(); + while (s != null) { + buf.append(" -> ").append(s).append("\n"); + s = s.getParent(); + } + LOG.error(buf.toString(), e); + } + + } + + private static void version(@Nonnull PrintStream out) { + BuildMetadata metadata = BuildMetadata.getInstance(); + out.println("Anarres Java C Preprocessor version " + metadata.getVersion() + " change-id " + metadata.getChangeId()); + out.println("Copyright (C) 2008-2014 Shevek (http://www.anarres.org/)."); + out.println("This is free software; see the source for copying conditions. There is NO"); + out.println("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/NumericValue.java b/src/main/java/com/jogamp/gluegen/jcpp/NumericValue.java new file mode 100644 index 0000000..8e79fd3 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/NumericValue.java @@ -0,0 +1,215 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.math.BigDecimal; +import java.math.BigInteger; +import javax.annotation.CheckForNull; +import javax.annotation.CheckForSigned; +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; + +public class NumericValue extends Number { + + public static final int F_UNSIGNED = 1; + public static final int F_INT = 2; + public static final int F_LONG = 4; + public static final int F_LONGLONG = 8; + public static final int F_FLOAT = 16; + public static final int F_DOUBLE = 32; + + public static final int FF_SIZE = F_INT | F_LONG | F_LONGLONG | F_FLOAT | F_DOUBLE; + + private final int base; + private final String integer; + private String fraction; + private int expbase = 0; + private String exponent; + private int flags; + + public NumericValue(int base, String integer) { + this.base = base; + this.integer = integer; + } + + @Nonnegative + public int getBase() { + return base; + } + + @Nonnull + public String getIntegerPart() { + return integer; + } + + @CheckForNull + public String getFractionalPart() { + return fraction; + } + + /* pp */ void setFractionalPart(String fraction) { + this.fraction = fraction; + } + + @CheckForSigned + public int getExponentBase() { + return expbase; + } + + @CheckForNull + public String getExponent() { + return exponent; + } + + /* pp */ void setExponent(int expbase, String exponent) { + this.expbase = expbase; + this.exponent = exponent; + } + + public int getFlags() { + return flags; + } + + /* pp */ void setFlags(int flags) { + this.flags = flags; + } + + /** + * So, it turns out that parsing arbitrary bases into arbitrary + * precision numbers is nontrivial, and this routine gets it wrong + * in many important cases. + */ + @Nonnull + public BigDecimal toBigDecimal() { + int scale = 0; + String text = getIntegerPart(); + String t_fraction = getFractionalPart(); + if (t_fraction != null) { + text += getFractionalPart(); + // XXX Wrong for anything but base 10. + scale += t_fraction.length(); + } + String t_exponent = getExponent(); + if (t_exponent != null) + scale -= Integer.parseInt(t_exponent); + BigInteger unscaled = new BigInteger(text, getBase()); + return new BigDecimal(unscaled, scale); + } + + @Nonnull + public Number toJavaLangNumber() { + int flags = getFlags(); + if ((flags & F_DOUBLE) != 0) + return doubleValue(); + else if ((flags & F_FLOAT) != 0) + return floatValue(); + else if ((flags & (F_LONG | F_LONGLONG)) != 0) + return longValue(); + else if ((flags & F_INT) != 0) + return intValue(); + else if (getFractionalPart() != null) + return doubleValue(); // .1 is a double in Java. + else if (getExponent() != null) + return doubleValue(); + else + return intValue(); + } + + private int exponentValue() { + return Integer.parseInt(exponent, 10); + } + + @Override + public int intValue() { + int v = integer.isEmpty() ? 0 : Integer.parseInt(integer, base); + if (expbase == 2) + v = v << exponentValue(); + else if (expbase != 0) + v = (int) (v * Math.pow(expbase, exponentValue())); + return v; + } + + @Override + public long longValue() { + long v = integer.isEmpty() ? 0 : Long.parseLong(integer, base); + if (expbase == 2) + v = v << exponentValue(); + else if (expbase != 0) + v = (long) (v * Math.pow(expbase, exponentValue())); + return v; + } + + @Override + public float floatValue() { + if (getBase() != 10) + return longValue(); + return Float.parseFloat(toString()); + } + + @Override + public double doubleValue() { + if (getBase() != 10) + return longValue(); + return Double.parseDouble(toString()); + } + + private boolean appendFlags(StringBuilder buf, String suffix, int flag) { + if ((getFlags() & flag) != flag) + return false; + buf.append(suffix); + return true; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + switch (base) { + case 8: + buf.append('0'); + break; + case 10: + break; + case 16: + buf.append("0x"); + break; + case 2: + buf.append('b'); + break; + default: + buf.append("[base-").append(base).append("]"); + break; + } + buf.append(getIntegerPart()); + if (getFractionalPart() != null) + buf.append('.').append(getFractionalPart()); + if (getExponent() != null) { + buf.append(base > 10 ? 'p' : 'e'); + buf.append(getExponent()); + } + /* + if (appendFlags(buf, "ui", F_UNSIGNED | F_INT)); + else if (appendFlags(buf, "ul", F_UNSIGNED | F_LONG)); + else if (appendFlags(buf, "ull", F_UNSIGNED | F_LONGLONG)); + else if (appendFlags(buf, "i", F_INT)); + else if (appendFlags(buf, "l", F_LONG)); + else if (appendFlags(buf, "ll", F_LONGLONG)); + else if (appendFlags(buf, "f", F_FLOAT)); + else if (appendFlags(buf, "d", F_DOUBLE)); + */ + return buf.toString(); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/Preprocessor.java b/src/main/java/com/jogamp/gluegen/jcpp/Preprocessor.java new file mode 100644 index 0000000..31eca4e --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/Preprocessor.java @@ -0,0 +1,2152 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.TreeMap; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +import static com.jogamp.gluegen.jcpp.PreprocessorCommand.*; +import static com.jogamp.gluegen.jcpp.Token.*; + +import com.jogamp.gluegen.Logging; +import com.jogamp.gluegen.Logging.LoggerIf; +import com.jogamp.gluegen.jcpp.PreprocessorListener.SourceChangeEvent; + +/** + * A C Preprocessor. + * The Preprocessor outputs a token stream which does not need + * re-lexing for C or C++. Alternatively, the output text may be + * reconstructed by concatenating the {@link Token#getText() text} + * values of the returned {@link Token Tokens}. (See + * {@link CppReader}, which does this.) + */ +/* + * Source file name and line number information is conveyed by lines of the form + * + * # linenum filename flags + * + * These are called linemarkers. They are inserted as needed into + * the output (but never within a string or character constant). They + * mean that the following line originated in file filename at line + * linenum. filename will never contain any non-printing characters; + * they are replaced with octal escape sequences. + * + * After the file name comes zero or more flags, which are `1', `2', + * `3', or `4'. If there are multiple flags, spaces separate them. Here + * is what the flags mean: + * + * `1' + * This indicates the start of a new file. + * `2' + * This indicates returning to a file (after having included another + * file). + * `3' + * This indicates that the following text comes from a system header + * file, so certain warnings should be suppressed. + * `4' + * This indicates that the following text should be treated as being + * wrapped in an implicit extern "C" block. + */ +public class Preprocessor implements Closeable { + + private static final LoggerIf LOG = Logging.getLogger(Preprocessor.class); + + private static final Source INTERNAL = new Source() { + @Override + public Token token() + throws IOException, + LexerException { + throw new LexerException("Cannot read from " + getName()); + } + + @Override + public String getPath() { + return "<internal-data>"; + } + + @Override + public String getName() { + return "internal data"; + } + }; + private static final Macro __LINE__ = new Macro(INTERNAL, "__LINE__"); + private static final Macro __FILE__ = new Macro(INTERNAL, "__FILE__"); + private static final Macro __COUNTER__ = new Macro(INTERNAL, "__COUNTER__"); + + private final List<Source> inputs; + + /* The fundamental engine. */ + private final Map<String, Macro> macros; + private final Stack<State> states; + private Source source; + + /* Miscellaneous support. */ + private int counter; + private final Set<String> onceseenpaths = new HashSet<String>(); + private final List<VirtualFile> includes = new ArrayList<VirtualFile>(); + + /* Support junk to make it work like cpp */ + private List<String> quoteincludepath; /* -iquote */ + + private List<String> sysincludepath; /* -I */ + + private List<String> frameworkspath; + private final Set<Feature> features; + private final Set<Warning> warnings; + private VirtualFileSystem filesystem; + private PreprocessorListener listener; + + public Preprocessor() { + this.inputs = new ArrayList<Source>(); + + this.macros = new HashMap<String, Macro>(); + macros.put(__LINE__.getName(), __LINE__); + macros.put(__FILE__.getName(), __FILE__); + macros.put(__COUNTER__.getName(), __COUNTER__); + this.states = new Stack<State>(); + states.push(new State()); + this.source = null; + + this.counter = 0; + + this.quoteincludepath = new ArrayList<String>(); + this.sysincludepath = new ArrayList<String>(); + this.frameworkspath = new ArrayList<String>(); + this.features = EnumSet.noneOf(Feature.class); + this.warnings = EnumSet.noneOf(Warning.class); + this.filesystem = new JavaFileSystem(); + this.listener = null; + } + + public Preprocessor(@Nonnull final Source initial) { + this(); + addInput(initial); + } + + /** Equivalent to + * 'new Preprocessor(new {@link FileLexerSource}(file))' + */ + public Preprocessor(@Nonnull final File file) + throws IOException { + this(new FileLexerSource(file)); + } + + /** + * Sets the VirtualFileSystem used by this Preprocessor. + */ + public void setFileSystem(@Nonnull final VirtualFileSystem filesystem) { + this.filesystem = filesystem; + } + + /** + * Returns the VirtualFileSystem used by this Preprocessor. + */ + @Nonnull + public VirtualFileSystem getFileSystem() { + return filesystem; + } + + /** + * Sets the PreprocessorListener which handles events for + * this Preprocessor. + * + * The listener is notified of warnings, errors and source + * changes, amongst other things. + */ + public void setListener(@Nonnull final PreprocessorListener listener) { + this.listener = listener; + Source s = source; + while (s != null) { + // s.setListener(listener); + s.init(this); + s = s.getParent(); + } + } + + /** + * Returns the PreprocessorListener which handles events for + * this Preprocessor. + */ + @Nonnull + public PreprocessorListener getListener() { + return listener; + } + + /** + * Returns the feature-set for this Preprocessor. + * + * This set may be freely modified by user code. + */ + @Nonnull + public Set<Feature> getFeatures() { + return features; + } + + /** + * Adds a feature to the feature-set of this Preprocessor. + */ + public void addFeature(@Nonnull final Feature f) { + features.add(f); + } + + /** + * Adds features to the feature-set of this Preprocessor. + */ + public void addFeatures(@Nonnull final Collection<Feature> f) { + features.addAll(f); + } + + /** + * Adds features to the feature-set of this Preprocessor. + */ + public void addFeatures(final Feature... f) { + addFeatures(Arrays.asList(f)); + } + + /** + * Returns true if the given feature is in + * the feature-set of this Preprocessor. + */ + public boolean getFeature(@Nonnull final Feature f) { + return features.contains(f); + } + + /** + * Returns the warning-set for this Preprocessor. + * + * This set may be freely modified by user code. + */ + @Nonnull + public Set<Warning> getWarnings() { + return warnings; + } + + /** + * Adds a warning to the warning-set of this Preprocessor. + */ + public void addWarning(@Nonnull final Warning w) { + warnings.add(w); + } + + /** + * Adds warnings to the warning-set of this Preprocessor. + */ + public void addWarnings(@Nonnull final Collection<Warning> w) { + warnings.addAll(w); + } + + /** + * Returns true if the given warning is in + * the warning-set of this Preprocessor. + */ + public boolean getWarning(@Nonnull final Warning w) { + return warnings.contains(w); + } + + /** + * Adds input for the Preprocessor. + * + * Inputs are processed in the order in which they are added. + */ + public void addInput(@Nonnull final Source source) { + source.init(this); + inputs.add(source); + } + + /** + * Adds input for the Preprocessor. + * + * @see #addInput(Source) + */ + public void addInput(@Nonnull final File file) + throws IOException { + addInput(new FileLexerSource(file)); + } + + /** + * Handles an error. + * + * If a PreprocessorListener is installed, it receives the + * error. Otherwise, an exception is thrown. + */ + protected void error(final int line, final int column, @Nonnull final String msg) + throws LexerException { + if (listener != null) + listener.handleError(source, line, column, msg); + else + throw new LexerException("Error at " + line + ":" + column + ": " + msg); + } + + /** + * Handles an error. + * + * If a PreprocessorListener is installed, it receives the + * error. Otherwise, an exception is thrown. + * + * @see #error(int, int, String) + */ + protected void error(@Nonnull final Token tok, @Nonnull final String msg) + throws LexerException { + error(tok.getLine(), tok.getColumn(), msg); + } + + /** + * Handles a warning. + * + * If a PreprocessorListener is installed, it receives the + * warning. Otherwise, an exception is thrown. + */ + protected void warning(final int line, final int column, @Nonnull final String msg) + throws LexerException { + if (warnings.contains(Warning.ERROR)) + error(line, column, msg); + else if (listener != null) + listener.handleWarning(source, line, column, msg); + else + throw new LexerException("Warning at " + line + ":" + column + ": " + msg); + } + + /** + * Handles a warning. + * + * If a PreprocessorListener is installed, it receives the + * warning. Otherwise, an exception is thrown. + * + * @see #warning(int, int, String) + */ + protected void warning(@Nonnull final Token tok, @Nonnull final String msg) + throws LexerException { + warning(tok.getLine(), tok.getColumn(), msg); + } + + /** + * Adds a Macro to this Preprocessor. + * + * The given {@link Macro} object encapsulates both the name + * and the expansion. + * @throws IOException + */ + public void addMacro(@Nonnull final Macro m) throws LexerException, IOException { + // System.out.println("Macro " + m); + final String name = m.getName(); + /* Already handled as a source error in macro(). */ + if ("defined".equals(name)) + throw new LexerException("Cannot redefine name 'defined'"); + + if ( isActive() && null != source && !source.isExpanding(m) ) { + m.setTokens( expand( m.getTokens() ) ); + } + macros.put(m.getName(), m); + } + + /** + * Defines the given name as a macro. + * + * The String value is lexed into a token stream, which is + * used as the macro expansion. + */ + public void addMacro(@Nonnull final String name, @Nonnull final String value) + throws LexerException { + try { + final Macro m = new Macro(name); + final StringLexerSource s = new StringLexerSource(value); + for (;;) { + final Token tok = s.token(); + if (tok.getType() == EOF) + break; + m.addToken(tok); + } + addMacro(m); + } catch (final IOException e) { + throw new LexerException(e); + } + } + + /** + * Defines the given name as a macro, with the value <code>1</code>. + * + * This is a convnience method, and is equivalent to + * <code>addMacro(name, "1")</code>. + */ + public void addMacro(@Nonnull final String name) + throws LexerException { + addMacro(name, "1"); + } + + /** + * Sets the user include path used by this Preprocessor. + */ + /* Note for future: Create an IncludeHandler? */ + public void setQuoteIncludePath(@Nonnull final List<String> path) { + this.quoteincludepath = path; + } + + /** + * Returns the user include-path of this Preprocessor. + * + * This list may be freely modified by user code. + */ + @Nonnull + public List<String> getQuoteIncludePath() { + return quoteincludepath; + } + + /** + * Sets the system include path used by this Preprocessor. + */ + /* Note for future: Create an IncludeHandler? */ + public void setSystemIncludePath(@Nonnull final List<String> path) { + this.sysincludepath = path; + } + + /** + * Returns the system include-path of this Preprocessor. + * + * This list may be freely modified by user code. + */ + @Nonnull + public List<String> getSystemIncludePath() { + return sysincludepath; + } + + /** + * Sets the Objective-C frameworks path used by this Preprocessor. + */ + /* Note for future: Create an IncludeHandler? */ + public void setFrameworksPath(@Nonnull final List<String> path) { + this.frameworkspath = path; + } + + /** + * Returns the Objective-C frameworks path used by this + * Preprocessor. + * + * This list may be freely modified by user code. + */ + @Nonnull + public List<String> getFrameworksPath() { + return frameworkspath; + } + + /** + * Returns the Map of Macros parsed during the run of this + * Preprocessor. + */ + @Nonnull + public Map<String, Macro> getMacros() { + return macros; + } + + /** + * Returns the named macro. + * + * While you can modify the returned object, unexpected things + * might happen if you do. + */ + @CheckForNull + public Macro getMacro(final String name) { + return macros.get(name); + } + + /** + * Returns the list of {@link VirtualFile VirtualFiles} which have been + * included by this Preprocessor. + * + * This does not include any {@link Source} provided to the constructor + * or {@link #addInput(java.io.File)} or {@link #addInput(Source)}. + */ + @Nonnull + public List<? extends VirtualFile> getIncludes() { + return includes; + } + + /* States */ + private void push_state() { + final State top = states.peek(); + states.push(new State(top)); + } + + private void pop_state() + throws LexerException { + final State s = states.pop(); + if (states.isEmpty()) { + error(0, 0, "#" + "endif without #" + "if"); + states.push(s); + } + } + + private boolean isActive() { + final State state = states.peek(); + return state.isParentActive() && state.isActive(); + } + + + /* Sources */ + /** + * Returns the top Source on the input stack. + * + * @see Source + * @see #push_source(Source,boolean) + * @see #pop_source() + */ + // @CheckForNull + public Source getSource() { + return source; + } + + /** + * Pushes a Source onto the input stack. + * + * @see #getSource() + * @see #pop_source() + */ + protected void push_source(@Nonnull final Source source, final boolean autopop) { + source.init(this); + source.setParent(this.source, autopop); + // source.setListener(listener); + if (listener != null) + listener.handleSourceChange(this.source, SourceChangeEvent.SUSPEND); + this.source = source; + if (listener != null) + listener.handleSourceChange(this.source, SourceChangeEvent.PUSH); + } + + /** + * Pops a Source from the input stack. + * + * @see #getSource() + * @see #push_source(Source,boolean) + */ + @CheckForNull + protected Token pop_source(final boolean linemarker) + throws IOException { + if (listener != null) + listener.handleSourceChange(this.source, SourceChangeEvent.POP); + final Source s = this.source; + this.source = s.getParent(); + /* Always a noop unless called externally. */ + s.close(); + if (listener != null && this.source != null) + listener.handleSourceChange(this.source, SourceChangeEvent.RESUME); + + final Source t = getSource(); + if (getFeature(Feature.LINEMARKERS) + && s.isNumbered() + && t != null) { + /* We actually want 'did the nested source + * contain a newline token', which isNumbered() + * approximates. This is not perfect, but works. + * FIXME: Removed the '+ 1', since all lines were off by one. + * This solves this case, but I don't know _why_ this was here in the first place. + */ + return line_token(t.getLine() /* SEE ABOVE: + 1 */, t.getName(), " 2"); + } + + return null; + } + + protected void pop_source() + throws IOException { + pop_source(false); + } + + @Nonnull + private Token next_source() { + if (inputs.isEmpty()) + return new Token(EOF); + final Source s = inputs.remove(0); + push_source(s, true); + return line_token(s.getLine(), s.getName(), " 1"); + } + + /* Source tokens */ + private Token source_token; + + /* XXX Make this include the NL, and make all cpp directives eat + * their own NL. */ + @Nonnull + private Token line_token(final int line, @CheckForNull final String name, @Nonnull final String extra) { + final StringBuilder buf = new StringBuilder(); + buf.append("#line ").append(line) + .append(" \""); + /* XXX This call to escape(name) is correct but ugly. */ + if (name == null) + buf.append("<no file>"); + else + MacroTokenSource.escape(buf, name); + buf.append("\"").append(extra).append("\n"); + return new Token(P_LINE, line, 0, buf.toString(), null); + } + + @Nonnull + private Token source_token() + throws IOException, + LexerException { + if (source_token != null) { + final Token tok = source_token; + source_token = null; + if (getFeature(Feature.DEBUG)) + LOG.debug("Returning unget token " + tok); + return tok; + } + + for (;;) { + final Source s = getSource(); + if (s == null) { + final Token t = next_source(); + if (t.getType() == P_LINE && !getFeature(Feature.LINEMARKERS)) + continue; + return t; + } + final Token tok = s.token(); + /* XXX Refactor with skipline() */ + if (tok.getType() == EOF && s.isAutopop()) { + // System.out.println("Autopop " + s); + final Token mark = pop_source(true); + if (mark != null) + return mark; + continue; + } + if (getFeature(Feature.DEBUG)) + LOG.debug("Returning fresh token " + tok); + return tok; + } + } + + private void source_untoken(final Token tok) { + if (this.source_token != null) + throw new IllegalStateException("Cannot return two tokens"); + this.source_token = tok; + } + + private boolean isWhite(final Token tok) { + final int type = tok.getType(); + return (type == WHITESPACE) + || (type == CCOMMENT) + || (type == CPPCOMMENT); + } + + private Token source_token_nonwhite() + throws IOException, + LexerException { + Token tok; + do { + tok = source_token(); + } while (isWhite(tok)); + return tok; + } + + /** + * Returns an NL or an EOF token. + * + * The metadata on the token will be correct, which is better + * than generating a new one. + * + * This method can, as of recent patches, return a P_LINE token. + */ + private Token source_skipline(final boolean white) + throws IOException, + LexerException { + // (new Exception("skipping line")).printStackTrace(System.out); + final Source s = getSource(); + final Token tok = s.skipline(white); + /* XXX Refactor with source_token() */ + if (tok.getType() == EOF && s.isAutopop()) { + // System.out.println("Autopop " + s); + final Token mark = pop_source(true); + if (mark != null) + return mark; + } + return tok; + } + + /* processes and expands a macro. */ + private boolean macro(final Macro m, final Token orig) + throws IOException, + LexerException { + Token tok; + List<Argument> args; + + // System.out.println("pp: expanding " + m); + if (m.isFunctionLike()) { + OPEN: + for (;;) { + tok = source_token(); + // System.out.println("pp: open: token is " + tok); + switch (tok.getType()) { + case WHITESPACE: /* XXX Really? */ + + case CCOMMENT: + case CPPCOMMENT: + case NL: + break; /* continue */ + + case '(': + break OPEN; + default: + source_untoken(tok); + return false; + } + } + + // tok = expanded_token_nonwhite(); + tok = source_token_nonwhite(); + + /* We either have, or we should have args. + * This deals elegantly with the case that we have + * one empty arg. */ + if (tok.getType() != ')' || m.getArgs() > 0) { + args = new ArrayList<Argument>(); + + Argument arg = new Argument(); + int depth = 0; + boolean space = false; + + ARGS: + for (;;) { + // System.out.println("pp: arg: token is " + tok); + switch (tok.getType()) { + case EOF: + error(tok, "EOF in macro args"); + return false; + + case ',': + if (depth == 0) { + if (m.isVariadic() + && /* We are building the last arg. */ args.size() == m.getArgs() - 1) { + /* Just add the comma. */ + arg.addToken(tok); + } else { + args.add(arg); + arg = new Argument(); + } + } else { + arg.addToken(tok); + } + space = false; + break; + case ')': + if (depth == 0) { + args.add(arg); + break ARGS; + } else { + depth--; + arg.addToken(tok); + } + space = false; + break; + case '(': + depth++; + arg.addToken(tok); + space = false; + break; + + case WHITESPACE: + case CCOMMENT: + case CPPCOMMENT: + case NL: + /* Avoid duplicating spaces. */ + space = true; + break; + + default: + /* Do not put space on the beginning of + * an argument token. */ + if (space && !arg.isEmpty()) + arg.addToken(Token.space); + arg.addToken(tok); + space = false; + break; + + } + // tok = expanded_token(); + tok = source_token(); + } + /* space may still be true here, thus trailing space + * is stripped from arguments. */ + + if (args.size() != m.getArgs()) { + if (m.isVariadic()) { + if (args.size() == m.getArgs() - 1) { + args.add(new Argument()); + } else { + error(tok, + "variadic macro " + m.getName() + + " has at least " + (m.getArgs() - 1) + " parameters " + + "but given " + args.size() + " args"); + return false; + } + } else { + error(tok, + "macro " + m.getName() + + " has " + m.getArgs() + " parameters " + + "but given " + args.size() + " args"); + /* We could replay the arg tokens, but I + * note that GNU cpp does exactly what we do, + * i.e. output the macro name and chew the args. + */ + return false; + } + } + + for (final Argument a : args) { + a.expand(this); + } + + // System.out.println("Macro " + m + " args " + args); + } else { + /* nargs == 0 and we (correctly) got () */ + args = null; + } + + } else { + /* Macro without args. */ + args = null; + } + + if (m == __LINE__) { + push_source(new FixedTokenSource( + new Token[]{new Token(NUMBER, + orig.getLine(), orig.getColumn(), + Integer.toString(orig.getLine()), + new NumericValue(10, Integer.toString(orig.getLine())))} + ), true); + } else if (m == __FILE__) { + final StringBuilder buf = new StringBuilder("\""); + String name = getSource().getName(); + if (name == null) + name = "<no file>"; + for (int i = 0; i < name.length(); i++) { + final char c = name.charAt(i); + switch (c) { + case '\\': + buf.append("\\\\"); + break; + case '"': + buf.append("\\\""); + break; + default: + buf.append(c); + break; + } + } + buf.append("\""); + final String text = buf.toString(); + push_source(new FixedTokenSource( + new Token[]{new Token(STRING, + orig.getLine(), orig.getColumn(), + text, text)} + ), true); + } else if (m == __COUNTER__) { + /* This could equivalently have been done by adding + * a special Macro subclass which overrides getTokens(). */ + final int value = this.counter++; + push_source(new FixedTokenSource( + new Token[]{new Token(NUMBER, + orig.getLine(), orig.getColumn(), + Integer.toString(value), + new NumericValue(10, Integer.toString(value)))} + ), true); + } else { + push_source(new MacroTokenSource(m, args), true); + } + + return true; + } + + /** + * Expands an argument. + */ + /* I'd rather this were done lazily, but doing so breaks spec. */ + @Nonnull + /* pp */ List<Token> expand(@Nonnull final List<Token> arg) + throws IOException, + LexerException { + final List<Token> expansion = new ArrayList<Token>(); + boolean space = false; + + push_source(new FixedTokenSource(arg), false); + + EXPANSION: + for (;;) { + final Token tok = expanded_token(); + switch (tok.getType()) { + case EOF: + break EXPANSION; + + case WHITESPACE: + case CCOMMENT: + case CPPCOMMENT: + space = true; + break; + + default: + if (space && !expansion.isEmpty()) + expansion.add(Token.space); + expansion.add(tok); + space = false; + break; + } + } + + // Always returns null. + pop_source(false); + + return expansion; + } + + /* processes a #define directive */ + private Token define() + throws IOException, + LexerException { + Token tok = source_token_nonwhite(); + if (tok.getType() != IDENTIFIER) { + error(tok, "Expected identifier"); + return source_skipline(false); + } + /* if predefined */ + final String name = tok.getText(); + if ("defined".equals(name)) { + error(tok, "Cannot redefine name 'defined'"); + return source_skipline(false); + } + + final Macro m = new Macro(getSource(), name); + List<String> args; + + tok = source_token(); + if (tok.getType() == '(') { + tok = source_token_nonwhite(); + if (tok.getType() != ')') { + args = new ArrayList<String>(); + ARGS: + for (;;) { + switch (tok.getType()) { + case IDENTIFIER: + args.add(tok.getText()); + break; + case ELLIPSIS: + // Unnamed Variadic macro + args.add("__VA_ARGS__"); + // We just named the ellipsis, but we unget the token + // to allow the ELLIPSIS handling below to process it. + source_untoken(tok); + break; + case NL: + case EOF: + error(tok, + "Unterminated macro parameter list"); + return tok; + default: + error(tok, + "error in macro parameters: " + + tok.getText()); + return source_skipline(false); + } + tok = source_token_nonwhite(); + switch (tok.getType()) { + case ',': + break; + case ELLIPSIS: + tok = source_token_nonwhite(); + if (tok.getType() != ')') + error(tok, + "ellipsis must be on last argument"); + m.setVariadic(true); + break ARGS; + case ')': + break ARGS; + + case NL: + case EOF: + /* Do not skip line. */ + error(tok, + "Unterminated macro parameters"); + return tok; + default: + error(tok, + "Bad token in macro parameters: " + + tok.getText()); + return source_skipline(false); + } + tok = source_token_nonwhite(); + } + } else { + assert tok.getType() == ')' : "Expected ')'"; + args = Collections.emptyList(); + } + + m.setArgs(args); + } else { + /* For searching. */ + args = Collections.emptyList(); + source_untoken(tok); + } + + /* Get an expansion for the macro, using indexOf. */ + boolean space = false; + boolean paste = false; + int idx; + + /* Ensure no space at start. */ + tok = source_token_nonwhite(); + EXPANSION: + for (;;) { + switch (tok.getType()) { + case EOF: + break EXPANSION; + case NL: + break EXPANSION; + + case CCOMMENT: + case CPPCOMMENT: + /* XXX This is where we implement GNU's cpp -CC. */ + // break; + case WHITESPACE: + if (!paste) + space = true; + break; + + /* Paste. */ + case PASTE: + space = false; + paste = true; + m.addPaste(new Token(M_PASTE, + tok.getLine(), tok.getColumn(), + "#" + "#", null)); + break; + + /* Stringify. */ + case '#': + if (space) + m.addToken(Token.space); + space = false; + final Token la = source_token_nonwhite(); + if (la.getType() == IDENTIFIER + && ((idx = args.indexOf(la.getText())) != -1)) { + m.addToken(new Token(M_STRING, + la.getLine(), la.getColumn(), + "#" + la.getText(), + Integer.valueOf(idx))); + } else { + m.addToken(tok); + /* Allow for special processing. */ + source_untoken(la); + } + break; + + case IDENTIFIER: + if (space) + m.addToken(Token.space); + space = false; + paste = false; + idx = args.indexOf(tok.getText()); + if (idx == -1) + m.addToken(tok); + else + m.addToken(new Token(M_ARG, + tok.getLine(), tok.getColumn(), + tok.getText(), + Integer.valueOf(idx))); + break; + + default: + if (space) + m.addToken(Token.space); + space = false; + paste = false; + m.addToken(tok); + break; + } + tok = source_token(); + } + + if (getFeature(Feature.DEBUG)) + LOG.debug("Defined macro " + m); + addMacro(m); + + return tok; /* NL or EOF. */ + + } + + @Nonnull + private Token undef() + throws IOException, + LexerException { + final Token tok = source_token_nonwhite(); + if (tok.getType() != IDENTIFIER) { + error(tok, + "Expected identifier, not " + tok.getText()); + if (tok.getType() == NL || tok.getType() == EOF) + return tok; + } else { + final Macro m = getMacro(tok.getText()); + if (m != null) { + /* XXX error if predefined */ + macros.remove(m.getName()); + } + } + return source_skipline(true); + } + + /** + * Attempts to include the given file. + * + * User code may override this method to implement a virtual + * file system. + */ + protected boolean include(@Nonnull final VirtualFile file) + throws IOException, + LexerException { + // System.out.println("Try to include " + ((File)file).getAbsolutePath()); + if (!file.isFile()) + return false; + if (getFeature(Feature.DEBUG)) + LOG.debug("pp: including " + file); + includes.add(file); + push_source(file.getSource(), true); + return true; + } + + /** + * Includes a file from an include path, by name. + */ + protected boolean include(@Nonnull final Iterable<String> path, @Nonnull final String name) + throws IOException, + LexerException { + for (final String dir : path) { + final VirtualFile file = getFileSystem().getFile(dir, name); + if (include(file)) + return true; + } + return false; + } + + /** + * Handles an include directive. + */ + private void include( + @CheckForNull final String parent, final int line, + @Nonnull final String name, final boolean quoted, final boolean next) + throws IOException, + LexerException { + if (name.startsWith("/")) { + final VirtualFile file = filesystem.getFile(name); + if (include(file)) + return; + final StringBuilder buf = new StringBuilder(); + buf.append("File not found: ").append(name); + error(line, 0, buf.toString()); + return; + } + + VirtualFile pdir = null; + if (quoted) { + if (parent != null) { + final VirtualFile pfile = filesystem.getFile(parent); + pdir = pfile.getParentFile(); + } + if (pdir != null) { + final VirtualFile ifile = pdir.getChildFile(name); + if (include(ifile)) + return; + } + if (include(quoteincludepath, name)) + return; + } else { + final int idx = name.indexOf('/'); + if (idx != -1) { + final String frameworkName = name.substring(0, idx); + final String headerName = name.substring(idx + 1); + final String headerPath = frameworkName + ".framework/Headers/" + headerName; + if (include(frameworkspath, headerPath)) + return; + } + } + + if (include(sysincludepath, name)) + return; + + final StringBuilder buf = new StringBuilder(); + buf.append("File not found: ").append(name); + buf.append(" in"); + if (quoted) { + buf.append(" .").append('(').append(pdir).append(')'); + for (final String dir : quoteincludepath) + buf.append(" ").append(dir); + } + for (final String dir : sysincludepath) + buf.append(" ").append(dir); + error(line, 0, buf.toString()); + } + + @Nonnull + private Token include(final boolean next) + throws IOException, + LexerException { + final LexerSource lexer = (LexerSource) source; + try { + lexer.setInclude(true); + Token tok = token_nonwhite(); + + String name; + boolean quoted; + + if (tok.getType() == STRING) { + /* XXX Use the original text, not the value. + * Backslashes must not be treated as escapes here. */ + final StringBuilder buf = new StringBuilder((String) tok.getValue()); + HEADER: + for (;;) { + tok = token_nonwhite(); + switch (tok.getType()) { + case STRING: + buf.append((String) tok.getValue()); + break; + case NL: + case EOF: + break HEADER; + default: + warning(tok, + "Unexpected token on #" + "include line"); + return source_skipline(false); + } + } + name = buf.toString(); + quoted = true; + } else if (tok.getType() == HEADER) { + name = (String) tok.getValue(); + quoted = false; + tok = source_skipline(true); + } else { + error(tok, + "Expected string or header, not " + tok.getText()); + switch (tok.getType()) { + case NL: + case EOF: + return tok; + default: + /* Only if not a NL or EOF already. */ + return source_skipline(false); + } + } + + /* Do the inclusion. */ + include(source.getPath(), tok.getLine(), name, quoted, next); + + /* 'tok' is the 'nl' after the include. We use it after the + * #line directive. */ + if (getFeature(Feature.LINEMARKERS)) + return line_token(1, source.getName(), " 1"); + return tok; + } finally { + lexer.setInclude(false); + } + } + + protected void pragma_once(@Nonnull final Token name) + throws IOException, LexerException { + final Source s = this.source; + if (!onceseenpaths.add(s.getPath())) { + final Token mark = pop_source(true); + // FixedTokenSource should never generate a linemarker on exit. + if (mark != null) + push_source(new FixedTokenSource(Arrays.asList(mark)), true); + } + } + + protected void pragma(@Nonnull final Token name, @Nonnull final List<Token> value) + throws IOException, + LexerException { + if (getFeature(Feature.PRAGMA_ONCE)) { + if ("once".equals(name.getText())) { + pragma_once(name); + return; + } + } + warning(name, "Unknown #" + "pragma: " + name.getText()); + } + + @Nonnull + private Token pragma() + throws IOException, + LexerException { + Token name; + + NAME: + for (;;) { + final Token tok = token(); + switch (tok.getType()) { + case EOF: + /* There ought to be a newline before EOF. + * At least, in any skipline context. */ + /* XXX Are we sure about this? */ + warning(tok, + "End of file in #" + "pragma"); + return tok; + case NL: + /* This may contain one or more newlines. */ + warning(tok, + "Empty #" + "pragma"); + return tok; + case CCOMMENT: + case CPPCOMMENT: + case WHITESPACE: + continue NAME; + case IDENTIFIER: + name = tok; + break NAME; + default: + return source_skipline(false); + } + } + + Token tok; + final List<Token> value = new ArrayList<Token>(); + VALUE: + for (;;) { + tok = token(); + switch (tok.getType()) { + case EOF: + /* There ought to be a newline before EOF. + * At least, in any skipline context. */ + /* XXX Are we sure about this? */ + warning(tok, + "End of file in #" + "pragma"); + break VALUE; + case NL: + /* This may contain one or more newlines. */ + break VALUE; + case CCOMMENT: + case CPPCOMMENT: + break; + case WHITESPACE: + value.add(tok); + break; + default: + value.add(tok); + break; + } + } + + pragma(name, value); + + return tok; /* The NL. */ + + } + + /* For #error and #warning. */ + private void error(@Nonnull final Token pptok, final boolean is_error) + throws IOException, + LexerException { + final StringBuilder buf = new StringBuilder(); + buf.append('#').append(pptok.getText()).append(' '); + /* Peculiar construction to ditch first whitespace. */ + Token tok = source_token_nonwhite(); + ERROR: + for (;;) { + switch (tok.getType()) { + case NL: + case EOF: + break ERROR; + default: + buf.append(tok.getText()); + break; + } + tok = source_token(); + } + if (is_error) + error(pptok, buf.toString()); + else + warning(pptok, buf.toString()); + } + + /* This bypasses token() for #elif expressions. + * If we don't do this, then isActive() == false + * causes token() to simply chew the entire input line. */ + @Nonnull + private Token expanded_token() + throws IOException, + LexerException { + for (;;) { + final Token tok = source_token(); + // System.out.println("Source token is " + tok); + if (tok.getType() == IDENTIFIER) { + final Macro m = getMacro(tok.getText()); + if (m == null) + return tok; + if (source.isExpanding(m)) + return tok; + if (macro(m, tok)) + continue; + } + return tok; + } + } + + @Nonnull + private Token expanded_token_nonwhite() + throws IOException, + LexerException { + Token tok; + do { + tok = expanded_token(); + // System.out.println("expanded token is " + tok); + } while (isWhite(tok)); + return tok; + } + + @CheckForNull + private Token expr_token = null; + + @Nonnull + private Token expr_token() + throws IOException, + LexerException { + Token tok = expr_token; + + if (tok != null) { + // System.out.println("ungetting"); + expr_token = null; + } else { + tok = expanded_token_nonwhite(); + // System.out.println("expt is " + tok); + + if (tok.getType() == IDENTIFIER + && tok.getText().equals("defined")) { + Token la = source_token_nonwhite(); + boolean paren = false; + if (la.getType() == '(') { + paren = true; + la = source_token_nonwhite(); + } + + // System.out.println("Core token is " + la); + if (la.getType() != IDENTIFIER) { + error(la, + "defined() needs identifier, not " + + la.getText()); + tok = new Token(NUMBER, + la.getLine(), la.getColumn(), + "0", new NumericValue(10, "0")); + } else if (macros.containsKey(la.getText())) { + // System.out.println("Found macro"); + tok = new Token(NUMBER, + la.getLine(), la.getColumn(), + "1", new NumericValue(10, "1")); + } else { + // System.out.println("Not found macro"); + tok = new Token(NUMBER, + la.getLine(), la.getColumn(), + "0", new NumericValue(10, "0")); + } + + if (paren) { + la = source_token_nonwhite(); + if (la.getType() != ')') { + expr_untoken(la); + error(la, "Missing ) in defined(). Got " + la.getText()); + } + } + } + } + + // System.out.println("expr_token returns " + tok); + return tok; + } + + private void expr_untoken(@Nonnull final Token tok) + throws LexerException { + if (expr_token != null) + throw new InternalException( + "Cannot unget two expression tokens." + ); + expr_token = tok; + } + + private int expr_priority(@Nonnull final Token op) { + switch (op.getType()) { + case '/': + return 11; + case '%': + return 11; + case '*': + return 11; + case '+': + return 10; + case '-': + return 10; + case LSH: + return 9; + case RSH: + return 9; + case '<': + return 8; + case '>': + return 8; + case LE: + return 8; + case GE: + return 8; + case EQ: + return 7; + case NE: + return 7; + case '&': + return 6; + case '^': + return 5; + case '|': + return 4; + case LAND: + return 3; + case LOR: + return 2; + case '?': + return 1; + default: + // System.out.println("Unrecognised operator " + op); + return 0; + } + } + + private long expr(final int priority) + throws IOException, + LexerException { + /* + * (new Exception("expr(" + priority + ") called")).printStackTrace(); + */ + + Token tok = expr_token(); + long lhs, rhs; + + // System.out.println("Expr lhs token is " + tok); + switch (tok.getType()) { + case '(': + lhs = expr(0); + tok = expr_token(); + if (tok.getType() != ')') { + expr_untoken(tok); + error(tok, "Missing ) in expression. Got " + tok.getText()); + return 0; + } + break; + + case '~': + lhs = ~expr(11); + break; + case '!': + lhs = expr(11) == 0 ? 1 : 0; + break; + case '-': + lhs = -expr(11); + break; + case NUMBER: + final NumericValue value = (NumericValue) tok.getValue(); + lhs = value.longValue(); + break; + case CHARACTER: + lhs = ((Character) tok.getValue()).charValue(); + break; + case IDENTIFIER: + if (warnings.contains(Warning.UNDEF)) + warning(tok, "Undefined token '" + tok.getText() + + "' encountered in conditional."); + lhs = 0; + break; + + default: + expr_untoken(tok); + error(tok, + "Bad token in expression: " + tok.getText()); + return 0; + } + + EXPR: + for (;;) { + // System.out.println("expr: lhs is " + lhs + ", pri = " + priority); + final Token op = expr_token(); + final int pri = expr_priority(op); /* 0 if not a binop. */ + + if (pri == 0 || priority >= pri) { + expr_untoken(op); + break EXPR; + } + rhs = expr(pri); + // System.out.println("rhs token is " + rhs); + switch (op.getType()) { + case '/': + if (rhs == 0) { + error(op, "Division by zero"); + lhs = 0; + } else { + lhs = lhs / rhs; + } + break; + case '%': + if (rhs == 0) { + error(op, "Modulus by zero"); + lhs = 0; + } else { + lhs = lhs % rhs; + } + break; + case '*': + lhs = lhs * rhs; + break; + case '+': + lhs = lhs + rhs; + break; + case '-': + lhs = lhs - rhs; + break; + case '<': + lhs = lhs < rhs ? 1 : 0; + break; + case '>': + lhs = lhs > rhs ? 1 : 0; + break; + case '&': + lhs = lhs & rhs; + break; + case '^': + lhs = lhs ^ rhs; + break; + case '|': + lhs = lhs | rhs; + break; + + case LSH: + lhs = lhs << rhs; + break; + case RSH: + lhs = lhs >> rhs; + break; + case LE: + lhs = lhs <= rhs ? 1 : 0; + break; + case GE: + lhs = lhs >= rhs ? 1 : 0; + break; + case EQ: + lhs = lhs == rhs ? 1 : 0; + break; + case NE: + lhs = lhs != rhs ? 1 : 0; + break; + case LAND: + lhs = (lhs != 0) && (rhs != 0) ? 1 : 0; + break; + case LOR: + lhs = (lhs != 0) || (rhs != 0) ? 1 : 0; + break; + + case '?': { + tok = expr_token(); + if (tok.getType() != ':') { + expr_untoken(tok); + error(tok, "Missing : in conditional expression. Got " + tok.getText()); + return 0; + } + final long falseResult = expr(0); + lhs = (lhs != 0) ? rhs : falseResult; + } + break; + + default: + error(op, + "Unexpected operator " + op.getText()); + return 0; + + } + } + + /* + * (new Exception("expr returning " + lhs)).printStackTrace(); + */ + // System.out.println("expr returning " + lhs); + return lhs; + } + + @Nonnull + private Token toWhitespace(@Nonnull final Token tok) { + final String text = tok.getText(); + final int len = text.length(); + boolean cr = false; + int nls = 0; + + for (int i = 0; i < len; i++) { + final char c = text.charAt(i); + + switch (c) { + case '\r': + cr = true; + nls++; + break; + case '\n': + if (cr) { + cr = false; + break; + } + /* fallthrough */ + case '\u2028': + case '\u2029': + case '\u000B': + case '\u000C': + case '\u0085': + cr = false; + nls++; + break; + } + } + + final char[] cbuf = new char[nls]; + Arrays.fill(cbuf, '\n'); + return new Token(WHITESPACE, + tok.getLine(), tok.getColumn(), + new String(cbuf)); + } + + @Nonnull + private Token _token() + throws IOException, + LexerException { + + for (;;) { + Token tok; + if (!isActive()) { + final Source s = getSource(); + if (s == null) { + final Token t = next_source(); + if (t.getType() == P_LINE && !getFeature(Feature.LINEMARKERS)) + continue; + return t; + } + + try { + /* XXX Tell lexer to ignore warnings. */ + s.setActive(false); + tok = source_token(); + } finally { + /* XXX Tell lexer to stop ignoring warnings. */ + s.setActive(true); + } + switch (tok.getType()) { + case HASH: + case NL: + case EOF: + /* The preprocessor has to take action here. */ + break; + case WHITESPACE: + return tok; + case CCOMMENT: + case CPPCOMMENT: + // Patch up to preserve whitespace. + if (getFeature(Feature.KEEPALLCOMMENTS)) + return tok; + if (!isActive()) + return toWhitespace(tok); + if (getFeature(Feature.KEEPCOMMENTS)) + return tok; + return toWhitespace(tok); + default: + // Return NL to preserve whitespace. + /* XXX This might lose a comment. */ + return source_skipline(false); + } + } else { + tok = source_token(); + } + + LEX: + switch (tok.getType()) { + case EOF: + /* Pop the stacks. */ + return tok; + + case WHITESPACE: + case NL: + return tok; + + case CCOMMENT: + case CPPCOMMENT: + return tok; + + case '!': + case '%': + case '&': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '[': + case ']': + case '^': + case '{': + case '|': + case '}': + case '~': + case '.': + + /* From Olivier Chafik for Objective C? */ + case '@': + /* The one remaining ASCII, might as well. */ + case '`': + + // case '#': + case AND_EQ: + case ARROW: + case CHARACTER: + case DEC: + case DIV_EQ: + case ELLIPSIS: + case EQ: + case GE: + case HEADER: /* Should only arise from include() */ + + case INC: + case LAND: + case LE: + case LOR: + case LSH: + case LSH_EQ: + case SUB_EQ: + case MOD_EQ: + case MULT_EQ: + case NE: + case OR_EQ: + case PLUS_EQ: + case RANGE: + case RSH: + case RSH_EQ: + case STRING: + case SQSTRING: + case XOR_EQ: + return tok; + + case NUMBER: + return tok; + + case IDENTIFIER: + final Macro m = getMacro(tok.getText()); + if (m == null) + return tok; + if (source.isExpanding(m)) + return tok; + if (macro(m, tok)) + break; + return tok; + + case P_LINE: + if (getFeature(Feature.LINEMARKERS)) + return tok; + break; + + case INVALID: + if (getFeature(Feature.CSYNTAX)) + error(tok, String.valueOf(tok.getValue())); + return tok; + + default: + throw new InternalException("Bad token " + tok); + // break; + + case HASH: + tok = source_token_nonwhite(); + // (new Exception("here")).printStackTrace(); + switch (tok.getType()) { + case NL: + break LEX; /* Some code has #\n */ + + case IDENTIFIER: + break; + default: + error(tok, + "Preprocessor directive not a word " + + tok.getText()); + return source_skipline(false); + } + final PreprocessorCommand ppcmd = PreprocessorCommand.forText(tok.getText()); + if (ppcmd == null) { + error(tok, + "Unknown preprocessor directive " + + tok.getText()); + return source_skipline(false); + } + + PP: + switch (ppcmd) { + + case PP_DEFINE: + if (!isActive()) + return source_skipline(false); + else + return define(); + // break; + + case PP_UNDEF: + if (!isActive()) + return source_skipline(false); + else + return undef(); + // break; + + case PP_INCLUDE: + if (!isActive()) + return source_skipline(false); + else + return include(false); + // break; + case PP_INCLUDE_NEXT: + if (!isActive()) + return source_skipline(false); + if (!getFeature(Feature.INCLUDENEXT)) { + error(tok, + "Directive include_next not enabled" + ); + return source_skipline(false); + } + return include(true); + // break; + + case PP_WARNING: + case PP_ERROR: + if (!isActive()) + return source_skipline(false); + else + error(tok, ppcmd == PP_ERROR); + break; + + case PP_IF: + push_state(); + if (!isActive()) { + return source_skipline(false); + } + expr_token = null; + states.peek().setActive(expr(0) != 0); + tok = expr_token(); /* unget */ + + if (tok.getType() == NL) + return tok; + return source_skipline(true); + // break; + + case PP_ELIF: + State state = states.peek(); + if (false) { + /* Check for 'if' */; + } else if (state.sawElse()) { + error(tok, + "#elif after #" + "else"); + return source_skipline(false); + } else if (!state.isParentActive()) { + /* Nested in skipped 'if' */ + return source_skipline(false); + } else if (state.isActive()) { + /* The 'if' part got executed. */ + state.setParentActive(false); + /* This is like # else # if but with + * only one # end. */ + state.setActive(false); + return source_skipline(false); + } else { + expr_token = null; + state.setActive(expr(0) != 0); + tok = expr_token(); /* unget */ + + if (tok.getType() == NL) + return tok; + return source_skipline(true); + } + // break; + + case PP_ELSE: + state = states.peek(); + if (false) + /* Check for 'if' */ ; else if (state.sawElse()) { + error(tok, + "#" + "else after #" + "else"); + return source_skipline(false); + } else { + state.setSawElse(); + state.setActive(!state.isActive()); + return source_skipline(warnings.contains(Warning.ENDIF_LABELS)); + } + // break; + + case PP_IFDEF: + push_state(); + if (!isActive()) { + return source_skipline(false); + } else { + tok = source_token_nonwhite(); + // System.out.println("ifdef " + tok); + if (tok.getType() != IDENTIFIER) { + error(tok, + "Expected identifier, not " + + tok.getText()); + return source_skipline(false); + } else { + final String text = tok.getText(); + final boolean exists + = macros.containsKey(text); + states.peek().setActive(exists); + return source_skipline(true); + } + } + // break; + + case PP_IFNDEF: + push_state(); + if (!isActive()) { + return source_skipline(false); + } else { + tok = source_token_nonwhite(); + if (tok.getType() != IDENTIFIER) { + error(tok, + "Expected identifier, not " + + tok.getText()); + return source_skipline(false); + } else { + final String text = tok.getText(); + final boolean exists + = macros.containsKey(text); + states.peek().setActive(!exists); + return source_skipline(true); + } + } + // break; + + case PP_ENDIF: + pop_state(); + return source_skipline(warnings.contains(Warning.ENDIF_LABELS)); + // break; + + case PP_LINE: + return source_skipline(false); + // break; + + case PP_PRAGMA: + if (!isActive()) + return source_skipline(false); + return pragma(); + // break; + + default: + /* Actual unknown directives are + * processed above. If we get here, + * we succeeded the map lookup but + * failed to handle it. Therefore, + * this is (unconditionally?) fatal. */ + // if (isActive()) /* XXX Could be warning. */ + throw new InternalException( + "Internal error: Unknown directive " + + tok); + // return source_skipline(false); + } + + } + } + } + + @Nonnull + private Token token_nonwhite() + throws IOException, + LexerException { + Token tok; + do { + tok = _token(); + } while (isWhite(tok)); + return tok; + } + + /** + * Returns the next preprocessor token. + * + * @see Token + * @throws LexerException if a preprocessing error occurs. + * @throws InternalException if an unexpected error condition arises. + */ + @Nonnull + public Token token() + throws IOException, + LexerException { + final Token tok = _token(); + if (getFeature(Feature.DEBUG)) + LOG.debug("pp: Returning " + tok); + return tok; + } + + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + + Source s = getSource(); + while (s != null) { + buf.append(" -> ").append(String.valueOf(s)).append("\n"); + s = s.getParent(); + } + + final Map<String, Macro> macros = new TreeMap<String, Macro>(getMacros()); + for (final Macro macro : macros.values()) { + buf.append("#").append("macro ").append(macro).append("\n"); + } + + return buf.toString(); + } + + @Override + public void close() + throws IOException { + { + Source s = source; + while (s != null) { + s.close(); + s = s.getParent(); + } + } + for (final Source s : inputs) { + s.close(); + } + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/PreprocessorCommand.java b/src/main/java/com/jogamp/gluegen/jcpp/PreprocessorCommand.java new file mode 100644 index 0000000..a01a04b --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/PreprocessorCommand.java @@ -0,0 +1,44 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jogamp.gluegen.jcpp; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * + * @author shevek + */ +public enum PreprocessorCommand { + + PP_DEFINE("define"), + PP_ELIF("elif"), + PP_ELSE("else"), + PP_ENDIF("endif"), + PP_ERROR("error"), + PP_IF("if"), + PP_IFDEF("ifdef"), + PP_IFNDEF("ifndef"), + PP_INCLUDE("include"), + PP_LINE("line"), + PP_PRAGMA("pragma"), + PP_UNDEF("undef"), + PP_WARNING("warning"), + PP_INCLUDE_NEXT("include_next"), + PP_IMPORT("import"); + private final String text; + /* pp */ PreprocessorCommand(String text) { + this.text = text; + } + + @CheckForNull + public static PreprocessorCommand forText(@Nonnull String text) { + for (PreprocessorCommand ppcmd : PreprocessorCommand.values()) + if (ppcmd.text.equals(text)) + return ppcmd; + return null; + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/PreprocessorListener.java b/src/main/java/com/jogamp/gluegen/jcpp/PreprocessorListener.java new file mode 100644 index 0000000..1152a58 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/PreprocessorListener.java @@ -0,0 +1,59 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import javax.annotation.Nonnull; + +/** + * A handler for preprocessor events, primarily errors and warnings. + * + * If no PreprocessorListener is installed in a Preprocessor, all + * error and warning events will throw an exception. Installing a + * listener allows more intelligent handling of these events. + */ +public interface PreprocessorListener { + + /** + * Handles a warning. + * + * The behaviour of this method is defined by the + * implementation. It may simply record the error message, or + * it may throw an exception. + */ + public void handleWarning(@Nonnull Source source, int line, int column, + @Nonnull String msg) + throws LexerException; + + /** + * Handles an error. + * + * The behaviour of this method is defined by the + * implementation. It may simply record the error message, or + * it may throw an exception. + */ + public void handleError(@Nonnull Source source, int line, int column, + @Nonnull String msg) + throws LexerException; + + public enum SourceChangeEvent { + + SUSPEND, PUSH, POP, RESUME; + } + + public void handleSourceChange(@Nonnull Source source, @Nonnull SourceChangeEvent event); + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/ResourceFileSystem.java b/src/main/java/com/jogamp/gluegen/jcpp/ResourceFileSystem.java new file mode 100644 index 0000000..438d9c1 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/ResourceFileSystem.java @@ -0,0 +1,81 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import javax.annotation.Nonnull; + +/** + * + * @author shevek + */ +public class ResourceFileSystem implements VirtualFileSystem { + + private final ClassLoader loader; + + public ResourceFileSystem(@Nonnull ClassLoader loader) { + this.loader = loader; + } + + @Override + public VirtualFile getFile(String path) { + return new ResourceFile(loader, path); + } + + @Override + public VirtualFile getFile(String dir, String name) { + return getFile(dir + "/" + name); + } + + private class ResourceFile implements VirtualFile { + + private final ClassLoader loader; + private final String path; + + public ResourceFile(ClassLoader loader, String path) { + this.loader = loader; + this.path = path; + } + + @Override + public boolean isFile() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String getPath() { + return path; + } + + @Override + public String getName() { + return path.substring(path.lastIndexOf('/') + 1); + } + + @Override + public ResourceFile getParentFile() { + int idx = path.lastIndexOf('/'); + if (idx < 1) + return null; + return new ResourceFile(loader, path.substring(0, idx)); + } + + @Override + public ResourceFile getChildFile(String name) { + return new ResourceFile(loader, path + "/" + name); + } + + @Override + public Source getSource() throws IOException { + InputStream stream = loader.getResourceAsStream(path); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + return new LexerSource(reader, true); + } + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/Source.java b/src/main/java/com/jogamp/gluegen/jcpp/Source.java new file mode 100644 index 0000000..d56b5d7 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/Source.java @@ -0,0 +1,298 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Iterator; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; + +import static com.jogamp.gluegen.jcpp.Token.CCOMMENT; +import static com.jogamp.gluegen.jcpp.Token.CPPCOMMENT; +import static com.jogamp.gluegen.jcpp.Token.EOF; +import static com.jogamp.gluegen.jcpp.Token.NL; +import static com.jogamp.gluegen.jcpp.Token.WHITESPACE; + +/** + * An input to the Preprocessor. + * + * Inputs may come from Files, Strings or other sources. The + * preprocessor maintains a stack of Sources. Operations such as + * file inclusion or token pasting will push a new source onto + * the Preprocessor stack. Sources pop from the stack when they + * are exhausted; this may be transparent or explicit. + * + * BUG: Error messages are not handled properly. + */ +public abstract class Source implements Iterable<Token>, Closeable { + + private Source parent; + private boolean autopop; + private PreprocessorListener listener; + private boolean active; + private boolean werror; + + /* LineNumberReader */ + + /* + // We can't do this, since we would lose the LexerException + private class Itr implements Iterator { + private Token next = null; + private void advance() { + try { + if (next != null) + next = token(); + } + catch (IOException e) { + throw new UnsupportedOperationException( + "Failed to advance token iterator: " + + e.getMessage() + ); + } + } + public boolean hasNext() { + return next.getType() != EOF; + } + public Token next() { + advance(); + Token t = next; + next = null; + return t; + } + public void remove() { + throw new UnsupportedOperationException( + "Cannot remove tokens from a Source." + ); + } + } + */ + public Source() { + this.parent = null; + this.autopop = false; + this.listener = null; + this.active = true; + this.werror = false; + } + + /** + * Sets the parent source of this source. + * + * Sources form a singly linked list. + */ + /* pp */ void setParent(final Source parent, final boolean autopop) { + this.parent = parent; + this.autopop = autopop; + } + + /** + * Returns the parent source of this source. + * + * Sources form a singly linked list. + */ + public final Source getParent() { + return parent; + } + + + // @OverrideMustInvoke + /* pp */ void init(final Preprocessor pp) { + setListener(pp.getListener()); + this.werror = pp.getWarnings().contains(Warning.ERROR); + } + + /** + * Sets the listener for this Source. + * + * Normally this is set by the Preprocessor when a Source is + * used, but if you are using a Source as a standalone object, + * you may wish to call this. + */ + public void setListener(final PreprocessorListener pl) { + this.listener = pl; + } + + /** + * Returns the File currently being lexed. + * + * If this Source is not a {@link FileLexerSource}, then + * it will ask the parent Source, and so forth recursively. + * If no Source on the stack is a FileLexerSource, returns null. + */ + @CheckForNull + public String getPath() { + final Source parent = getParent(); + if (parent != null) + return parent.getPath(); + return null; + } + + /** + * Returns the human-readable name of the current Source. + */ + @CheckForNull + public String getName() { + final Source parent = getParent(); + if (parent != null) + return parent.getName(); + return null; + } + + /** + * Returns the current line number within this Source. + */ + @Nonnegative + public int getLine() { + final Source parent = getParent(); + if (parent == null) + return 0; + return parent.getLine(); + } + + /** + * Returns the current column number within this Source. + */ + public int getColumn() { + final Source parent = getParent(); + if (parent == null) + return 0; + return parent.getColumn(); + } + + /** + * Returns true if this Source is expanding the given macro. + * + * This is used to prevent macro recursion. + */ + /* pp */ boolean isExpanding(@Nonnull final Macro m) { + final Source parent = getParent(); + if (parent != null) + return parent.isExpanding(m); + return false; + } + + /** + * Returns true if this Source should be transparently popped + * from the input stack. + * + * Examples of such sources are macro expansions. + */ + /* pp */ boolean isAutopop() { + return autopop; + } + + /** + * Returns true if this source has line numbers. + */ + /* pp */ boolean isNumbered() { + return false; + } + + /* This is an incredibly lazy way of disabling warnings when + * the source is not active. */ + /* pp */ void setActive(final boolean b) { + this.active = b; + } + + /* pp */ boolean isActive() { + return active; + } + + /** + * Returns the next Token parsed from this input stream. + * + * @see Token + */ + @Nonnull + public abstract Token token() + throws IOException, + LexerException; + + /** + * Returns a token iterator for this Source. + */ + @Override + public Iterator<Token> iterator() { + return new SourceIterator(this); + } + + /** + * Skips tokens until the end of line. + * + * @param white true if only whitespace is permitted on the + * remainder of the line. + * @return the NL token. + */ + @Nonnull + public Token skipline(final boolean white) + throws IOException, + LexerException { + for (;;) { + final Token tok = token(); + switch (tok.getType()) { + case EOF: + /* There ought to be a newline before EOF. + * At least, in any skipline context. */ + /* XXX Are we sure about this? */ + warning(tok.getLine(), tok.getColumn(), + "No newline before end of file"); + return new Token(NL, + tok.getLine(), tok.getColumn(), + "\n"); + // return tok; + case NL: + /* This may contain one or more newlines. */ + return tok; + case CCOMMENT: + case CPPCOMMENT: + case WHITESPACE: + break; + default: + /* XXX Check white, if required. */ + if (white) + warning(tok.getLine(), tok.getColumn(), + "Unexpected nonwhite token"); + break; + } + } + } + + protected void error(final int line, final int column, final String msg) + throws LexerException { + if (listener != null) + listener.handleError(this, line, column, msg); + else + throw new LexerException("Error at " + line + ":" + column + ": " + msg); + } + + protected void warning(final int line, final int column, final String msg) + throws LexerException { + if (werror) + error(line, column, msg); + else if (listener != null) + listener.handleWarning(this, line, column, msg); + else + throw new LexerException("Warning at " + line + ":" + column + ": " + msg); + } + + public void close() + throws IOException { + } + +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/SourceIterator.java b/src/main/java/com/jogamp/gluegen/jcpp/SourceIterator.java new file mode 100644 index 0000000..4990512 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/SourceIterator.java @@ -0,0 +1,88 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static com.jogamp.gluegen.jcpp.Token.EOF; + +/** + * An Iterator for {@link Source Sources}, + * returning {@link Token Tokens}. + */ +public class SourceIterator implements Iterator<Token> { + + private final Source source; + private Token tok; + + public SourceIterator(Source s) { + this.source = s; + this.tok = null; + } + + /** + * Rethrows IOException inside IllegalStateException. + */ + private void advance() { + try { + if (tok == null) + tok = source.token(); + } catch (LexerException e) { + throw new IllegalStateException(e); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Returns true if the enclosed Source has more tokens. + * + * The EOF token is never returned by the iterator. + * @throws IllegalStateException if the Source + * throws a LexerException or IOException + */ + public boolean hasNext() { + advance(); + return tok.getType() != EOF; + } + + /** + * Returns the next token from the enclosed Source. + * + * The EOF token is never returned by the iterator. + * @throws IllegalStateException if the Source + * throws a LexerException or IOException + */ + public Token next() { + if (!hasNext()) + throw new NoSuchElementException(); + Token t = this.tok; + this.tok = null; + return t; + } + + /** + * Not supported. + * + * @throws UnsupportedOperationException. + */ + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/State.java b/src/main/java/com/jogamp/gluegen/jcpp/State.java new file mode 100644 index 0000000..4d7f886 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/State.java @@ -0,0 +1,68 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +/* pp */ class State { + + boolean parent; + boolean active; + boolean sawElse; + + /* pp */ State() { + this.parent = true; + this.active = true; + this.sawElse = false; + } + + /* pp */ State(State parent) { + this.parent = parent.isParentActive() && parent.isActive(); + this.active = true; + this.sawElse = false; + } + + /* Required for #elif */ + /* pp */ void setParentActive(boolean b) { + this.parent = b; + } + + /* pp */ boolean isParentActive() { + return parent; + } + + /* pp */ void setActive(boolean b) { + this.active = b; + } + + /* pp */ boolean isActive() { + return active; + } + + /* pp */ void setSawElse() { + sawElse = true; + } + + /* pp */ boolean sawElse() { + return sawElse; + } + + @Override + public String toString() { + return "parent=" + parent + + ", active=" + active + + ", sawelse=" + sawElse; + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/StringLexerSource.java b/src/main/java/com/jogamp/gluegen/jcpp/StringLexerSource.java new file mode 100644 index 0000000..568bdca --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/StringLexerSource.java @@ -0,0 +1,53 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.StringReader; + +/** + * A Source for lexing a String. + * + * This class is used by token pasting, but can be used by user + * code. + */ +public class StringLexerSource extends LexerSource { + + /** + * Creates a new Source for lexing the given String. + * + * @param ppvalid true if preprocessor directives are to be + * honoured within the string. + */ + public StringLexerSource(String string, boolean ppvalid) { + super(new StringReader(string), ppvalid); + } + + /** + * Creates a new Source for lexing the given String. + * + * By default, preprocessor directives are not honoured within + * the string. + */ + public StringLexerSource(String string) { + this(string, false); + } + + @Override + public String toString() { + return "string literal"; + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/Token.java b/src/main/java/com/jogamp/gluegen/jcpp/Token.java new file mode 100644 index 0000000..9c1794b --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/Token.java @@ -0,0 +1,193 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +/** + * A Preprocessor token. + * + * @see Preprocessor + */ +public final class Token { + + // public static final int EOF = -1; + private final int type; + private int line; + private int column; + private final Object value; + private final String text; + + public Token(int type, int line, int column, + String text, Object value) { + this.type = type; + this.line = line; + this.column = column; + this.text = text; + this.value = value; + } + + public Token(int type, int line, int column, String text) { + this(type, line, column, text, null); + } + + /* pp */ Token(int type, String text, Object value) { + this(type, -1, -1, text, value); + } + + /* pp */ Token(int type, String text) { + this(type, text, null); + } + + /* pp */ Token(int type) { + this(type, TokenType.getTokenText(type)); + } + + /** + * Returns the semantic type of this token. + */ + public int getType() { + return type; + } + + /* pp */ void setLocation(int line, int column) { + this.line = line; + this.column = column; + } + + /** + * Returns the line at which this token started. + * + * Lines are numbered from zero. + */ + public int getLine() { + return line; + } + + /** + * Returns the column at which this token started. + * + * Columns are numbered from zero. + */ + public int getColumn() { + return column; + } + + /** + * Returns the original or generated text of this token. + * + * This is distinct from the semantic value of the token. + * + * @see #getValue() + */ + public String getText() { + return text; + } + + /** + * Returns the semantic value of this token. + * + * For strings, this is the parsed String. + * For integers, this is an Integer object. + * For other token types, as appropriate. + * + * @see #getText() + */ + public Object getValue() { + return value; + } + + /** + * Returns a description of this token, for debugging purposes. + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + + buf.append('[').append(getTokenName(type)); + if (line != -1) { + buf.append('@').append(line); + if (column != -1) + buf.append(',').append(column); + } + buf.append("]:"); + if (text != null) + buf.append('"').append(text).append('"'); + else if (type > 3 && type < 256) + buf.append((char) type); + else + buf.append('<').append(type).append('>'); + if (value != null) + buf.append('=').append(value); + return buf.toString(); + } + + /** + * Returns the descriptive name of the given token type. + * + * This is mostly used for stringification and debugging. + */ + public static String getTokenName(int type) { + return TokenType.getTokenName(type); + } + + public static final int AND_EQ = 257; + public static final int ARROW = 258; + public static final int CHARACTER = 259; + public static final int CCOMMENT = 260; + public static final int CPPCOMMENT = 261; + public static final int DEC = 262; + public static final int DIV_EQ = 263; + public static final int ELLIPSIS = 264; + public static final int EOF = 265; + public static final int EQ = 266; + public static final int GE = 267; + public static final int HASH = 268; + public static final int HEADER = 269; + public static final int IDENTIFIER = 270; + public static final int INC = 271; + public static final int NUMBER = 272; + public static final int LAND = 273; + public static final int LAND_EQ = 274; + public static final int LE = 275; + public static final int LITERAL = 276; + public static final int LOR = 277; + public static final int LOR_EQ = 278; + public static final int LSH = 279; + public static final int LSH_EQ = 280; + public static final int MOD_EQ = 281; + public static final int MULT_EQ = 282; + public static final int NE = 283; + public static final int NL = 284; + public static final int OR_EQ = 285; + public static final int PASTE = 286; + public static final int PLUS_EQ = 287; + public static final int RANGE = 288; + public static final int RSH = 289; + public static final int RSH_EQ = 290; + public static final int SQSTRING = 291; + public static final int STRING = 292; + public static final int SUB_EQ = 293; + public static final int WHITESPACE = 294; + public static final int XOR_EQ = 295; + public static final int M_ARG = 296; + public static final int M_PASTE = 297; + public static final int M_STRING = 298; + public static final int P_LINE = 299; + public static final int INVALID = 300; + + /** The position-less space token. */ + /* pp */ static final Token space = new Token(WHITESPACE, -1, -1, " "); +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/TokenSnifferSource.java b/src/main/java/com/jogamp/gluegen/jcpp/TokenSnifferSource.java new file mode 100644 index 0000000..cc60698 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/TokenSnifferSource.java @@ -0,0 +1,46 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.IOException; +import java.util.List; + +import static com.jogamp.gluegen.jcpp.Token.EOF; + +@Deprecated +/* pp */ class TokenSnifferSource extends Source { + + private final List<Token> target; + + /* pp */ TokenSnifferSource(List<Token> target) { + this.target = target; + } + + public Token token() + throws IOException, + LexerException { + Token tok = getParent().token(); + if (tok.getType() != EOF) + target.add(tok); + return tok; + } + + @Override + public String toString() { + return getParent().toString(); + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/TokenType.java b/src/main/java/com/jogamp/gluegen/jcpp/TokenType.java new file mode 100644 index 0000000..e481bab --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/TokenType.java @@ -0,0 +1,130 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jogamp.gluegen.jcpp; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; + +import static com.jogamp.gluegen.jcpp.Token.*; + +/** + * + * @author shevek + */ +/* pp */ class TokenType { + + private static final List<TokenType> TYPES = new ArrayList<TokenType>(); + + private static void addTokenType(@Nonnegative int type, @Nonnull String name, @CheckForNull String text) { + while (TYPES.size() <= type) + TYPES.add(null); + TYPES.set(type, new TokenType(name, text)); + } + + private static void addTokenType(@Nonnegative int type, @Nonnull String name) { + addTokenType(type, name, null); + } + + @CheckForNull + public static TokenType getTokenType(@Nonnegative int type) { + try { + return TYPES.get(type); + } catch (IndexOutOfBoundsException e) { + return null; + } + } + + @Nonnull + public static String getTokenName(@Nonnegative int type) { + if (type < 0) + return "Invalid" + type; + TokenType tokenType = getTokenType(type); + if (tokenType == null) + return "Unknown" + type; + return tokenType.getName(); + } + + @CheckForNull + public static String getTokenText(@Nonnegative int type) { + TokenType tokenType = getTokenType(type); + if (tokenType == null) + return null; + return tokenType.getText(); + } + + static { + for (int i = 0; i < 255; i++) { + String text = String.valueOf((char) i); + addTokenType(i, text, text); + } + addTokenType(AND_EQ, "AND_EQ", "&="); + addTokenType(ARROW, "ARROW", "->"); + addTokenType(CHARACTER, "CHARACTER"); + addTokenType(CCOMMENT, "CCOMMENT"); + addTokenType(CPPCOMMENT, "CPPCOMMENT"); + addTokenType(DEC, "DEC", "--"); + addTokenType(DIV_EQ, "DIV_EQ", "/="); + addTokenType(ELLIPSIS, "ELLIPSIS", "..."); + addTokenType(EOF, "EOF"); + addTokenType(EQ, "EQ", "=="); + addTokenType(GE, "GE", ">="); + addTokenType(HASH, "HASH", "#"); + addTokenType(HEADER, "HEADER"); + addTokenType(IDENTIFIER, "IDENTIFIER"); + addTokenType(INC, "INC", "++"); + addTokenType(NUMBER, "NUMBER"); + addTokenType(LAND, "LAND", "&&"); + addTokenType(LAND_EQ, "LAND_EQ", "&&="); + addTokenType(LE, "LE", "<="); + addTokenType(LITERAL, "LITERAL"); + addTokenType(LOR, "LOR", "||"); + addTokenType(LOR_EQ, "LOR_EQ", "||="); + addTokenType(LSH, "LSH", "<<"); + addTokenType(LSH_EQ, "LSH_EQ", "<<="); + addTokenType(MOD_EQ, "MOD_EQ", "%="); + addTokenType(MULT_EQ, "MULT_EQ", "*="); + addTokenType(NE, "NE", "!="); + addTokenType(NL, "NL"); + addTokenType(OR_EQ, "OR_EQ", "|="); + addTokenType(PASTE, "PASTE", "##"); + addTokenType(PLUS_EQ, "PLUS_EQ", "+="); + addTokenType(RANGE, "RANGE", ".."); + addTokenType(RSH, "RSH", ">>"); + addTokenType(RSH_EQ, "RSH_EQ", ">>="); + addTokenType(SQSTRING, "SQSTRING"); + addTokenType(STRING, "STRING"); + addTokenType(SUB_EQ, "SUB_EQ", "-="); + addTokenType(WHITESPACE, "WHITESPACE"); + addTokenType(XOR_EQ, "XOR_EQ", "^="); + addTokenType(M_ARG, "M_ARG"); + addTokenType(M_PASTE, "M_PASTE"); + addTokenType(M_STRING, "M_STRING"); + addTokenType(P_LINE, "P_LINE"); + addTokenType(INVALID, "INVALID"); + } + + private final String name; + private final String text; + + /* pp */ TokenType(@Nonnull String name, @CheckForNull String text) { + this.name = name; + this.text = text; + } + + @Nonnull + public String getName() { + return name; + } + + @CheckForNull + public String getText() { + return text; + } +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/VirtualFile.java b/src/main/java/com/jogamp/gluegen/jcpp/VirtualFile.java new file mode 100644 index 0000000..26bbbcb --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/VirtualFile.java @@ -0,0 +1,45 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import java.io.IOException; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +/** + * An extremely lightweight virtual file interface. + */ +public interface VirtualFile { + + // public String getParent(); + public boolean isFile(); + + @Nonnull + public String getPath(); + + @Nonnull + public String getName(); + + @CheckForNull + public VirtualFile getParentFile(); + + @Nonnull + public VirtualFile getChildFile(String name); + + @Nonnull + public Source getSource() throws IOException; +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/VirtualFileSystem.java b/src/main/java/com/jogamp/gluegen/jcpp/VirtualFileSystem.java new file mode 100644 index 0000000..3f72f0c --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/VirtualFileSystem.java @@ -0,0 +1,31 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +import javax.annotation.Nonnull; + +/** + * An extremely lightweight virtual file system interface. + */ +public interface VirtualFileSystem { + + @Nonnull + public VirtualFile getFile(@Nonnull String path); + + @Nonnull + public VirtualFile getFile(@Nonnull String dir, @Nonnull String name); +} diff --git a/src/main/java/com/jogamp/gluegen/jcpp/Warning.java b/src/main/java/com/jogamp/gluegen/jcpp/Warning.java new file mode 100644 index 0000000..89388f4 --- /dev/null +++ b/src/main/java/com/jogamp/gluegen/jcpp/Warning.java @@ -0,0 +1,32 @@ +/* + * Anarres C Preprocessor + * Copyright (c) 2007-2008, Shevek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.jogamp.gluegen.jcpp; + +/** + * Warning classes which may optionally be emitted by the Preprocessor. + */ +public enum Warning { + + TRIGRAPHS, + // TRADITIONAL, + IMPORT, + UNDEF, + UNUSED_MACROS, + ENDIF_LABELS, + ERROR, + // SYSTEM_HEADERS +} |