aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorShevek <[email protected]>2013-12-27 05:49:13 -0800
committerShevek <[email protected]>2013-12-27 05:49:13 -0800
commitbdc6c852f418c3e042aa41469d84544e5f60a526 (patch)
tree7866346f0fa48ad46a6a427d016dd4b83451dbbe /src
parent39264fd6d2a6646e49f83b5b2b3512c1663a1c9b (diff)
Version bump to 1.4.0-SNAPSHOT.
Rewrite build system to use gradle. Clean up source for the new generation.
Diffstat (limited to 'src')
-rw-r--r--src/docs/javadoc/.gitignore0
-rw-r--r--src/java/org/anarres/cpp/ChrootFileSystem.java79
-rw-r--r--src/java/org/anarres/cpp/CppReader.java153
-rw-r--r--src/java/org/anarres/cpp/CppTask.java226
-rw-r--r--src/java/org/anarres/cpp/FixedTokenSource.java65
-rw-r--r--src/java/org/anarres/cpp/JavaFileSystem.java80
-rw-r--r--src/java/org/anarres/cpp/JoinReader.java209
-rw-r--r--src/java/org/anarres/cpp/LexerSource.java893
-rw-r--r--src/java/org/anarres/cpp/MacroTokenSource.java208
-rw-r--r--src/java/org/anarres/cpp/Main.java410
-rw-r--r--src/java/org/anarres/cpp/NumericValue.java179
-rw-r--r--src/java/org/anarres/cpp/Preprocessor.java1983
-rw-r--r--src/java/org/anarres/cpp/PreprocessorListener.java88
-rw-r--r--src/java/org/anarres/cpp/Source.java293
-rw-r--r--src/java/org/anarres/cpp/Token.java213
-rw-r--r--src/main/java/org/anarres/cpp/Argument.java (renamed from src/java/org/anarres/cpp/Argument.java)0
-rw-r--r--src/main/java/org/anarres/cpp/ChrootFileSystem.java84
-rw-r--r--src/main/java/org/anarres/cpp/CppReader.java153
-rw-r--r--src/main/java/org/anarres/cpp/CppTask.java215
-rw-r--r--src/main/java/org/anarres/cpp/Feature.java (renamed from src/java/org/anarres/cpp/Feature.java)0
-rw-r--r--src/main/java/org/anarres/cpp/FileLexerSource.java (renamed from src/java/org/anarres/cpp/FileLexerSource.java)0
-rw-r--r--src/main/java/org/anarres/cpp/FixedTokenSource.java60
-rw-r--r--src/main/java/org/anarres/cpp/InputLexerSource.java (renamed from src/java/org/anarres/cpp/InputLexerSource.java)62
-rw-r--r--src/main/java/org/anarres/cpp/InternalException.java (renamed from src/java/org/anarres/cpp/InternalException.java)0
-rw-r--r--src/main/java/org/anarres/cpp/JavaFileSystem.java84
-rw-r--r--src/main/java/org/anarres/cpp/JoinReader.java218
-rw-r--r--src/main/java/org/anarres/cpp/LexerException.java (renamed from src/java/org/anarres/cpp/LexerException.java)0
-rw-r--r--src/main/java/org/anarres/cpp/LexerSource.java910
-rw-r--r--src/main/java/org/anarres/cpp/Macro.java (renamed from src/java/org/anarres/cpp/Macro.java)1
-rw-r--r--src/main/java/org/anarres/cpp/MacroTokenSource.java203
-rw-r--r--src/main/java/org/anarres/cpp/Main.java320
-rw-r--r--src/main/java/org/anarres/cpp/NumericValue.java179
-rw-r--r--src/main/java/org/anarres/cpp/Preprocessor.java2016
-rw-r--r--src/main/java/org/anarres/cpp/PreprocessorCommand.java44
-rw-r--r--src/main/java/org/anarres/cpp/PreprocessorListener.java85
-rw-r--r--src/main/java/org/anarres/cpp/ResourceFileSystem.java81
-rw-r--r--src/main/java/org/anarres/cpp/Source.java287
-rw-r--r--src/main/java/org/anarres/cpp/SourceIterator.java (renamed from src/java/org/anarres/cpp/SourceIterator.java)0
-rw-r--r--src/main/java/org/anarres/cpp/State.java (renamed from src/java/org/anarres/cpp/State.java)0
-rw-r--r--src/main/java/org/anarres/cpp/StringLexerSource.java (renamed from src/java/org/anarres/cpp/StringLexerSource.java)55
-rw-r--r--src/main/java/org/anarres/cpp/Token.java193
-rw-r--r--src/main/java/org/anarres/cpp/TokenSnifferSource.java (renamed from src/java/org/anarres/cpp/TokenSnifferSource.java)0
-rw-r--r--src/main/java/org/anarres/cpp/TokenType.java128
-rw-r--r--src/main/java/org/anarres/cpp/VirtualFile.java (renamed from src/java/org/anarres/cpp/VirtualFile.java)28
-rw-r--r--src/main/java/org/anarres/cpp/VirtualFileSystem.java (renamed from src/java/org/anarres/cpp/VirtualFileSystem.java)7
-rw-r--r--src/main/java/org/anarres/cpp/Warning.java (renamed from src/java/org/anarres/cpp/Warning.java)16
-rw-r--r--src/main/resources/log4j.properties (renamed from src/resources/log4j.properties)0
-rw-r--r--src/main/resources/org/anarres/cpp/taskdef.properties (renamed from src/resources/org/anarres/cpp/taskdef.properties)0
-rw-r--r--src/main/velocity/org/anarres/cpp/Version.java (renamed from src/java/org/anarres/cpp/Version.java)11
-rw-r--r--src/test/java/org/anarres/cpp/CppReaderTest.java34
-rw-r--r--src/test/java/org/anarres/cpp/ErrorTest.java65
-rw-r--r--src/test/java/org/anarres/cpp/JavaFileSystemTest.java38
-rw-r--r--src/test/java/org/anarres/cpp/JoinReaderTest.java41
-rw-r--r--src/test/java/org/anarres/cpp/LexerSourceTest.java86
-rw-r--r--src/test/java/org/anarres/cpp/MainTest.java11
-rw-r--r--src/test/java/org/anarres/cpp/PreprocessorTest.java167
-rw-r--r--src/test/resources/test0.c (renamed from src/input/test0.c)0
-rw-r--r--src/test/resources/test0.h (renamed from src/input/test0.h)0
-rw-r--r--src/test/resources/test1.c (renamed from src/input/test1.c)0
-rw-r--r--src/test/resources/test1.h (renamed from src/input/test1.h)0
-rw-r--r--src/test/resources/trigraph.c (renamed from src/input/trigraph.c)0
-rw-r--r--src/tests/AutoTestSuite.java121
-rw-r--r--src/tests/org/anarres/cpp/BaseTestCase.java6
-rw-r--r--src/tests/org/anarres/cpp/CppReaderTestCase.java34
-rw-r--r--src/tests/org/anarres/cpp/ErrorTestCase.java66
-rw-r--r--src/tests/org/anarres/cpp/JavaFileSystemTestCase.java39
-rw-r--r--src/tests/org/anarres/cpp/JoinReaderTestCase.java40
-rw-r--r--src/tests/org/anarres/cpp/LexerSourceTestCase.java87
-rw-r--r--src/tests/org/anarres/cpp/MainTestCase.java15
-rw-r--r--src/tests/org/anarres/cpp/PreprocessorTestCase.java160
70 files changed, 5795 insertions, 5734 deletions
diff --git a/src/docs/javadoc/.gitignore b/src/docs/javadoc/.gitignore
deleted file mode 100644
index e69de29..0000000
--- a/src/docs/javadoc/.gitignore
+++ /dev/null
diff --git a/src/java/org/anarres/cpp/ChrootFileSystem.java b/src/java/org/anarres/cpp/ChrootFileSystem.java
deleted file mode 100644
index bce2cfe..0000000
--- a/src/java/org/anarres/cpp/ChrootFileSystem.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-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;
- }
-
- public VirtualFile getFile(String path) {
- return new ChrootFile(path);
- }
-
- 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());
- }
-
- public ChrootFile getChildFile(String name) {
- return new ChrootFile(this, name);
- }
-
- @Override
- public boolean isFile() {
- File real = new File(root, getPath());
- return real.isFile();
- }
-
- public Source getSource() throws IOException {
- return new FileLexerSource(new File(root, getPath()),
- getPath());
- }
- }
-
-}
diff --git a/src/java/org/anarres/cpp/CppReader.java b/src/java/org/anarres/cpp/CppReader.java
deleted file mode 100644
index fbe3545..0000000
--- a/src/java/org/anarres/cpp/CppReader.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.Reader;
-
-import static org.anarres.cpp.Token.*;
-
-/**
- * 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 {
-
- private Preprocessor cpp;
- private String token;
- private int idx;
-
- public CppReader(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(Preprocessor p) {
- cpp = p;
- token = "";
- idx = 0;
- }
-
- /**
- * Returns the Preprocessor used by this CppReader.
- */
- public Preprocessor getPreprocessor() {
- return cpp;
- }
-
- /**
- * Defines the given name as a macro.
- *
- * This is a convnience method.
- */
- public void addMacro(String name)
- throws LexerException {
- cpp.addMacro(name);
- }
-
- /**
- * Defines the given name as a macro.
- *
- * This is a convnience method.
- */
- public void addMacro(String name, 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) {
- /* Never happens.
- if (e.getCause() instanceof IOException)
- throw (IOException)e.getCause();
- */
- IOException ie = new IOException(String.valueOf(e));
- ie.initCause(e);
- throw ie;
- }
- }
-
- 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;
- }
-
- public void close()
- throws IOException {
- if (cpp != null) {
- cpp.close();
- cpp = null;
- }
- token = null;
- }
-
-}
diff --git a/src/java/org/anarres/cpp/CppTask.java b/src/java/org/anarres/cpp/CppTask.java
deleted file mode 100644
index 05fc1f9..0000000
--- a/src/java/org/anarres/cpp/CppTask.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-
-import org.apache.tools.ant.BuildException;
-import org.apache.tools.ant.Task;
-import org.apache.tools.ant.taskdefs.Copy;
-import org.apache.tools.ant.types.FileSet;
-import org.apache.tools.ant.types.FilterChain;
-import org.apache.tools.ant.types.FilterSet;
-import org.apache.tools.ant.types.FilterSetCollection;
-import org.apache.tools.ant.types.Path;
-
-import org.anarres.cpp.LexerException;
-import org.anarres.cpp.Preprocessor;
-import org.anarres.cpp.PreprocessorListener;
-import org.anarres.cpp.Token;
-
-/**
- * An ant task for jcpp.
- */
-public class CppTask extends Copy {
-
- private class Listener extends PreprocessorListener {
- 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 && tok.getType() == Token.EOF)
- break;
- writer.write(tok.getText());
- }
- }
- finally {
- if (writer != null) {
- try {
- writer.close();
- }
- catch (IOException e) {
- }
- }
- }
- }
-
- protected void doFileOperations() {
- if(fileCopyMap.size() > 0) {
- log("Copying " + fileCopyMap.size()
- + " file" + (fileCopyMap.size() == 1 ? "" : "s")
- + " to " + destDir.getAbsolutePath());
-
- Enumeration e = fileCopyMap.keys();
-
- while (e.hasMoreElements()) {
- String fromFile = (String)e.nextElement();
- String[] toFiles = (String[])fileCopyMap.get(fromFile);
-
- for(int i = 0; i < toFiles.length; i++) {
- String toFile = toFiles[i];
-
- 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/java/org/anarres/cpp/FixedTokenSource.java b/src/java/org/anarres/cpp/FixedTokenSource.java
deleted file mode 100644
index eb19685..0000000
--- a/src/java/org/anarres/cpp/FixedTokenSource.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PushbackReader;
-import java.io.Reader;
-import java.io.StringReader;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Iterator;
-
-/* pp */ class FixedTokenSource extends Source {
- private static final Token EOF =
- new Token(Token.EOF, "<ts-eof>");
-
- private 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;
- }
-
- public Token token()
- throws IOException,
- LexerException {
- if (idx >= tokens.size())
- return EOF;
- return tokens.get(idx++);
- }
-
- public String toString() {
- StringBuilder buf = new StringBuilder();
- buf.append("constant token stream " + tokens);
- Source parent = getParent();
- if (parent != null)
- buf.append(" in ").append(String.valueOf(parent));
- return buf.toString();
- }
-}
diff --git a/src/java/org/anarres/cpp/JavaFileSystem.java b/src/java/org/anarres/cpp/JavaFileSystem.java
deleted file mode 100644
index 83a5caa..0000000
--- a/src/java/org/anarres/cpp/JavaFileSystem.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * A virtual filesystem implementation using java.io.
- */
-public class JavaFileSystem implements VirtualFileSystem {
- public VirtualFile getFile(String path) {
- return new JavaFile(path);
- }
-
- 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);
- }
-
- public JavaFile getChildFile(String name) {
- return new JavaFile(this, name);
- }
-
- public Source getSource() throws IOException {
- return new FileLexerSource(this);
- }
-
- }
-
-}
diff --git a/src/java/org/anarres/cpp/JoinReader.java b/src/java/org/anarres/cpp/JoinReader.java
deleted file mode 100644
index 91908a7..0000000
--- a/src/java/org/anarres/cpp/JoinReader.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.Reader;
-import java.io.PushbackReader;
-import java.io.IOException;
-
-/* pp */ class JoinReader /* extends Reader */ {
- private 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;
- }
-
- public void close()
- throws IOException {
- in.close();
- }
-
- 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/java/org/anarres/cpp/LexerSource.java b/src/java/org/anarres/cpp/LexerSource.java
deleted file mode 100644
index 5f3bfc7..0000000
--- a/src/java/org/anarres/cpp/LexerSource.java
+++ /dev/null
@@ -1,893 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PushbackReader;
-import java.io.Reader;
-
-import java.util.Set;
-
-import static org.anarres.cpp.Token.*;
-
-/** Does not handle digraphs. */
-public class LexerSource extends Source {
- private static final boolean DEBUG = false;
-
- private JoinReader reader;
- private 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 final 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 final 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. */
- 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);
- }
-
- private Token ccomment()
- throws IOException,
- LexerException {
- StringBuilder text = new StringBuilder("/*");
- int d;
- do {
- do {
- d = read();
- text.append((char)d);
- } while (d != '*');
- do {
- d = read();
- text.append((char)d);
- } while (d == '*');
- } while (d != '/');
- return new Token(CCOMMENT, text.toString());
- }
-
- 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;
- }
- }
-
- 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));
- }
-
- 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 " + (char)close);
- }
- }
-
- 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("Nultiple 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("Nultiple 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("Nultiple 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("Nultiple length suffixes after " + text);
- flags |= NumericValue.F_DOUBLE;
- text.append((char)d);
- d = read();
- }
- // This should probably be isPunct() || isWhite().
- else if (Character.isLetter(d) || d == '_') {
- unread(d);
- value.setFlags(flags);
- return invalid(text,
- "Invalid suffix \"" + (char)d +
- "\" on numeric constant");
- }
- else {
- unread(d);
- value.setFlags(flags);
- return new Token(NUMBER,
- text.toString(), value);
- }
- }
- }
-
- /* Either a decimal part, or a hex exponent. */
- private String _number_part(StringBuilder text, int base)
- throws IOException,
- LexerException {
- StringBuilder part = new StringBuilder();
- int d = read();
- while (Character.digit(d, base) != -1) {
- text.append((char)d);
- part.append((char)d);
- d = read();
- }
- unread(d);
- return part.toString();
- }
-
- /* We already chewed a zero, so empty is fine. */
- private Token number_octal()
- throws IOException,
- LexerException {
- StringBuilder text = new StringBuilder("0");
- String integer = _number_part(text, 8);
- int d = read();
- NumericValue value = new NumericValue(8, integer);
- return _number_suffix(text, value, d);
- }
-
- /* We do not know whether know the first digit is valid. */
- private Token number_hex(char x)
- throws IOException,
- LexerException {
- StringBuilder text = new StringBuilder("0");
- text.append(x);
- String integer = _number_part(text, 16);
- NumericValue value = new NumericValue(16, integer);
- int d = read();
- if (d == '.') {
- String fraction = _number_part(text, 16);
- value.setFractionalPart(fraction);
- d = read();
- }
- if (d == 'P' || d == 'p') {
- String exponent = _number_part(text, 10);
- value.setExponent(exponent);
- d = read();
- }
- // XXX Make sure it's got enough parts
- return _number_suffix(text, value, d);
- }
-
- /* We know we have at least one valid digit, but empty is not
- * fine. */
- private Token number_decimal()
- throws IOException,
- LexerException {
- StringBuilder text = new StringBuilder();
- String integer = _number_part(text, 10);
- NumericValue value = new NumericValue(10, integer);
- int d = read();
- if (d == '.') {
- String fraction = _number_part(text, 10);
- value.setFractionalPart(fraction);
- d = read();
- }
- if (d == 'E' || d == 'e') {
- String exponent = _number_part(text, 10);
- value.setExponent(exponent);
- d = read();
- }
- // XXX Make sure it's got enough parts
- return _number_suffix(text, value, d);
- }
-
- 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());
- }
-
- 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. */
- 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);
- }
-
- 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_decimal();
- }
- /* XXX decimal fraction */
- break;
-
- case '0':
- /* octal or hex */
- d = read();
- if (d == 'x' || d == 'X')
- tok = number_hex((char)d);
- else {
- unread(d);
- tok = number_octal();
- }
- 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_decimal();
- }
- 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;
- }
-
- public void close()
- throws IOException {
- if (reader != null) {
- reader.close();
- reader = null;
- }
- super.close();
- }
-
-}
diff --git a/src/java/org/anarres/cpp/MacroTokenSource.java b/src/java/org/anarres/cpp/MacroTokenSource.java
deleted file mode 100644
index 0759875..0000000
--- a/src/java/org/anarres/cpp/MacroTokenSource.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PushbackReader;
-import java.io.Reader;
-import java.io.StringReader;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-import static org.anarres.cpp.Token.*;
-
-/* This source should always be active, since we don't expand macros
- * in any inactive context. */
-/* pp */ class MacroTokenSource extends Source {
- private Macro macro;
- private Iterator<Token> tokens; /* Pointer into the macro. */
- private List<Argument> args; /* { unexpanded, expanded } */
- private Iterator<Token> arg; /* "current expansion" */
-
- /* pp */ MacroTokenSource(Macro m, List<Argument> args) {
- this.macro = m;
- this.tokens = m.getTokens().iterator();
- this.args = args;
- this.arg = null;
- }
-
- @Override
- /* pp */ boolean isExpanding(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(StringBuilder buf, CharSequence cs) {
- 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(StringBuilder buf, Argument arg) {
- Iterator<Token> it = arg.iterator();
- while (it.hasNext()) {
- Token tok = it.next();
- buf.append(tok.getText());
- }
- }
-
- private Token stringify(Token pos, 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(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);
- }
-
- 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 */
- }
-
- 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/java/org/anarres/cpp/Main.java b/src/java/org/anarres/cpp/Main.java
deleted file mode 100644
index 8777c72..0000000
--- a/src/java/org/anarres/cpp/Main.java
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-
-import gnu.getopt.Getopt;
-import gnu.getopt.LongOpt;
-
-import static org.anarres.cpp.Token.*;
-
-/**
- * (Currently a simple test class).
- */
-public class Main {
-
- private static class Option extends LongOpt {
- private String eg;
- private String help;
- public Option(String word, int arg, int ch,
- String eg, String help) {
- super(word, arg, null, ch);
- this.eg = eg;
- this.help = help;
- }
- }
-
- private static final Option[] OPTS = new Option[] {
- new Option("help", LongOpt.NO_ARGUMENT, 'h', null,
- "Displays help and usage information."),
- new Option("define", LongOpt.REQUIRED_ARGUMENT, 'D', "name=definition",
- "Defines the given macro."),
- new Option("undefine", LongOpt.REQUIRED_ARGUMENT, 'U', "name",
- "Undefines the given macro, previously either builtin or defined using -D."),
- new Option("include", LongOpt.REQUIRED_ARGUMENT, 1, "file",
- "Process file as if \"#" + "include \"file\"\" appeared as the first line of the primary source file."),
- new Option("incdir", LongOpt.REQUIRED_ARGUMENT, 'I', "dir",
- "Adds the directory dir to the list of directories to be searched for header files."),
- new Option("iquote", LongOpt.REQUIRED_ARGUMENT, 0, "dir",
- "Adds the directory dir to the list of directories to be searched for header files included using \"\"."),
- new Option("warning", LongOpt.REQUIRED_ARGUMENT, 'W', "type",
- "Enables the named warning class (" + getWarnings() + ")."),
- new Option("no-warnings", LongOpt.NO_ARGUMENT, 'w', null,
- "Disables ALL warnings."),
- new Option("verbose", LongOpt.NO_ARGUMENT, 'v', null,
- "Operates incredibly verbosely."),
- new Option("debug", LongOpt.NO_ARGUMENT, 3, null,
- "Operates incredibly verbosely."),
- new Option("version", LongOpt.NO_ARGUMENT, 2, null,
- "Prints jcpp's version number (" + Version.getVersion() + ")"),
- };
-
- 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 {
- Option[]opts = OPTS;
- String sopts = getShortOpts(opts);
- Getopt g = new Getopt("jcpp", args, sopts, opts);
- int c;
- String arg;
- int idx;
-
- Preprocessor pp = new Preprocessor();
- pp.addFeature(Feature.DIGRAPHS);
- pp.addFeature(Feature.TRIGRAPHS);
- pp.addFeature(Feature.LINEMARKERS);
- pp.addWarning(Warning.IMPORT);
- pp.setListener(new PreprocessorListener());
- 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");
-
- GETOPT: while ((c = g.getopt()) != -1) {
- switch (c) {
- case 'D':
- arg = g.getOptarg();
- idx = arg.indexOf('=');
- if (idx == -1)
- pp.addMacro(arg);
- else
- pp.addMacro(arg.substring(0, idx),
- arg.substring(idx + 1));
- break;
- case 'U':
- pp.getMacros().remove(g.getOptarg());
- break;
- case 'I':
- pp.getSystemIncludePath().add(g.getOptarg());
- break;
- case 0: // --iquote=
- pp.getQuoteIncludePath().add(g.getOptarg());
- break;
- case 'W':
- arg = g.getOptarg().toUpperCase();
- arg = arg.replace('-', '_');
- if (arg.equals("ALL"))
- pp.addWarnings(EnumSet.allOf(Warning.class));
- else
- pp.addWarning(Enum.valueOf(Warning.class, arg));
- break;
- case 'w':
- pp.getWarnings().clear();
- break;
- case 1: // --include=
- // pp.addInput(new File(g.getOptarg()));
- // Comply exactly with spec.
- pp.addInput(new StringLexerSource(
- "#" + "include \"" + g.getOptarg() + "\"\n"
- ));
- break;
- case 2: // --version
- version(System.out);
- return;
- case 'v':
- pp.addFeature(Feature.VERBOSE);
- break;
- case 3:
- pp.addFeature(Feature.DEBUG);
- break;
- case 'h':
- usage(getClass().getName(), opts);
- return;
- default:
- throw new Exception("Illegal option " + (char)c);
- case '?':
- continue; /* Make failure-proof. */
- }
- }
-
- for (int i = g.getOptind(); i < args.length; i++)
- pp.addInput(new FileLexerSource(new File(args[i])));
- if (g.getOptind() == args.length)
- pp.addInput(new InputLexerSource(System.in));
-
- if (pp.getFeature(Feature.VERBOSE)) {
- System.err.println("#"+"include \"...\" search starts here:");
- for (String dir : pp.getQuoteIncludePath())
- System.err.println(" " + dir);
- System.err.println("#"+"include <...> search starts here:");
- for (String dir : pp.getSystemIncludePath())
- System.err.println(" " + dir);
- System.err.println("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) {
- e.printStackTrace(System.err);
- Source s = pp.getSource();
- while (s != null) {
- System.err.println(" -> " + s);
- s = s.getParent();
- }
- }
-
- }
-
- private void version(PrintStream out) {
- out.println("Anarres Java C Preprocessor version " + Version.getVersion());
- out.println("Copyright (C) 2008 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.");
- }
-
-
- private static String getShortOpts(Option[] opts)
- throws Exception {
- StringBuilder buf = new StringBuilder();
- for (int i = 0; i < opts.length; i++) {
- char c = (char)opts[i].getVal();
- if (!Character.isLetterOrDigit(c))
- continue;
- for (int j = 0; j < buf.length(); j++)
- if (buf.charAt(j) == c)
- throw new Exception(
- "Duplicate short option " + c
- );
- buf.append(c);
- switch (opts[i].getHasArg()) {
- case LongOpt.NO_ARGUMENT:
- break;
- case LongOpt.OPTIONAL_ARGUMENT:
- buf.append("::");
- break;
- case LongOpt.REQUIRED_ARGUMENT:
- buf.append(":");
- break;
- }
- }
- return buf.toString();
- }
-
- /* This is incomplete but nearly there. */
- /**
- * Wraps a string.
- *
- * The output form is:
- * <pre>
- * prefix in[0]
- * &lt;--indent-&gt; in[1]
- * &lt;--indent-&gt; in[2]
- * &lt;-----width----&gt;
- * </pre>
- */
- /* XXX There's some of this in commons. */
- private static String wrap(String in, String prefix,
- int indent, int width) {
- StringBuilder buf = new StringBuilder(prefix);
-
- while (buf.length() < indent)
- buf.append(' ');
-
- int start = 0;
-
- while (start < in.length()) {
- while (start < in.length() &&
- Character.isWhitespace(in.charAt(start)))
- start++;
-
- int end = start + width - indent;
-
- if (end > in.length()) {
- buf.append(in.substring(start));
- break;
- }
-
- int idx = end;
- while (!Character.isWhitespace(in.charAt(idx)))
- idx--;
-
- if (idx == start) {
- idx = end - 1;
- buf.append(in.substring(start, idx));
- buf.append('-');
- }
- else {
- buf.append(in.substring(start, idx));
- start = idx;
- }
-
- start = idx;
- }
-
- return buf.toString();
- }
-
- private static void usage(String command, Option[] options) {
- StringBuilder text = new StringBuilder("Usage: ");
- text.append(command).append('\n');
- for (int i = 0; i < options.length; i++) {
- StringBuilder line = new StringBuilder();
- Option opt = options[i];
- line.append(" --").append(opt.getName());
- switch (opt.getHasArg()) {
- case LongOpt.NO_ARGUMENT:
- break;
- case LongOpt.OPTIONAL_ARGUMENT:
- line.append("[=").append(opt.eg).append(']');
- break;
- case LongOpt.REQUIRED_ARGUMENT:
- line.append('=').append(opt.eg);
- break;
- }
- if (Character.isLetterOrDigit(opt.getVal()))
- line.append(" (-").append((char)opt.getVal()).append(")");
- if (line.length() < 30) {
- while (line.length() < 30)
- line.append(' ');
- }
- else {
- line.append('\n');
- for (int j = 0; j < 30; j++)
- line.append(' ');
- }
- /* This should use wrap. */
- line.append(opt.help);
- line.append('\n');
- text.append(line);
- }
-
- System.out.println(text);
- }
-
-
-
-#if (false)
- public static void oldmain(String[] args) throws Exception {
- List<String> path = new ArrayList<String>();
- path.add("/usr/include");
- path.add("/usr/local/include");
- path.add("/usr/lib/gcc/i686-pc-linux-gnu/4.1.2/include");
-
- Source source = new FileLexerSource(new File(args[0]));
- Preprocessor pp = new Preprocessor(source);
- pp.setSystemIncludePath(path);
-
- for (int i = 1; i < args.length; i++) {
- pp.push_source(new FileLexerSource(new File(args[i])),true);
- }
-
- Macro m = new Macro("__WORDSIZE");
- m.addToken(new Token(NUMBER, -1, -1, "32", new NumericValue(10, "32")));
- pp.addMacro(m);
-
- m = new Macro("__STDC__");
- m.addToken(new Token(NUMBER, -1, -1, "1", new NumericValue(10, "1")));
- pp.addMacro(m);
-
- try {
- for (;;) {
- Token tok = pp.token();
- if (tok != null && tok.getType() == Token.EOF)
- break;
- switch (2) {
- case 0:
- System.out.print(tok);
- break;
- case 1:
- System.out.print("[" + tok.getText() + "]");
- break;
- case 2:
- System.out.print(tok.getText());
- break;
- }
- }
- }
- catch (Exception e) {
- e.printStackTrace();
- Source s = pp.getSource();
- while (s != null) {
- System.out.println(" -> " + s);
- s = s.getParent();
- }
-
- /*
- Iterator<State> it = pp.states.iterator();
- while (it.hasNext()) {
- System.out.println(" -? " + it.next());
- }
- */
-
- }
-
- Map<String,Macro> macros = pp.getMacros();
- List<String> keys = new ArrayList<String>(
- macros.keySet()
- );
- Collections.sort(keys);
- Iterator<String> mt = keys.iterator();
- while (mt.hasNext()) {
- String key = mt.next();
- Macro macro = macros.get(key);
- System.out.println("#" + "macro " + macro);
- }
-
- }
-#end
-
-}
diff --git a/src/java/org/anarres/cpp/NumericValue.java b/src/java/org/anarres/cpp/NumericValue.java
deleted file mode 100644
index f8b1559..0000000
--- a/src/java/org/anarres/cpp/NumericValue.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-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 int base;
- private String integer;
- private String fraction;
- private String exponent;
- private int flags;
-
- public NumericValue(int base, String integer) {
- this.base = base;
- this.integer = integer;
- }
-
- public int getBase() {
- return base;
- }
-
- public String getIntegerPart() {
- return integer;
- }
-
- public String getFractionalPart() {
- return fraction;
- }
-
- /* pp */ void setFractionalPart(String fraction) {
- this.fraction = fraction;
- }
-
- public String getExponent() {
- return exponent;
- }
-
- /* pp */ void setExponent(String exponent) {
- 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.
- */
- 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 += getFractionalPart().length();
- }
- if (getExponent() != null)
- scale -= Integer.parseInt(getExponent());
- BigInteger unscaled = new BigInteger(text, getBase());
- return new BigDecimal(unscaled, scale);
- }
-
- 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();
- }
-
- @Override
- public int intValue() {
- return Integer.parseInt(toString());
- }
-
- @Override
- public long longValue() {
- return Long.parseLong(toString());
- }
-
- @Override
- public float floatValue() {
- return Float.parseFloat(toString());
- }
-
- @Override
- public double doubleValue() {
- 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/java/org/anarres/cpp/Preprocessor.java b/src/java/org/anarres/cpp/Preprocessor.java
deleted file mode 100644
index 9bda523..0000000
--- a/src/java/org/anarres/cpp/Preprocessor.java
+++ /dev/null
@@ -1,1983 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-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.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-
-import static org.anarres.cpp.Token.*;
-
-/**
- * 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 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 List<Source> inputs;
-
- /* The fundamental engine. */
- private Map<String,Macro> macros;
- private Stack<State> states;
- private Source source;
-
- /* Miscellaneous support. */
- private int counter;
-
- /* Support junk to make it work like cpp */
- private List<String> quoteincludepath; /* -iquote */
- private List<String> sysincludepath; /* -I */
- private List<String> frameworkspath;
- private Set<Feature> features;
- private 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(Source initial) {
- this();
- addInput(initial);
- }
-
- /** Equivalent to
- * 'new Preprocessor(new {@link FileLexerSource}(file))'
- */
- public Preprocessor(File file)
- throws IOException {
- this(new FileLexerSource(file));
- }
-
- /**
- * Sets the VirtualFileSystem used by this Preprocessor.
- */
- public void setFileSystem(VirtualFileSystem filesystem) {
- this.filesystem = filesystem;
- }
-
- /**
- * Returns the VirtualFileSystem used by this Preprocessor.
- */
- 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(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.
- */
- public PreprocessorListener getListener() {
- return listener;
- }
-
- /**
- * Returns the feature-set for this Preprocessor.
- *
- * This set may be freely modified by user code.
- */
- public Set<Feature> getFeatures() {
- return features;
- }
-
- /**
- * Adds a feature to the feature-set of this Preprocessor.
- */
- public void addFeature(Feature f) {
- features.add(f);
- }
-
- /**
- * Adds features to the feature-set of this Preprocessor.
- */
- public void addFeatures(Collection<Feature> f) {
- features.addAll(f);
- }
-
- /**
- * Returns true if the given feature is in
- * the feature-set of this Preprocessor.
- */
- public boolean getFeature(Feature f) {
- return features.contains(f);
- }
-
- /**
- * Returns the warning-set for this Preprocessor.
- *
- * This set may be freely modified by user code.
- */
- public Set<Warning> getWarnings() {
- return warnings;
- }
-
- /**
- * Adds a warning to the warning-set of this Preprocessor.
- */
- public void addWarning(Warning w) {
- warnings.add(w);
- }
-
- /**
- * Adds warnings to the warning-set of this Preprocessor.
- */
- public void addWarnings(Collection<Warning> w) {
- warnings.addAll(w);
- }
-
- /**
- * Returns true if the given warning is in
- * the warning-set of this Preprocessor.
- */
- public boolean getWarning(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(Source source) {
- source.init(this);
- inputs.add(source);
- }
-
- /**
- * Adds input for the Preprocessor.
- *
- * @see #addInput(Source)
- */
- public void addInput(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(int line, int column, 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(Token tok, 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(int line, int column, 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(Token tok, 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.
- */
- public void addMacro(Macro m) throws LexerException {
- // System.out.println("Macro " + m);
- String name = m.getName();
- /* Already handled as a source error in macro(). */
- if ("defined".equals(name))
- throw new LexerException("Cannot redefine name 'defined'");
- 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(String name, String value)
- throws LexerException {
- try {
- Macro m = new Macro(name);
- StringLexerSource s = new StringLexerSource(value);
- for (;;) {
- Token tok = s.token();
- if (tok.getType() == EOF)
- break;
- m.addToken(tok);
- }
- addMacro(m);
- }
- catch (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(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(List<String> path) {
- this.quoteincludepath = path;
- }
-
- /**
- * Returns the user include-path of this Preprocessor.
- *
- * This list may be freely modified by user code.
- */
- public List<String> getQuoteIncludePath() {
- return quoteincludepath;
- }
-
- /**
- * Sets the system include path used by this Preprocessor.
- */
- /* Note for future: Create an IncludeHandler? */
- public void setSystemIncludePath(List<String> path) {
- this.sysincludepath = path;
- }
-
- /**
- * Returns the system include-path of this Preprocessor.
- *
- * This list may be freely modified by user code.
- */
- 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(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.
- */
- public List<String> getFrameworksPath() {
- return frameworkspath;
- }
-
- /**
- * Returns the Map of Macros parsed during the run of this
- * Preprocessor.
- */
- 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.
- */
- public Macro getMacro(String name) {
- return macros.get(name);
- }
-
-/* States */
-
- private void push_state() {
- State top = states.peek();
- states.push(new State(top));
- }
-
- private void pop_state()
- throws LexerException {
- State s = states.pop();
- if (states.isEmpty()) {
- error(0, 0, "#" + "endif without #" + "if");
- states.push(s);
- }
- }
-
- private boolean isActive() {
- 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()
- */
- protected Source getSource() {
- return source;
- }
-
- /**
- * Pushes a Source onto the input stack.
- *
- * @see #getSource()
- * @see #pop_source()
- */
- protected void push_source(Source source, boolean autopop) {
- source.init(this);
- source.setParent(this.source, autopop);
- // source.setListener(listener);
- if (listener != null)
- listener.handleSourceChange(this.source, "suspend");
- this.source = source;
- if (listener != null)
- listener.handleSourceChange(this.source, "push");
- }
-
- /**
- * Pops a Source from the input stack.
- *
- * @see #getSource()
- * @see #push_source(Source,boolean)
- */
- protected void pop_source()
- throws IOException {
- if (listener != null)
- listener.handleSourceChange(this.source, "pop");
- 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, "resume");
- }
-
-
-/* Source tokens */
-
- private Token source_token;
-
- /* XXX Make this include the NL, and make all cpp directives eat
- * their own NL. */
- private Token line_token(int line, String name, String extra) {
- StringBuilder buf = new StringBuilder();
- buf.append("#line ").append(line)
- .append(" \"");
- /* XXX This call to escape(name) is correct but ugly. */
- MacroTokenSource.escape(buf, name);
- buf.append("\"").append(extra).append("\n");
- return new Token(P_LINE, line, 0, buf.toString(), null);
- }
-
- private Token source_token()
- throws IOException,
- LexerException {
- if (source_token != null) {
- Token tok = source_token;
- source_token = null;
- if (getFeature(Feature.DEBUG))
- System.err.println("Returning unget token " + tok);
- return tok;
- }
-
- for (;;) {
- Source s = getSource();
- if (s == null) {
- if (inputs.isEmpty())
- return new Token(EOF);
- Source t = inputs.remove(0);
- push_source(t, true);
- if (getFeature(Feature.LINEMARKERS))
- return line_token(t.getLine(), t.getName(), " 1");
- continue;
- }
- Token tok = s.token();
- /* XXX Refactor with skipline() */
- if (tok.getType() == EOF && s.isAutopop()) {
- // System.out.println("Autopop " + s);
- pop_source();
- 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. */
- return line_token(t.getLine() + 1, t.getName(), " 2");
- }
- continue;
- }
- if (getFeature(Feature.DEBUG))
- System.err.println("Returning fresh token " + tok);
- return tok;
- }
- }
-
- private void source_untoken(Token tok) {
- if (this.source_token != null)
- throw new IllegalStateException("Cannot return two tokens");
- this.source_token = tok;
- }
-
- private boolean isWhite(Token tok) {
- 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(boolean white)
- throws IOException,
- LexerException {
- // (new Exception("skipping line")).printStackTrace(System.out);
- Source s = getSource();
- Token tok = s.skipline(white);
- /* XXX Refactor with source_token() */
- if (tok.getType() == EOF && s.isAutopop()) {
- // System.out.println("Autopop " + s);
- pop_source();
- 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. */
- return line_token(t.getLine() + 1, t.getName(), " 2");
- }
- }
- return tok;
- }
-
- /* processes and expands a macro. */
- private boolean macro(Macro m, 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:
- /* 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()) {
- 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 (Argument a : args)
- a.expand(this);
- */
-
- for (int i = 0; i < args.size(); i++) {
- args.get(i).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(),
- String.valueOf(orig.getLine()),
- new NumericValue(10, "" + orig.getLine())) }
- ), true);
- }
- else if (m == __FILE__) {
- StringBuilder buf = new StringBuilder("\"");
- String name = getSource().getName();
- if (name == null)
- name = "<no file>";
- for (int i = 0; i < name.length(); i++) {
- char c = name.charAt(i);
- switch (c) {
- case '\\':
- buf.append("\\\\");
- break;
- case '"':
- buf.append("\\\"");
- break;
- default:
- buf.append(c);
- break;
- }
- }
- buf.append("\"");
- 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(). */
- int value = this.counter++;
- push_source(new FixedTokenSource(
- new Token[] { new Token(NUMBER,
- orig.getLine(), orig.getColumn(),
- String.valueOf(value),
- new NumericValue(10, "" + 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. */
- /* pp */ List<Token> expand(List<Token> arg)
- throws IOException,
- LexerException {
- List<Token> expansion = new ArrayList<Token>();
- boolean space = false;
-
- push_source(new FixedTokenSource(arg), false);
-
- EXPANSION: for (;;) {
- 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;
- }
- }
-
- pop_source();
-
- 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 */
-
- String name = tok.getText();
- if ("defined".equals(name)) {
- error(tok, "Cannot redefine name 'defined'");
- return source_skipline(false);
- }
-
- 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 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;
- 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))
- System.err.println("Defined macro " + m);
- addMacro(m);
-
- return tok; /* NL or EOF. */
- }
-
- private Token undef()
- throws IOException,
- LexerException {
- 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 {
- Macro m = macros.get(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.
- */
- private boolean include(VirtualFile file)
- throws IOException,
- LexerException {
- // System.out.println("Try to include " + file);
- if (!file.isFile())
- return false;
- if (getFeature(Feature.DEBUG))
- System.err.println("pp: including " + file);
- push_source(file.getSource(), true);
- return true;
- }
-
- /**
- * Includes a file from an include path, by name.
- */
- private boolean include(Iterable<String> path, String name)
- throws IOException,
- LexerException {
- for (String dir : path) {
- VirtualFile file = filesystem.getFile(dir, name);
- if (include(file))
- return true;
- }
- return false;
- }
-
- /**
- * Handles an include directive.
- */
- private void include(String parent, int line,
- String name, boolean quoted)
- throws IOException,
- LexerException {
- VirtualFile pdir = null;
- if (quoted) {
- VirtualFile pfile = filesystem.getFile(parent);
- pdir = pfile.getParentFile();
- VirtualFile ifile = pdir.getChildFile(name);
- if (include(ifile))
- return;
- if (include(quoteincludepath, name))
- return;
- }
-
- if (include(sysincludepath, name))
- return;
-
- StringBuilder buf = new StringBuilder();
- buf.append("File not found: ").append(name);
- buf.append(" in");
- if (quoted) {
- buf.append(" .").append('(').append(pdir).append(')');
- for (String dir : quoteincludepath)
- buf.append(" ").append(dir);
- }
- for (String dir : sysincludepath)
- buf.append(" ").append(dir);
- error(line, 0, buf.toString());
- }
-
- private Token include(boolean next)
- throws IOException,
- LexerException {
- 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. */
- 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);
-
- /* '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(Token name, List<Token> value)
- throws IOException,
- LexerException {
- warning(name, "Unknown #" + "pragma: " + name.getText());
- }
-
- private Token pragma()
- throws IOException,
- LexerException {
- Token name;
-
- NAME: for (;;) {
- 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;
- 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(Token pptok, boolean is_error)
- throws IOException,
- LexerException {
- 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. */
- private Token expanded_token()
- throws IOException,
- LexerException {
- for (;;) {
- Token tok = source_token();
- // System.out.println("Source token is " + tok);
- if (tok.getType() == IDENTIFIER) {
- Macro m = macros.get(tok.getText());
- if (m == null)
- return tok;
- if (source.isExpanding(m))
- return tok;
- if (macro(m, tok))
- continue;
- }
- return tok;
- }
- }
-
- 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;
- }
-
-
- private Token expr_token = null;
-
- 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()");
- }
- }
- }
- }
-
- // System.out.println("expr_token returns " + tok);
-
- return tok;
- }
-
- private void expr_untoken(Token tok)
- throws LexerException {
- if (expr_token != null)
- throw new InternalException(
- "Cannot unget two expression tokens."
- );
- expr_token = tok;
- }
-
- private int expr_priority(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(int priority)
- throws IOException,
- LexerException {
- /*
- System.out.flush();
- (new Exception("expr(" + priority + ") called")).printStackTrace();
- System.err.flush();
- */
-
- 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");
- return 0;
- }
- break;
-
- case '~': lhs = ~expr(11); break;
- case '!': lhs = expr(11) == 0 ? 1 : 0; break;
- case '-': lhs = -expr(11); break;
- case NUMBER:
- NumericValue value = (NumericValue)tok.getValue();
- lhs = value.longValue();
- break;
- case CHARACTER:
- lhs = (long)((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);
- Token op = expr_token();
- 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 '?':
- /* XXX Handle this? */
-
- default:
- error(op,
- "Unexpected operator " + op.getText());
- return 0;
-
- }
- }
-
- /*
- System.out.flush();
- (new Exception("expr returning " + lhs)).printStackTrace();
- System.err.flush();
- */
- // System.out.println("expr returning " + lhs);
-
- return lhs;
- }
-
- private Token toWhitespace(Token tok) {
- String text = tok.getText();
- int len = text.length();
- boolean cr = false;
- int nls = 0;
-
- for (int i = 0; i < len; i++) {
- 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;
- }
- }
-
- char[] cbuf = new char[nls];
- Arrays.fill(cbuf, '\n');
- return new Token(WHITESPACE,
- tok.getLine(), tok.getColumn(),
- new String(cbuf));
- }
-
- private final Token _token()
- throws IOException,
- LexerException {
-
- for (;;) {
- Token tok;
- if (!isActive()) {
- try {
- /* XXX Tell lexer to ignore warnings. */
- source.setActive(false);
- tok = source_token();
- }
- finally {
- /* XXX Tell lexer to stop ignoring warnings. */
- source.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 XOR_EQ:
- return tok;
-
- case NUMBER:
- return tok;
-
- case IDENTIFIER:
- Macro m = macros.get(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);
- }
- Integer _ppcmd = ppcmds.get(tok.getText());
- if (_ppcmd == null) {
- error(tok,
- "Unknown preprocessor directive " +
- tok.getText());
- return source_skipline(false);
- }
- int ppcmd = _ppcmd.intValue();
-
- 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 {
- String text = tok.getText();
- 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 {
- String text = tok.getText();
- 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);
- }
-
-
- }
- }
- }
-
- 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.
- */
- public Token token()
- throws IOException,
- LexerException {
- Token tok = _token();
- if (getFeature(Feature.DEBUG))
- System.err.println("pp: Returning " + tok);
- return tok;
- }
-
-#set ($i = 1) /* First ppcmd is 1, not 0. */
-#set ($ppcmds = [ "define", "elif", "else", "endif", "error", "if", "ifdef", "ifndef", "include", "line", "pragma", "undef", "warning", "include_next", "import" ])
-#foreach ($ppcmd in $ppcmds)
- private static final int PP_$ppcmd.toUpperCase() = $i;
-#set ($i = $i + 1)
-#end
-
- private static final Map<String,Integer> ppcmds =
- new HashMap<String,Integer>();
-
- static {
-#foreach ($ppcmd in $ppcmds)
- ppcmds.put("$ppcmd", Integer.valueOf(PP_$ppcmd.toUpperCase()));
-#end
- }
-
-
- public String toString() {
- StringBuilder buf = new StringBuilder();
-
- Source s = getSource();
- while (s != null) {
- buf.append(" -> ").append(String.valueOf(s)).append("\n");
- s = s.getParent();
- }
-
- Map<String,Macro> macros = getMacros();
- List<String> keys = new ArrayList<String>(
- macros.keySet()
- );
- Collections.sort(keys);
- Iterator<String> mt = keys.iterator();
- while (mt.hasNext()) {
- String key = mt.next();
- Macro macro = macros.get(key);
- buf.append("#").append("macro ").append(macro).append("\n");
- }
-
- return buf.toString();
- }
-
- public void close()
- throws IOException {
- {
- Source s = source;
- while (s != null) {
- s.close();
- s = s.getParent();
- }
- }
- for (Source s : inputs) {
- s.close();
- }
- }
-
-}
diff --git a/src/java/org/anarres/cpp/PreprocessorListener.java b/src/java/org/anarres/cpp/PreprocessorListener.java
deleted file mode 100644
index d149a70..0000000
--- a/src/java/org/anarres/cpp/PreprocessorListener.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.File;
-
-/**
- * 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 PreprocessorListener {
-
- private int errors;
- private int warnings;
-
- public PreprocessorListener() {
- clear();
- }
-
- public void clear() {
- errors = 0;
- warnings = 0;
- }
-
- public int getErrors() {
- return errors;
- }
-
- public int getWarnings() {
- return warnings;
- }
-
- protected void print(String msg) {
- System.err.println(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.
- */
- public void handleWarning(Source source, int line, int column,
- 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.
- */
- public void handleError(Source source, int line, int column,
- String msg)
- throws LexerException {
- errors++;
- print(source.getName() + ":" + line + ":" + column +
- ": error: " + msg);
- }
-
- public void handleSourceChange(Source source, String event) {
- }
-
-}
diff --git a/src/java/org/anarres/cpp/Source.java b/src/java/org/anarres/cpp/Source.java
deleted file mode 100644
index 5f50a86..0000000
--- a/src/java/org/anarres/cpp/Source.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-import java.io.BufferedReader;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PushbackReader;
-import java.io.Reader;
-import java.io.StringReader;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import static org.anarres.cpp.Token.*;
-
-/**
- * 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(Source parent, boolean autopop) {
- this.parent = parent;
- this.autopop = autopop;
- }
-
- /**
- * Returns the parent source of this source.
- *
- * Sources form a singly linked list.
- */
- /* pp */ final Source getParent() {
- return parent;
- }
-
- // @OverrideMustInvoke
- /* pp */ void init(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(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.
- */
- /* pp */ String getPath() {
- Source parent = getParent();
- if (parent != null)
- return parent.getPath();
- return null;
- }
-
- /**
- * Returns the human-readable name of the current Source.
- */
- /* pp */ String getName() {
- Source parent = getParent();
- if (parent != null)
- return parent.getName();
- return null;
- }
-
- /**
- * Returns the current line number within this Source.
- */
- public int getLine() {
- Source parent = getParent();
- if (parent == null)
- return 0;
- return parent.getLine();
- }
-
- /**
- * Returns the current column number within this Source.
- */
- public int getColumn() {
- 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(Macro m) {
- 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(boolean b) {
- this.active = b;
- }
-
- /* pp */ boolean isActive() {
- return active;
- }
-
- /**
- * Returns the next Token parsed from this input stream.
- *
- * @see Token
- */
- public abstract Token token()
- throws IOException,
- LexerException;
-
- /**
- * Returns a token iterator for this Source.
- */
- 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.
- */
- public Token skipline(boolean white)
- throws IOException,
- LexerException {
- for (;;) {
- 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(int line, int column, 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(int line, int column, 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/java/org/anarres/cpp/Token.java b/src/java/org/anarres/cpp/Token.java
deleted file mode 100644
index a2cac39..0000000
--- a/src/java/org/anarres/cpp/Token.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * 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 org.anarres.cpp;
-
-/**
- * A Preprocessor token.
- *
- * @see Preprocessor
- */
-public final class Token {
-
- // public static final int EOF = -1;
-
- private int type;
- private int line;
- private int column;
- private Object value;
- private 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, type < _TOKENS ? texts[type] : "TOK" + 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.
- */
- 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 final String getTokenName(int type) {
- if (type < 0)
- return "Invalid" + type;
- if (type >= names.length)
- return "Invalid" + type;
- if (names[type] == null)
- return "Unknown" + type;
- return names[type];
- }
-
-#set ($i = 257)
-#set ($tokens = [ "AND_EQ", "ARROW", "CHARACTER", "CCOMMENT", "CPPCOMMENT", "DEC", "DIV_EQ", "ELLIPSIS", "EOF", "EQ", "GE", "HASH", "HEADER", "IDENTIFIER", "INC", "NUMBER", "LAND", "LAND_EQ", "LE", "LITERAL", "LOR", "LOR_EQ", "LSH", "LSH_EQ", "MOD_EQ", "MULT_EQ", "NE", "NL", "OR_EQ", "PASTE", "PLUS_EQ", "RANGE", "RSH", "RSH_EQ", "SQSTRING", "STRING", "SUB_EQ", "WHITESPACE", "XOR_EQ", "M_ARG", "M_PASTE", "M_STRING", "P_LINE", "INVALID" ])
-#foreach ($token in $tokens)
- /** The token type $token. */
- public static final int $token = $i;
-#set ($i = $i + 1)
-#end
- /**
- * The number of possible semantic token types.
- *
- * Please note that not all token types below 255 are used.
- */
- public static final int _TOKENS = $i;
-
- /** The position-less space token. */
- /* pp */ static final Token space = new Token(WHITESPACE, -1, -1, " ");
-
- private static final String[] names = new String[_TOKENS];
- private static final String[] texts = new String[_TOKENS];
- static {
- for (int i = 0; i < 255; i++) {
- texts[i] = String.valueOf(new char[] { (char)i });
- names[i] = texts[i];
- }
-
- texts[AND_EQ] = "&=";
- texts[ARROW] = "->";
- texts[DEC] = "--";
- texts[DIV_EQ] = "/=";
- texts[ELLIPSIS] = "...";
- texts[EQ] = "==";
- texts[GE] = ">=";
- texts[HASH] = "#";
- texts[INC] = "++";
- texts[LAND] = "&&";
- texts[LAND_EQ] = "&&=";
- texts[LE] = "<=";
- texts[LOR] = "||";
- texts[LOR_EQ] = "||=";
- texts[LSH] = "<<";
- texts[LSH_EQ] = "<<=";
- texts[MOD_EQ] = "%=";
- texts[MULT_EQ] = "*=";
- texts[NE] = "!=";
- texts[NL] = "\n";
- texts[OR_EQ] = "|=";
- /* We have to split the two hashes or Velocity eats them. */
- texts[PASTE] = "#" + "#";
- texts[PLUS_EQ] = "+=";
- texts[RANGE] = "..";
- texts[RSH] = ">>";
- texts[RSH_EQ] = ">>=";
- texts[SUB_EQ] = "-=";
- texts[XOR_EQ] = "^=";
-
-#foreach ($token in $tokens)
- names[$token] = "$token";
-#end
- }
-
-}
diff --git a/src/java/org/anarres/cpp/Argument.java b/src/main/java/org/anarres/cpp/Argument.java
index a868db5..a868db5 100644
--- a/src/java/org/anarres/cpp/Argument.java
+++ b/src/main/java/org/anarres/cpp/Argument.java
diff --git a/src/main/java/org/anarres/cpp/ChrootFileSystem.java b/src/main/java/org/anarres/cpp/ChrootFileSystem.java
new file mode 100644
index 0000000..1cec184
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/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 org.anarres.cpp;
+
+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/org/anarres/cpp/CppReader.java b/src/main/java/org/anarres/cpp/CppReader.java
new file mode 100644
index 0000000..7307f56
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/CppReader.java
@@ -0,0 +1,153 @@
+/*
+ * 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 org.anarres.cpp;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Reader;
+
+import static org.anarres.cpp.Token.*;
+
+/**
+ * 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 Preprocessor cpp;
+ private String token;
+ private int idx;
+
+ public CppReader(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(Preprocessor p) {
+ cpp = p;
+ token = "";
+ idx = 0;
+ }
+
+ /**
+ * Returns the Preprocessor used by this CppReader.
+ */
+ public Preprocessor getPreprocessor() {
+ return cpp;
+ }
+
+ /**
+ * Defines the given name as a macro.
+ *
+ * This is a convnience method.
+ */
+ public void addMacro(String name)
+ throws LexerException {
+ cpp.addMacro(name);
+ }
+
+ /**
+ * Defines the given name as a macro.
+ *
+ * This is a convnience method.
+ */
+ public void addMacro(String name, 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) {
+ /* Never happens.
+ if (e.getCause() instanceof IOException)
+ throw (IOException)e.getCause();
+ */
+ IOException ie = new IOException(String.valueOf(e));
+ ie.initCause(e);
+ throw ie;
+ }
+ }
+
+ @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 {
+ if (cpp != null) {
+ cpp.close();
+ cpp = null;
+ }
+ token = null;
+ }
+
+}
diff --git a/src/main/java/org/anarres/cpp/CppTask.java b/src/main/java/org/anarres/cpp/CppTask.java
new file mode 100644
index 0000000..5e17786
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/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 org.anarres.cpp;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.ArrayList;
+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 PreprocessorListener {
+
+ @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/java/org/anarres/cpp/Feature.java b/src/main/java/org/anarres/cpp/Feature.java
index 04a68b7..04a68b7 100644
--- a/src/java/org/anarres/cpp/Feature.java
+++ b/src/main/java/org/anarres/cpp/Feature.java
diff --git a/src/java/org/anarres/cpp/FileLexerSource.java b/src/main/java/org/anarres/cpp/FileLexerSource.java
index db5f9e0..db5f9e0 100644
--- a/src/java/org/anarres/cpp/FileLexerSource.java
+++ b/src/main/java/org/anarres/cpp/FileLexerSource.java
diff --git a/src/main/java/org/anarres/cpp/FixedTokenSource.java b/src/main/java/org/anarres/cpp/FixedTokenSource.java
new file mode 100644
index 0000000..a1c7500
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/FixedTokenSource.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.anarres.cpp;
+
+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/java/org/anarres/cpp/InputLexerSource.java b/src/main/java/org/anarres/cpp/InputLexerSource.java
index 0931dc4..3e1dcb3 100644
--- a/src/java/org/anarres/cpp/InputLexerSource.java
+++ b/src/main/java/org/anarres/cpp/InputLexerSource.java
@@ -14,7 +14,6 @@
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-
package org.anarres.cpp;
import java.io.BufferedReader;
@@ -22,11 +21,6 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
-import java.util.List;
-import java.util.Iterator;
-
-import static org.anarres.cpp.Token.*;
-
/**
* A {@link Source} which lexes a file.
*
@@ -35,34 +29,36 @@ import static org.anarres.cpp.Token.*;
* @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
- /* pp */ String getPath() {
- return "<standard-input>";
- }
+ /**
+ * 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
+ /* pp */ String getPath() {
+ return "<standard-input>";
+ }
- @Override
- /* pp */ String getName() {
- return "standard input";
- }
+ @Override
+ /* pp */ String getName() {
+ return "standard input";
+ }
- public String toString() {
- return getPath();
- }
+ @Override
+ public String toString() {
+ return getPath();
+ }
}
diff --git a/src/java/org/anarres/cpp/InternalException.java b/src/main/java/org/anarres/cpp/InternalException.java
index ac53017..ac53017 100644
--- a/src/java/org/anarres/cpp/InternalException.java
+++ b/src/main/java/org/anarres/cpp/InternalException.java
diff --git a/src/main/java/org/anarres/cpp/JavaFileSystem.java b/src/main/java/org/anarres/cpp/JavaFileSystem.java
new file mode 100644
index 0000000..a60271d
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/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 org.anarres.cpp;
+
+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/org/anarres/cpp/JoinReader.java b/src/main/java/org/anarres/cpp/JoinReader.java
new file mode 100644
index 0000000..0286577
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/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 org.anarres.cpp;
+
+import java.io.Closeable;
+import java.io.Reader;
+import java.io.IOException;
+
+/* 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/java/org/anarres/cpp/LexerException.java b/src/main/java/org/anarres/cpp/LexerException.java
index 41c6275..41c6275 100644
--- a/src/java/org/anarres/cpp/LexerException.java
+++ b/src/main/java/org/anarres/cpp/LexerException.java
diff --git a/src/main/java/org/anarres/cpp/LexerSource.java b/src/main/java/org/anarres/cpp/LexerSource.java
new file mode 100644
index 0000000..ca18314
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/LexerSource.java
@@ -0,0 +1,910 @@
+/*
+ * 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 org.anarres.cpp;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import javax.annotation.Nonnull;
+import static org.anarres.cpp.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();
+ text.append((char) d);
+ } while (d != '*');
+ do {
+ d = read();
+ 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("Nultiple 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("Nultiple 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("Nultiple 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("Nultiple length suffixes after " + text);
+ flags |= NumericValue.F_DOUBLE;
+ text.append((char) d);
+ d = read();
+ } // This should probably be isPunct() || isWhite().
+ else if (Character.isLetter(d) || d == '_') {
+ unread(d);
+ value.setFlags(flags);
+ return invalid(text,
+ "Invalid suffix \"" + (char) d
+ + "\" on numeric constant");
+ } 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)
+ throws IOException,
+ LexerException {
+ StringBuilder part = new StringBuilder();
+ int d = read();
+ while (Character.digit(d, base) != -1) {
+ text.append((char) d);
+ part.append((char) d);
+ d = read();
+ }
+ unread(d);
+ return part.toString();
+ }
+
+ /* We already chewed a zero, so empty is fine. */
+ @Nonnull
+ private Token number_octal()
+ throws IOException,
+ LexerException {
+ StringBuilder text = new StringBuilder("0");
+ String integer = _number_part(text, 8);
+ int d = read();
+ NumericValue value = new NumericValue(8, integer);
+ return _number_suffix(text, value, d);
+ }
+
+ /* 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);
+ NumericValue value = new NumericValue(16, integer);
+ int d = read();
+ if (d == '.') {
+ String fraction = _number_part(text, 16);
+ value.setFractionalPart(fraction);
+ d = read();
+ }
+ if (d == 'P' || d == 'p') {
+ String exponent = _number_part(text, 10);
+ value.setExponent(exponent);
+ d = read();
+ }
+ // XXX Make sure it's got enough parts
+ return _number_suffix(text, value, d);
+ }
+
+ /* 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);
+ NumericValue value = new NumericValue(10, integer);
+ int d = read();
+ if (d == '.') {
+ String fraction = _number_part(text, 10);
+ value.setFractionalPart(fraction);
+ d = read();
+ }
+ if (d == 'E' || d == 'e') {
+ String exponent = _number_part(text, 10);
+ value.setExponent(exponent);
+ d = read();
+ }
+ // XXX Make sure it's got enough parts
+ return _number_suffix(text, value, d);
+ }
+
+ @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_decimal();
+ }
+ /* XXX decimal fraction */
+ break;
+
+ case '0':
+ /* octal or hex */
+ d = read();
+ if (d == 'x' || d == 'X')
+ tok = number_hex((char) d);
+ else {
+ unread(d);
+ tok = number_octal();
+ }
+ 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_decimal();
+ } 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;
+ }
+
+ public void close()
+ throws IOException {
+ if (reader != null) {
+ reader.close();
+ reader = null;
+ }
+ super.close();
+ }
+
+}
diff --git a/src/java/org/anarres/cpp/Macro.java b/src/main/java/org/anarres/cpp/Macro.java
index ca024bc..534cb2b 100644
--- a/src/java/org/anarres/cpp/Macro.java
+++ b/src/main/java/org/anarres/cpp/Macro.java
@@ -165,6 +165,7 @@ public class Macro {
return buf.toString();
}
+ @Override
public String toString() {
StringBuilder buf = new StringBuilder(name);
if (args != null) {
diff --git a/src/main/java/org/anarres/cpp/MacroTokenSource.java b/src/main/java/org/anarres/cpp/MacroTokenSource.java
new file mode 100644
index 0000000..516e4f2
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/MacroTokenSource.java
@@ -0,0 +1,203 @@
+/*
+ * 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 org.anarres.cpp;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.anarres.cpp.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(Macro m, List<Argument> args) {
+ this.macro = m;
+ this.tokens = m.getTokens().iterator();
+ this.args = args;
+ this.arg = null;
+ }
+
+ @Override
+ /* pp */ boolean isExpanding(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(StringBuilder buf, CharSequence cs) {
+ 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(StringBuilder buf, Argument arg) {
+ Iterator<Token> it = arg.iterator();
+ while (it.hasNext()) {
+ Token tok = it.next();
+ buf.append(tok.getText());
+ }
+ }
+
+ private Token stringify(Token pos, 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(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);
+ }
+
+ 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/org/anarres/cpp/Main.java b/src/main/java/org/anarres/cpp/Main.java
new file mode 100644
index 0000000..1902798
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/Main.java
@@ -0,0 +1,320 @@
+/*
+ * 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 org.anarres.cpp;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.EnumSet;
+import gnu.getopt.Getopt;
+import gnu.getopt.LongOpt;
+
+/**
+ * (Currently a simple test class).
+ */
+public class Main {
+
+ private static class Option extends LongOpt {
+
+ private String eg;
+ private String help;
+
+ public Option(String word, int arg, int ch,
+ String eg, String help) {
+ super(word, arg, null, ch);
+ this.eg = eg;
+ this.help = help;
+ }
+ }
+
+ private static final Option[] OPTS = new Option[]{
+ new Option("help", LongOpt.NO_ARGUMENT, 'h', null,
+ "Displays help and usage information."),
+ new Option("define", LongOpt.REQUIRED_ARGUMENT, 'D', "name=definition",
+ "Defines the given macro."),
+ new Option("undefine", LongOpt.REQUIRED_ARGUMENT, 'U', "name",
+ "Undefines the given macro, previously either builtin or defined using -D."),
+ new Option("include", LongOpt.REQUIRED_ARGUMENT, 1, "file",
+ "Process file as if \"#" + "include \"file\"\" appeared as the first line of the primary source file."),
+ new Option("incdir", LongOpt.REQUIRED_ARGUMENT, 'I', "dir",
+ "Adds the directory dir to the list of directories to be searched for header files."),
+ new Option("iquote", LongOpt.REQUIRED_ARGUMENT, 0, "dir",
+ "Adds the directory dir to the list of directories to be searched for header files included using \"\"."),
+ new Option("warning", LongOpt.REQUIRED_ARGUMENT, 'W', "type",
+ "Enables the named warning class (" + getWarnings() + ")."),
+ new Option("no-warnings", LongOpt.NO_ARGUMENT, 'w', null,
+ "Disables ALL warnings."),
+ new Option("verbose", LongOpt.NO_ARGUMENT, 'v', null,
+ "Operates incredibly verbosely."),
+ new Option("debug", LongOpt.NO_ARGUMENT, 3, null,
+ "Operates incredibly verbosely."),
+ new Option("version", LongOpt.NO_ARGUMENT, 2, null,
+ "Prints jcpp's version number (" + Version.getVersion() + ")"),};
+
+ 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 {
+ Option[] opts = OPTS;
+ String sopts = getShortOpts(opts);
+ Getopt g = new Getopt("jcpp", args, sopts, opts);
+ int c;
+ String arg;
+ int idx;
+
+ Preprocessor pp = new Preprocessor();
+ pp.addFeature(Feature.DIGRAPHS);
+ pp.addFeature(Feature.TRIGRAPHS);
+ pp.addFeature(Feature.LINEMARKERS);
+ pp.addWarning(Warning.IMPORT);
+ pp.setListener(new PreprocessorListener());
+ 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");
+
+ GETOPT:
+ while ((c = g.getopt()) != -1) {
+ switch (c) {
+ case 'D':
+ arg = g.getOptarg();
+ idx = arg.indexOf('=');
+ if (idx == -1)
+ pp.addMacro(arg);
+ else
+ pp.addMacro(arg.substring(0, idx),
+ arg.substring(idx + 1));
+ break;
+ case 'U':
+ pp.getMacros().remove(g.getOptarg());
+ break;
+ case 'I':
+ pp.getSystemIncludePath().add(g.getOptarg());
+ break;
+ case 0: // --iquote=
+ pp.getQuoteIncludePath().add(g.getOptarg());
+ break;
+ case 'W':
+ arg = g.getOptarg().toUpperCase();
+ arg = arg.replace('-', '_');
+ if (arg.equals("ALL"))
+ pp.addWarnings(EnumSet.allOf(Warning.class));
+ else
+ pp.addWarning(Enum.valueOf(Warning.class, arg));
+ break;
+ case 'w':
+ pp.getWarnings().clear();
+ break;
+ case 1: // --include=
+ // pp.addInput(new File(g.getOptarg()));
+ // Comply exactly with spec.
+ pp.addInput(new StringLexerSource(
+ "#" + "include \"" + g.getOptarg() + "\"\n"
+ ));
+ break;
+ case 2: // --version
+ version(System.out);
+ return;
+ case 'v':
+ pp.addFeature(Feature.VERBOSE);
+ break;
+ case 3:
+ pp.addFeature(Feature.DEBUG);
+ break;
+ case 'h':
+ usage(getClass().getName(), opts);
+ return;
+ default:
+ throw new Exception("Illegal option " + (char) c);
+ case '?':
+ continue; /* Make failure-proof. */
+
+ }
+ }
+
+ for (int i = g.getOptind(); i < args.length; i++)
+ pp.addInput(new FileLexerSource(new File(args[i])));
+ if (g.getOptind() == args.length)
+ pp.addInput(new InputLexerSource(System.in));
+
+ if (pp.getFeature(Feature.VERBOSE)) {
+ System.err.println("#" + "include \"...\" search starts here:");
+ for (String dir : pp.getQuoteIncludePath())
+ System.err.println(" " + dir);
+ System.err.println("#" + "include <...> search starts here:");
+ for (String dir : pp.getSystemIncludePath())
+ System.err.println(" " + dir);
+ System.err.println("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) {
+ e.printStackTrace(System.err);
+ Source s = pp.getSource();
+ while (s != null) {
+ System.err.println(" -> " + s);
+ s = s.getParent();
+ }
+ }
+
+ }
+
+ private void version(PrintStream out) {
+ out.println("Anarres Java C Preprocessor version " + Version.getVersion());
+ 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.");
+ }
+
+ private static String getShortOpts(Option[] opts)
+ throws Exception {
+ StringBuilder buf = new StringBuilder();
+ for (int i = 0; i < opts.length; i++) {
+ char c = (char) opts[i].getVal();
+ if (!Character.isLetterOrDigit(c))
+ continue;
+ for (int j = 0; j < buf.length(); j++)
+ if (buf.charAt(j) == c)
+ throw new Exception(
+ "Duplicate short option " + c
+ );
+ buf.append(c);
+ switch (opts[i].getHasArg()) {
+ case LongOpt.NO_ARGUMENT:
+ break;
+ case LongOpt.OPTIONAL_ARGUMENT:
+ buf.append("::");
+ break;
+ case LongOpt.REQUIRED_ARGUMENT:
+ buf.append(":");
+ break;
+ }
+ }
+ return buf.toString();
+ }
+
+ /* This is incomplete but nearly there. */
+ /**
+ * Wraps a string.
+ *
+ * The output form is:
+ * <pre>
+ * prefix in[0]
+ * &lt;--indent-&gt; in[1]
+ * &lt;--indent-&gt; in[2]
+ * &lt;-----width----&gt;
+ * </pre>
+ */
+ /* XXX There's some of this in commons. */
+ private static String wrap(String in, String prefix,
+ int indent, int width) {
+ StringBuilder buf = new StringBuilder(prefix);
+
+ while (buf.length() < indent)
+ buf.append(' ');
+
+ int start = 0;
+
+ while (start < in.length()) {
+ while (start < in.length()
+ && Character.isWhitespace(in.charAt(start)))
+ start++;
+
+ int end = start + width - indent;
+
+ if (end > in.length()) {
+ buf.append(in.substring(start));
+ break;
+ }
+
+ int idx = end;
+ while (!Character.isWhitespace(in.charAt(idx)))
+ idx--;
+
+ if (idx == start) {
+ idx = end - 1;
+ buf.append(in.substring(start, idx));
+ buf.append('-');
+ } else {
+ buf.append(in.substring(start, idx));
+ start = idx;
+ }
+
+ start = idx;
+ }
+
+ return buf.toString();
+ }
+
+ private static void usage(String command, Option[] options) {
+ StringBuilder text = new StringBuilder("Usage: ");
+ text.append(command).append('\n');
+ for (int i = 0; i < options.length; i++) {
+ StringBuilder line = new StringBuilder();
+ Option opt = options[i];
+ line.append(" --").append(opt.getName());
+ switch (opt.getHasArg()) {
+ case LongOpt.NO_ARGUMENT:
+ break;
+ case LongOpt.OPTIONAL_ARGUMENT:
+ line.append("[=").append(opt.eg).append(']');
+ break;
+ case LongOpt.REQUIRED_ARGUMENT:
+ line.append('=').append(opt.eg);
+ break;
+ }
+ if (Character.isLetterOrDigit(opt.getVal()))
+ line.append(" (-").append((char) opt.getVal()).append(")");
+ if (line.length() < 30) {
+ while (line.length() < 30)
+ line.append(' ');
+ } else {
+ line.append('\n');
+ for (int j = 0; j < 30; j++)
+ line.append(' ');
+ }
+ /* This should use wrap. */
+ line.append(opt.help);
+ line.append('\n');
+ text.append(line);
+ }
+
+ System.out.println(text);
+ }
+
+}
diff --git a/src/main/java/org/anarres/cpp/NumericValue.java b/src/main/java/org/anarres/cpp/NumericValue.java
new file mode 100644
index 0000000..8d961c4
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/NumericValue.java
@@ -0,0 +1,179 @@
+/*
+ * 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 org.anarres.cpp;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+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 String exponent;
+ private int flags;
+
+ public NumericValue(int base, String integer) {
+ this.base = base;
+ this.integer = integer;
+ }
+
+ public int getBase() {
+ return base;
+ }
+
+ public String getIntegerPart() {
+ return integer;
+ }
+
+ public String getFractionalPart() {
+ return fraction;
+ }
+
+ /* pp */ void setFractionalPart(String fraction) {
+ this.fraction = fraction;
+ }
+
+ public String getExponent() {
+ return exponent;
+ }
+
+ /* pp */ void setExponent(String exponent) {
+ 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.
+ */
+ 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 += getFractionalPart().length();
+ }
+ if (getExponent() != null)
+ scale -= Integer.parseInt(getExponent());
+ BigInteger unscaled = new BigInteger(text, getBase());
+ return new BigDecimal(unscaled, scale);
+ }
+
+ 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();
+ }
+
+ @Override
+ public int intValue() {
+ return Integer.parseInt(toString());
+ }
+
+ @Override
+ public long longValue() {
+ return Long.parseLong(toString());
+ }
+
+ @Override
+ public float floatValue() {
+ return Float.parseFloat(toString());
+ }
+
+ @Override
+ public double doubleValue() {
+ 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/org/anarres/cpp/Preprocessor.java b/src/main/java/org/anarres/cpp/Preprocessor.java
new file mode 100644
index 0000000..35480b2
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/Preprocessor.java
@@ -0,0 +1,2016 @@
+/*
+ * 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 org.anarres.cpp;
+
+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.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import javax.annotation.Nonnull;
+import static org.anarres.cpp.Token.*;
+import static org.anarres.cpp.PreprocessorCommand.*;
+
+/**
+ * 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 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 List<Source> inputs;
+
+ /* The fundamental engine. */
+ private Map<String, Macro> macros;
+ private Stack<State> states;
+ private Source source;
+
+ /* Miscellaneous support. */
+ private int counter;
+
+ /* Support junk to make it work like cpp */
+ private List<String> quoteincludepath; /* -iquote */
+
+ private List<String> sysincludepath; /* -I */
+
+ private List<String> frameworkspath;
+ private Set<Feature> features;
+ private 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(Source initial) {
+ this();
+ addInput(initial);
+ }
+
+ /** Equivalent to
+ * 'new Preprocessor(new {@link FileLexerSource}(file))'
+ */
+ public Preprocessor(File file)
+ throws IOException {
+ this(new FileLexerSource(file));
+ }
+
+ /**
+ * Sets the VirtualFileSystem used by this Preprocessor.
+ */
+ public void setFileSystem(VirtualFileSystem filesystem) {
+ this.filesystem = filesystem;
+ }
+
+ /**
+ * Returns the VirtualFileSystem used by this Preprocessor.
+ */
+ 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(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.
+ */
+ public PreprocessorListener getListener() {
+ return listener;
+ }
+
+ /**
+ * Returns the feature-set for this Preprocessor.
+ *
+ * This set may be freely modified by user code.
+ */
+ public Set<Feature> getFeatures() {
+ return features;
+ }
+
+ /**
+ * Adds a feature to the feature-set of this Preprocessor.
+ */
+ public void addFeature(Feature f) {
+ features.add(f);
+ }
+
+ /**
+ * Adds features to the feature-set of this Preprocessor.
+ */
+ public void addFeatures(Collection<Feature> f) {
+ features.addAll(f);
+ }
+
+ /**
+ * Returns true if the given feature is in
+ * the feature-set of this Preprocessor.
+ */
+ public boolean getFeature(Feature f) {
+ return features.contains(f);
+ }
+
+ /**
+ * Returns the warning-set for this Preprocessor.
+ *
+ * This set may be freely modified by user code.
+ */
+ public Set<Warning> getWarnings() {
+ return warnings;
+ }
+
+ /**
+ * Adds a warning to the warning-set of this Preprocessor.
+ */
+ public void addWarning(Warning w) {
+ warnings.add(w);
+ }
+
+ /**
+ * Adds warnings to the warning-set of this Preprocessor.
+ */
+ public void addWarnings(Collection<Warning> w) {
+ warnings.addAll(w);
+ }
+
+ /**
+ * Returns true if the given warning is in
+ * the warning-set of this Preprocessor.
+ */
+ public boolean getWarning(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(Source source) {
+ source.init(this);
+ inputs.add(source);
+ }
+
+ /**
+ * Adds input for the Preprocessor.
+ *
+ * @see #addInput(Source)
+ */
+ public void addInput(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(int line, int column, 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(Token tok, 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(int line, int column, 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(Token tok, 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.
+ */
+ public void addMacro(Macro m) throws LexerException {
+ // System.out.println("Macro " + m);
+ String name = m.getName();
+ /* Already handled as a source error in macro(). */
+ if ("defined".equals(name))
+ throw new LexerException("Cannot redefine name 'defined'");
+ 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(String name, String value)
+ throws LexerException {
+ try {
+ Macro m = new Macro(name);
+ StringLexerSource s = new StringLexerSource(value);
+ for (;;) {
+ Token tok = s.token();
+ if (tok.getType() == EOF)
+ break;
+ m.addToken(tok);
+ }
+ addMacro(m);
+ } catch (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(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(List<String> path) {
+ this.quoteincludepath = path;
+ }
+
+ /**
+ * Returns the user include-path of this Preprocessor.
+ *
+ * This list may be freely modified by user code.
+ */
+ public List<String> getQuoteIncludePath() {
+ return quoteincludepath;
+ }
+
+ /**
+ * Sets the system include path used by this Preprocessor.
+ */
+ /* Note for future: Create an IncludeHandler? */
+ public void setSystemIncludePath(List<String> path) {
+ this.sysincludepath = path;
+ }
+
+ /**
+ * Returns the system include-path of this Preprocessor.
+ *
+ * This list may be freely modified by user code.
+ */
+ 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(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.
+ */
+ public List<String> getFrameworksPath() {
+ return frameworkspath;
+ }
+
+ /**
+ * Returns the Map of Macros parsed during the run of this
+ * Preprocessor.
+ */
+ 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.
+ */
+ public Macro getMacro(String name) {
+ return macros.get(name);
+ }
+
+ /* States */
+ private void push_state() {
+ State top = states.peek();
+ states.push(new State(top));
+ }
+
+ private void pop_state()
+ throws LexerException {
+ State s = states.pop();
+ if (states.isEmpty()) {
+ error(0, 0, "#" + "endif without #" + "if");
+ states.push(s);
+ }
+ }
+
+ private boolean isActive() {
+ 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()
+ */
+ protected Source getSource() {
+ return source;
+ }
+
+ /**
+ * Pushes a Source onto the input stack.
+ *
+ * @see #getSource()
+ * @see #pop_source()
+ */
+ protected void push_source(Source source, boolean autopop) {
+ source.init(this);
+ source.setParent(this.source, autopop);
+ // source.setListener(listener);
+ if (listener != null)
+ listener.handleSourceChange(this.source, "suspend");
+ this.source = source;
+ if (listener != null)
+ listener.handleSourceChange(this.source, "push");
+ }
+
+ /**
+ * Pops a Source from the input stack.
+ *
+ * @see #getSource()
+ * @see #push_source(Source,boolean)
+ */
+ protected void pop_source()
+ throws IOException {
+ if (listener != null)
+ listener.handleSourceChange(this.source, "pop");
+ 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, "resume");
+ }
+
+
+ /* Source tokens */
+ private Token source_token;
+
+ /* XXX Make this include the NL, and make all cpp directives eat
+ * their own NL. */
+ private Token line_token(int line, String name, String extra) {
+ StringBuilder buf = new StringBuilder();
+ buf.append("#line ").append(line)
+ .append(" \"");
+ /* XXX This call to escape(name) is correct but ugly. */
+ MacroTokenSource.escape(buf, name);
+ buf.append("\"").append(extra).append("\n");
+ return new Token(P_LINE, line, 0, buf.toString(), null);
+ }
+
+ private Token source_token()
+ throws IOException,
+ LexerException {
+ if (source_token != null) {
+ Token tok = source_token;
+ source_token = null;
+ if (getFeature(Feature.DEBUG))
+ System.err.println("Returning unget token " + tok);
+ return tok;
+ }
+
+ for (;;) {
+ Source s = getSource();
+ if (s == null) {
+ if (inputs.isEmpty())
+ return new Token(EOF);
+ Source t = inputs.remove(0);
+ push_source(t, true);
+ if (getFeature(Feature.LINEMARKERS))
+ return line_token(t.getLine(), t.getName(), " 1");
+ continue;
+ }
+ Token tok = s.token();
+ /* XXX Refactor with skipline() */
+ if (tok.getType() == EOF && s.isAutopop()) {
+ // System.out.println("Autopop " + s);
+ pop_source();
+ 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. */
+ return line_token(t.getLine() + 1, t.getName(), " 2");
+ }
+ continue;
+ }
+ if (getFeature(Feature.DEBUG))
+ System.err.println("Returning fresh token " + tok);
+ return tok;
+ }
+ }
+
+ private void source_untoken(Token tok) {
+ if (this.source_token != null)
+ throw new IllegalStateException("Cannot return two tokens");
+ this.source_token = tok;
+ }
+
+ private boolean isWhite(Token tok) {
+ 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(boolean white)
+ throws IOException,
+ LexerException {
+ // (new Exception("skipping line")).printStackTrace(System.out);
+ Source s = getSource();
+ Token tok = s.skipline(white);
+ /* XXX Refactor with source_token() */
+ if (tok.getType() == EOF && s.isAutopop()) {
+ // System.out.println("Autopop " + s);
+ pop_source();
+ 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. */
+ return line_token(t.getLine() + 1, t.getName(), " 2");
+ }
+ }
+ return tok;
+ }
+
+ /* processes and expands a macro. */
+ private boolean macro(Macro m, 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:
+ /* 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()) {
+ 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 (Argument a : args)
+ a.expand(this);
+ */
+ for (int i = 0; i < args.size(); i++) {
+ args.get(i).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(),
+ String.valueOf(orig.getLine()),
+ new NumericValue(10, "" + orig.getLine()))}
+ ), true);
+ } else if (m == __FILE__) {
+ StringBuilder buf = new StringBuilder("\"");
+ String name = getSource().getName();
+ if (name == null)
+ name = "<no file>";
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+ switch (c) {
+ case '\\':
+ buf.append("\\\\");
+ break;
+ case '"':
+ buf.append("\\\"");
+ break;
+ default:
+ buf.append(c);
+ break;
+ }
+ }
+ buf.append("\"");
+ 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(). */
+ int value = this.counter++;
+ push_source(new FixedTokenSource(
+ new Token[]{new Token(NUMBER,
+ orig.getLine(), orig.getColumn(),
+ String.valueOf(value),
+ new NumericValue(10, "" + 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. */
+ /* pp */ List<Token> expand(List<Token> arg)
+ throws IOException,
+ LexerException {
+ List<Token> expansion = new ArrayList<Token>();
+ boolean space = false;
+
+ push_source(new FixedTokenSource(arg), false);
+
+ EXPANSION:
+ for (;;) {
+ 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;
+ }
+ }
+
+ pop_source();
+
+ 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 */
+
+ String name = tok.getText();
+ if ("defined".equals(name)) {
+ error(tok, "Cannot redefine name 'defined'");
+ return source_skipline(false);
+ }
+
+ 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 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;
+ 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))
+ System.err.println("Defined macro " + m);
+ addMacro(m);
+
+ return tok; /* NL or EOF. */
+
+ }
+
+ private Token undef()
+ throws IOException,
+ LexerException {
+ 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 {
+ Macro m = macros.get(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.
+ */
+ private boolean include(VirtualFile file)
+ throws IOException,
+ LexerException {
+ // System.out.println("Try to include " + ((File)file).getAbsolutePath());
+ if (!file.isFile())
+ return false;
+ if (getFeature(Feature.DEBUG))
+ System.err.println("pp: including " + file);
+ push_source(file.getSource(), true);
+ return true;
+ }
+
+ /**
+ * Includes a file from an include path, by name.
+ */
+ private boolean include(Iterable<String> path, String name)
+ throws IOException,
+ LexerException {
+ for (String dir : path) {
+ VirtualFile file = filesystem.getFile(dir, name);
+ if (include(file))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handles an include directive.
+ */
+ private void include(String parent, int line,
+ String name, boolean quoted)
+ throws IOException,
+ LexerException {
+ VirtualFile pdir = null;
+ if (quoted) {
+ VirtualFile pfile = filesystem.getFile(parent);
+ pdir = pfile.getParentFile();
+ VirtualFile ifile = pdir.getChildFile(name);
+ if (include(ifile))
+ return;
+ if (include(quoteincludepath, name))
+ return;
+ }
+
+ if (include(sysincludepath, name))
+ return;
+
+ StringBuilder buf = new StringBuilder();
+ buf.append("File not found: ").append(name);
+ buf.append(" in");
+ if (quoted) {
+ buf.append(" .").append('(').append(pdir).append(')');
+ for (String dir : quoteincludepath)
+ buf.append(" ").append(dir);
+ }
+ for (String dir : sysincludepath)
+ buf.append(" ").append(dir);
+ error(line, 0, buf.toString());
+ }
+
+ private Token include(boolean next)
+ throws IOException,
+ LexerException {
+ 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. */
+ 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);
+
+ /* '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(Token name, List<Token> value)
+ throws IOException,
+ LexerException {
+ warning(name, "Unknown #" + "pragma: " + name.getText());
+ }
+
+ private Token pragma()
+ throws IOException,
+ LexerException {
+ Token name;
+
+ NAME:
+ for (;;) {
+ 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;
+ 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(Token pptok, boolean is_error)
+ throws IOException,
+ LexerException {
+ 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. */
+ private Token expanded_token()
+ throws IOException,
+ LexerException {
+ for (;;) {
+ Token tok = source_token();
+ // System.out.println("Source token is " + tok);
+ if (tok.getType() == IDENTIFIER) {
+ Macro m = macros.get(tok.getText());
+ if (m == null)
+ return tok;
+ if (source.isExpanding(m))
+ return tok;
+ if (macro(m, tok))
+ continue;
+ }
+ return tok;
+ }
+ }
+
+ 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;
+ }
+
+ private Token expr_token = null;
+
+ 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()");
+ }
+ }
+ }
+ }
+
+ // System.out.println("expr_token returns " + tok);
+ return tok;
+ }
+
+ private void expr_untoken(Token tok)
+ throws LexerException {
+ if (expr_token != null)
+ throw new InternalException(
+ "Cannot unget two expression tokens."
+ );
+ expr_token = tok;
+ }
+
+ private int expr_priority(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(int priority)
+ throws IOException,
+ LexerException {
+ /*
+ System.out.flush();
+ (new Exception("expr(" + priority + ") called")).printStackTrace();
+ System.err.flush();
+ */
+
+ 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");
+ return 0;
+ }
+ break;
+
+ case '~':
+ lhs = ~expr(11);
+ break;
+ case '!':
+ lhs = expr(11) == 0 ? 1 : 0;
+ break;
+ case '-':
+ lhs = -expr(11);
+ break;
+ case NUMBER:
+ NumericValue value = (NumericValue) tok.getValue();
+ lhs = value.longValue();
+ break;
+ case CHARACTER:
+ lhs = (long) ((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);
+ Token op = expr_token();
+ 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 '?':
+ /* XXX Handle this? */
+
+ default:
+ error(op,
+ "Unexpected operator " + op.getText());
+ return 0;
+
+ }
+ }
+
+ /*
+ System.out.flush();
+ (new Exception("expr returning " + lhs)).printStackTrace();
+ System.err.flush();
+ */
+ // System.out.println("expr returning " + lhs);
+ return lhs;
+ }
+
+ private Token toWhitespace(Token tok) {
+ String text = tok.getText();
+ int len = text.length();
+ boolean cr = false;
+ int nls = 0;
+
+ for (int i = 0; i < len; i++) {
+ 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;
+ }
+ }
+
+ 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()) {
+ try {
+ /* XXX Tell lexer to ignore warnings. */
+ source.setActive(false);
+ tok = source_token();
+ } finally {
+ /* XXX Tell lexer to stop ignoring warnings. */
+ source.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 XOR_EQ:
+ return tok;
+
+ case NUMBER:
+ return tok;
+
+ case IDENTIFIER:
+ Macro m = macros.get(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);
+ }
+ 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 {
+ String text = tok.getText();
+ 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 {
+ String text = tok.getText();
+ 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);
+ }
+
+ }
+ }
+ }
+
+ 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 {
+ Token tok = _token();
+ if (getFeature(Feature.DEBUG))
+ System.err.println("pp: Returning " + tok);
+ return tok;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+
+ Source s = getSource();
+ while (s != null) {
+ buf.append(" -> ").append(String.valueOf(s)).append("\n");
+ s = s.getParent();
+ }
+
+ Map<String, Macro> macros = getMacros();
+ List<String> keys = new ArrayList<String>(
+ macros.keySet()
+ );
+ Collections.sort(keys);
+ Iterator<String> mt = keys.iterator();
+ while (mt.hasNext()) {
+ String key = mt.next();
+ Macro macro = macros.get(key);
+ 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 (Source s : inputs) {
+ s.close();
+ }
+ }
+
+}
diff --git a/src/main/java/org/anarres/cpp/PreprocessorCommand.java b/src/main/java/org/anarres/cpp/PreprocessorCommand.java
new file mode 100644
index 0000000..3938360
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/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 org.anarres.cpp;
+
+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/org/anarres/cpp/PreprocessorListener.java b/src/main/java/org/anarres/cpp/PreprocessorListener.java
new file mode 100644
index 0000000..a5b4339
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/PreprocessorListener.java
@@ -0,0 +1,85 @@
+/*
+ * 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 org.anarres.cpp;
+
+/**
+ * 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 PreprocessorListener {
+
+ private int errors;
+ private int warnings;
+
+ public PreprocessorListener() {
+ clear();
+ }
+
+ public void clear() {
+ errors = 0;
+ warnings = 0;
+ }
+
+ public int getErrors() {
+ return errors;
+ }
+
+ public int getWarnings() {
+ return warnings;
+ }
+
+ protected void print(String msg) {
+ System.err.println(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.
+ */
+ public void handleWarning(Source source, int line, int column,
+ 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.
+ */
+ public void handleError(Source source, int line, int column,
+ String msg)
+ throws LexerException {
+ errors++;
+ print(source.getName() + ":" + line + ":" + column
+ + ": error: " + msg);
+ }
+
+ public void handleSourceChange(Source source, String event) {
+ }
+
+}
diff --git a/src/main/java/org/anarres/cpp/ResourceFileSystem.java b/src/main/java/org/anarres/cpp/ResourceFileSystem.java
new file mode 100644
index 0000000..7efd664
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/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 org.anarres.cpp;
+
+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/org/anarres/cpp/Source.java b/src/main/java/org/anarres/cpp/Source.java
new file mode 100644
index 0000000..532f5fc
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/Source.java
@@ -0,0 +1,287 @@
+/*
+ * 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 org.anarres.cpp;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Iterator;
+import javax.annotation.Nonnull;
+import static org.anarres.cpp.Token.*;
+
+/**
+ * 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(Source parent, boolean autopop) {
+ this.parent = parent;
+ this.autopop = autopop;
+ }
+
+ /**
+ * Returns the parent source of this source.
+ *
+ * Sources form a singly linked list.
+ */
+ /* pp */ final Source getParent() {
+ return parent;
+ }
+
+
+ // @OverrideMustInvoke
+ /* pp */ void init(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(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.
+ */
+ /* pp */ String getPath() {
+ Source parent = getParent();
+ if (parent != null)
+ return parent.getPath();
+ return null;
+ }
+
+ /**
+ * Returns the human-readable name of the current Source.
+ */
+ /* pp */ String getName() {
+ Source parent = getParent();
+ if (parent != null)
+ return parent.getName();
+ return null;
+ }
+
+ /**
+ * Returns the current line number within this Source.
+ */
+ public int getLine() {
+ Source parent = getParent();
+ if (parent == null)
+ return 0;
+ return parent.getLine();
+ }
+
+ /**
+ * Returns the current column number within this Source.
+ */
+ public int getColumn() {
+ 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(Macro m) {
+ 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(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(boolean white)
+ throws IOException,
+ LexerException {
+ for (;;) {
+ 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(int line, int column, 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(int line, int column, 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/java/org/anarres/cpp/SourceIterator.java b/src/main/java/org/anarres/cpp/SourceIterator.java
index d5c63c7..d5c63c7 100644
--- a/src/java/org/anarres/cpp/SourceIterator.java
+++ b/src/main/java/org/anarres/cpp/SourceIterator.java
diff --git a/src/java/org/anarres/cpp/State.java b/src/main/java/org/anarres/cpp/State.java
index ed5c736..ed5c736 100644
--- a/src/java/org/anarres/cpp/State.java
+++ b/src/main/java/org/anarres/cpp/State.java
diff --git a/src/java/org/anarres/cpp/StringLexerSource.java b/src/main/java/org/anarres/cpp/StringLexerSource.java
index 2285097..8640bc8 100644
--- a/src/java/org/anarres/cpp/StringLexerSource.java
+++ b/src/main/java/org/anarres/cpp/StringLexerSource.java
@@ -14,18 +14,10 @@
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-
package org.anarres.cpp;
-import java.io.IOException;
-import java.io.Reader;
import java.io.StringReader;
-import java.util.List;
-import java.util.Iterator;
-
-import static org.anarres.cpp.Token.*;
-
/**
* A Source for lexing a String.
*
@@ -34,27 +26,28 @@ import static org.anarres.cpp.Token.*;
*/
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);
- }
-
- public String toString() {
- return "string literal";
- }
+ /**
+ * 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/org/anarres/cpp/Token.java b/src/main/java/org/anarres/cpp/Token.java
new file mode 100644
index 0000000..3e6eb3e
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/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 org.anarres.cpp;
+
+/**
+ * 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/java/org/anarres/cpp/TokenSnifferSource.java b/src/main/java/org/anarres/cpp/TokenSnifferSource.java
index 1512b2e..1512b2e 100644
--- a/src/java/org/anarres/cpp/TokenSnifferSource.java
+++ b/src/main/java/org/anarres/cpp/TokenSnifferSource.java
diff --git a/src/main/java/org/anarres/cpp/TokenType.java b/src/main/java/org/anarres/cpp/TokenType.java
new file mode 100644
index 0000000..86df097
--- /dev/null
+++ b/src/main/java/org/anarres/cpp/TokenType.java
@@ -0,0 +1,128 @@
+/*
+ * 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 org.anarres.cpp;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnegative;
+import javax.annotation.Nonnull;
+import static org.anarres.cpp.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/java/org/anarres/cpp/VirtualFile.java b/src/main/java/org/anarres/cpp/VirtualFile.java
index 405891a..aee1cad 100644
--- a/src/java/org/anarres/cpp/VirtualFile.java
+++ b/src/main/java/org/anarres/cpp/VirtualFile.java
@@ -14,20 +14,32 @@
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-
package org.anarres.cpp;
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();
- public String getPath();
- public String getName();
- public VirtualFile getParentFile();
- public VirtualFile getChildFile(String name);
- public Source getSource() throws IOException;
+
+ // 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/java/org/anarres/cpp/VirtualFileSystem.java b/src/main/java/org/anarres/cpp/VirtualFileSystem.java
index eb5c4a1..56e53ac 100644
--- a/src/java/org/anarres/cpp/VirtualFileSystem.java
+++ b/src/main/java/org/anarres/cpp/VirtualFileSystem.java
@@ -14,13 +14,14 @@
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-
package org.anarres.cpp;
/**
* An extremely lightweight virtual file system interface.
*/
public interface VirtualFileSystem {
- public VirtualFile getFile(String path);
- public VirtualFile getFile(String dir, String name);
+
+ public VirtualFile getFile(String path);
+
+ public VirtualFile getFile(String dir, String name);
}
diff --git a/src/java/org/anarres/cpp/Warning.java b/src/main/java/org/anarres/cpp/Warning.java
index abe38d0..da2b2c6 100644
--- a/src/java/org/anarres/cpp/Warning.java
+++ b/src/main/java/org/anarres/cpp/Warning.java
@@ -14,19 +14,19 @@
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-
package org.anarres.cpp;
/**
* Warning classes which may optionally be emitted by the Preprocessor.
*/
public enum Warning {
- TRIGRAPHS,
- // TRADITIONAL,
- IMPORT,
- UNDEF,
- UNUSED_MACROS,
- ENDIF_LABELS,
- ERROR,
+
+ TRIGRAPHS,
+ // TRADITIONAL,
+ IMPORT,
+ UNDEF,
+ UNUSED_MACROS,
+ ENDIF_LABELS,
+ ERROR,
// SYSTEM_HEADERS
}
diff --git a/src/resources/log4j.properties b/src/main/resources/log4j.properties
index 901854c..901854c 100644
--- a/src/resources/log4j.properties
+++ b/src/main/resources/log4j.properties
diff --git a/src/resources/org/anarres/cpp/taskdef.properties b/src/main/resources/org/anarres/cpp/taskdef.properties
index 6d575d7..6d575d7 100644
--- a/src/resources/org/anarres/cpp/taskdef.properties
+++ b/src/main/resources/org/anarres/cpp/taskdef.properties
diff --git a/src/java/org/anarres/cpp/Version.java b/src/main/velocity/org/anarres/cpp/Version.java
index 3cc9e47..62494b5 100644
--- a/src/java/org/anarres/cpp/Version.java
+++ b/src/main/velocity/org/anarres/cpp/Version.java
@@ -17,6 +17,8 @@
package org.anarres.cpp;
+import javax.annotation.Nonnull;
+
/**
* System version metadata for Anarres Java C Preprocessor ${version}.
*
@@ -30,17 +32,18 @@ public class Version {
private static final String VERSION = "${version}";
- private static final int major;
- private static final int minor;
- private static final int patch;
+ private static final int major;
+ private static final int minor;
+ private static final int patch;
static {
- String[] tmp = VERSION.split("\\.");
+ String[] tmp = VERSION.split("[\\.-]");
major = Integer.parseInt(tmp[0]);
minor = Integer.parseInt(tmp[1]);
patch = Integer.parseInt(tmp[2]);
}
+ @Nonnull
public static String getVersion() {
return VERSION;
}
diff --git a/src/test/java/org/anarres/cpp/CppReaderTest.java b/src/test/java/org/anarres/cpp/CppReaderTest.java
new file mode 100644
index 0000000..27eba06
--- /dev/null
+++ b/src/test/java/org/anarres/cpp/CppReaderTest.java
@@ -0,0 +1,34 @@
+package org.anarres.cpp;
+
+import java.util.Collections;
+
+import java.io.StringReader;
+import java.io.BufferedReader;
+import org.junit.Test;
+
+public class CppReaderTest {
+
+ private void testCppReader(String in, String out)
+ throws Exception {
+ System.out.println("Testing " + in + " => " + out);
+ StringReader r = new StringReader(in);
+ CppReader p = new CppReader(r);
+ p.getPreprocessor().setSystemIncludePath(
+ Collections.singletonList("src/test/resources")
+ );
+ p.getPreprocessor().getFeatures().add(Feature.LINEMARKERS);
+ BufferedReader b = new BufferedReader(p);
+
+ String line;
+ while ((line = b.readLine()) != null) {
+ System.out.println(" >> " + line);
+ }
+ }
+
+ @Test
+ public void testCppReader()
+ throws Exception {
+ testCppReader("#include <test0.h>\n", "ab");
+ }
+
+}
diff --git a/src/test/java/org/anarres/cpp/ErrorTest.java b/src/test/java/org/anarres/cpp/ErrorTest.java
new file mode 100644
index 0000000..8777452
--- /dev/null
+++ b/src/test/java/org/anarres/cpp/ErrorTest.java
@@ -0,0 +1,65 @@
+package org.anarres.cpp;
+
+import java.io.IOException;
+import org.junit.Test;
+import static org.anarres.cpp.Token.*;
+import static org.junit.Assert.*;
+
+public class ErrorTest {
+
+ private boolean testError(Preprocessor p)
+ throws LexerException,
+ IOException {
+ for (;;) {
+ Token tok = p.token();
+ if (tok.getType() == EOF)
+ break;
+ if (tok.getType() == INVALID)
+ return true;
+ }
+ return false;
+ }
+
+ private void testError(String input) throws Exception {
+ StringLexerSource sl;
+ PreprocessorListener pl;
+ Preprocessor p;
+
+ /* Without a PreprocessorListener, throws an exception. */
+ sl = new StringLexerSource(input, true);
+ p = new Preprocessor();
+ p.addFeature(Feature.CSYNTAX);
+ p.addInput(sl);
+ try {
+ assertTrue(testError(p));
+ fail("Lexing unexpectedly succeeded without listener.");
+ } catch (LexerException e) {
+ /* required */
+ }
+
+ /* With a PreprocessorListener, records the error. */
+ sl = new StringLexerSource(input, true);
+ p = new Preprocessor();
+ p.addFeature(Feature.CSYNTAX);
+ p.addInput(sl);
+ pl = new PreprocessorListener();
+ p.setListener(pl);
+ assertNotNull("CPP has listener", p.getListener());
+ assertTrue(testError(p));
+ assertTrue("Listener has errors", pl.getErrors() > 0);
+
+ /* Without CSYNTAX, works happily. */
+ sl = new StringLexerSource(input, true);
+ p = new Preprocessor();
+ p.addInput(sl);
+ assertTrue(testError(p));
+ }
+
+ @Test
+ public void testErrors() throws Exception {
+ testError("\"");
+ testError("'");
+ // testError("''");
+ }
+
+}
diff --git a/src/test/java/org/anarres/cpp/JavaFileSystemTest.java b/src/test/java/org/anarres/cpp/JavaFileSystemTest.java
new file mode 100644
index 0000000..0ca44be
--- /dev/null
+++ b/src/test/java/org/anarres/cpp/JavaFileSystemTest.java
@@ -0,0 +1,38 @@
+package org.anarres.cpp;
+
+import java.io.FileNotFoundException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class JavaFileSystemTest {
+
+ @Test
+ public void testJavaFileSystem() throws Exception {
+ JavaFileSystem fs = new JavaFileSystem();
+ VirtualFile f;
+
+ /* Anyone who has this file on their Unix box is messed up. */
+ f = fs.getFile("/foo/bar baz");
+ try {
+ f.getSource(); /* drop on floor */
+
+ assertTrue("Got a source for a non-file", f.isFile());
+ } catch (FileNotFoundException e) {
+ assertFalse("Got no source for a file", f.isFile());
+ }
+
+ /* We hope we have this. */
+ f = fs.getFile("/usr/include/stdio.h");
+ try {
+ f.getSource(); /* drop on floor */
+
+ System.out.println("Opened stdio.h");
+ assertTrue("Got a source for a non-file", f.isFile());
+ } catch (FileNotFoundException e) {
+ System.out.println("Failed to open stdio.h");
+ assertFalse("Got no source for a file", f.isFile());
+ }
+
+ }
+
+}
diff --git a/src/test/java/org/anarres/cpp/JoinReaderTest.java b/src/test/java/org/anarres/cpp/JoinReaderTest.java
new file mode 100644
index 0000000..527aa81
--- /dev/null
+++ b/src/test/java/org/anarres/cpp/JoinReaderTest.java
@@ -0,0 +1,41 @@
+package org.anarres.cpp;
+
+import java.io.StringReader;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class JoinReaderTest {
+
+ private void testJoinReader(String in, String out, boolean tg)
+ throws Exception {
+ System.out.println("Testing " + in + " => " + out);
+ StringReader r = new StringReader(in);
+ JoinReader j = new JoinReader(r, tg);
+
+ for (int i = 0; i < out.length(); i++) {
+ int c = j.read();
+ System.out.println("At offset " + i + ": " + (char) c);
+ assertEquals((char) out.charAt(i), c);
+ }
+ assertEquals(-1, j.read());
+ assertEquals(-1, j.read());
+ }
+
+ private void testJoinReader(String in, String out)
+ throws Exception {
+ testJoinReader(in, out, true);
+ testJoinReader(in, out, false);
+ }
+
+ @Test
+ public void testJoinReader()
+ throws Exception {
+ testJoinReader("ab", "ab");
+ testJoinReader("a\\b", "a\\b");
+ testJoinReader("a\nb", "a\nb");
+ testJoinReader("a\\\nb", "ab\n");
+ testJoinReader("foo??(bar", "foo[bar", true);
+ testJoinReader("foo??/\nbar", "foobar\n", true);
+ }
+
+}
diff --git a/src/test/java/org/anarres/cpp/LexerSourceTest.java b/src/test/java/org/anarres/cpp/LexerSourceTest.java
new file mode 100644
index 0000000..76bc673
--- /dev/null
+++ b/src/test/java/org/anarres/cpp/LexerSourceTest.java
@@ -0,0 +1,86 @@
+package org.anarres.cpp;
+
+import java.util.Arrays;
+import org.junit.Test;
+import static org.anarres.cpp.Token.*;
+import static org.junit.Assert.*;
+
+public class LexerSourceTest {
+
+ private void testLexerSource(String in, int... out)
+ throws Exception {
+ System.out.println("Testing '" + in + "' => "
+ + Arrays.toString(out));
+ StringLexerSource s = new StringLexerSource(in);
+
+ int col = 0;
+ for (int i = 0; i < out.length; i++) {
+ Token tok = s.token();
+ System.out.println("Token is " + tok);
+ assertEquals(out[i], tok.getType());
+ // assertEquals(col, tok.getColumn());
+ col += tok.getText().length();
+ }
+
+ Token tok = s.token();
+ System.out.println("Token is " + tok);
+ assertEquals(EOF, tok.getType());
+ }
+
+ @Test
+ public void testLexerSource()
+ throws Exception {
+
+ testLexerSource("int a = 5;",
+ IDENTIFIER, WHITESPACE, IDENTIFIER, WHITESPACE,
+ '=', WHITESPACE, NUMBER, ';', EOF
+ );
+
+ // \n is WHITESPACE because ppvalid = false
+ testLexerSource("# # \r\n\n\r \rfoo",
+ HASH, WHITESPACE, '#', WHITESPACE, IDENTIFIER
+ );
+
+ testLexerSource("%:%:", PASTE);
+ testLexerSource("%:?", '#', '?');
+ testLexerSource("%:%=", '#', MOD_EQ);
+ testLexerSource("0x1234ffdUL 0765I",
+ NUMBER, WHITESPACE, NUMBER);
+
+ testLexerSource("+= -= *= /= %= <= >= >>= <<= &= |= ^= x",
+ PLUS_EQ, WHITESPACE,
+ SUB_EQ, WHITESPACE,
+ MULT_EQ, WHITESPACE,
+ DIV_EQ, WHITESPACE,
+ MOD_EQ, WHITESPACE,
+ LE, WHITESPACE,
+ GE, WHITESPACE,
+ RSH_EQ, WHITESPACE,
+ LSH_EQ, WHITESPACE,
+ AND_EQ, WHITESPACE,
+ OR_EQ, WHITESPACE,
+ XOR_EQ, WHITESPACE,
+ IDENTIFIER);
+
+ testLexerSource("/**/", CCOMMENT);
+ testLexerSource("/* /**/ */", CCOMMENT, WHITESPACE, '*', '/');
+ testLexerSource("/** ** **/", CCOMMENT);
+ testLexerSource("//* ** **/", CPPCOMMENT);
+ testLexerSource("'\\r' '\\xf' '\\xff' 'x' 'aa' ''",
+ CHARACTER, WHITESPACE,
+ CHARACTER, WHITESPACE,
+ CHARACTER, WHITESPACE,
+ CHARACTER, WHITESPACE,
+ SQSTRING, WHITESPACE,
+ SQSTRING);
+
+ testLexerSource("1i1I1l1L1ui1ul",
+ NUMBER, NUMBER,
+ NUMBER, NUMBER,
+ NUMBER, NUMBER);
+
+ testLexerSource("'' 'x' 'xx'",
+ SQSTRING, WHITESPACE, CHARACTER, WHITESPACE, SQSTRING);
+ }
+
+}
diff --git a/src/test/java/org/anarres/cpp/MainTest.java b/src/test/java/org/anarres/cpp/MainTest.java
new file mode 100644
index 0000000..5ff7350
--- /dev/null
+++ b/src/test/java/org/anarres/cpp/MainTest.java
@@ -0,0 +1,11 @@
+package org.anarres.cpp;
+
+import org.junit.Test;
+
+public class MainTest {
+
+ @Test
+ public void testMain() throws Exception {
+ Main.main(new String[]{"--version"});
+ }
+}
diff --git a/src/test/java/org/anarres/cpp/PreprocessorTest.java b/src/test/java/org/anarres/cpp/PreprocessorTest.java
new file mode 100644
index 0000000..fb2a8ac
--- /dev/null
+++ b/src/test/java/org/anarres/cpp/PreprocessorTest.java
@@ -0,0 +1,167 @@
+package org.anarres.cpp;
+
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import org.junit.Before;
+import org.junit.Test;
+import static org.anarres.cpp.Token.*;
+import static org.junit.Assert.*;
+
+public class PreprocessorTest {
+
+ private OutputStreamWriter writer;
+ private Preprocessor p;
+
+ @Before
+ public void setUp() throws Exception {
+ final PipedOutputStream po = new PipedOutputStream();
+ writer = new OutputStreamWriter(po);
+
+ p = new Preprocessor();
+ p.addInput(
+ new LexerSource(
+ new InputStreamReader(
+ new PipedInputStream(po)
+ ),
+ true
+ )
+ );
+ }
+
+ private static class I {
+
+ private String t;
+
+ public I(String t) {
+ this.t = t;
+ }
+
+ public String getText() {
+ return t;
+ }
+
+ public String toString() {
+ return getText();
+ }
+ }
+
+ private static I I(String t) {
+ return new I(t);
+ }
+
+ /*
+ * When writing tests in this file, remember the preprocessor
+ * stashes NLs, so you won't see an immediate NL at the end of any
+ * input line. You will see it right before the next nonblank on
+ * the following input line.
+ */
+ @Test
+ public void testPreprocessor() throws Exception {
+ /* Magic macros */
+ testInput("line = __LINE__\n",
+ I("line"), WHITESPACE, '=', WHITESPACE, NUMBER
+ /*, NL - all nls deferred so as not to block the reader */
+ );
+ testInput("file = __FILE__\n", NL, /* from before, etc */
+ I("file"), WHITESPACE, '=', WHITESPACE, STRING
+ );
+
+ /* Simple definitions */
+ testInput("#define A a /* a defined */\n", NL);
+ testInput("#define B b /* b defined */\n", NL);
+ testInput("#define C c /* c defined */\n", NL);
+
+ /* Expansion of arguments */
+ testInput("#define EXPAND(x) x\n", NL);
+ testInput("EXPAND(a)\n", NL, I("a"));
+ testInput("EXPAND(A)\n", NL, I("a"));
+
+ /* Stringification */
+ testInput("#define _STRINGIFY(x) #x\n", NL);
+ testInput("_STRINGIFY(A)\n", NL, "A");
+ testInput("#define STRINGIFY(x) _STRINGIFY(x)\n", NL);
+ testInput("STRINGIFY(b)\n", NL, "b");
+ testInput("STRINGIFY(A)\n", NL, "a");
+
+ /* Concatenation */
+ testInput("#define _CONCAT(x, y) x ## y\n", NL);
+ testInput("_CONCAT(A, B)\n", NL, I("AB"));
+ testInput("#define A_CONCAT done_a_concat\n", NL);
+ testInput("_CONCAT(A, _CONCAT(B, C))\n", NL,
+ I("done_a_concat"), '(', I("b"), ',', WHITESPACE, I("c"), ')'
+ );
+ testInput("#define CONCAT(x, y) _CONCAT(x, y)\n", NL);
+ testInput("CONCAT(A, CONCAT(B, C))\n", NL, I("abc"));
+ testInput("#define _CONCAT3(x, y, z) x ## y ## z\n", NL);
+ testInput("_CONCAT3(a, b, c)\n", NL, I("abc"));
+ testInput("_CONCAT3(A, B, C)\n", NL, I("ABC"));
+
+ /* Redefinitions, undefinitions. */
+ testInput("#define two three\n", NL);
+ testInput("one /* one */\n", NL, I("one"), WHITESPACE, CCOMMENT);
+ testInput("#define one two\n", NL);
+ testInput("one /* three */\n", NL, I("three"), WHITESPACE, CCOMMENT);
+ testInput("#undef two\n", NL);
+ testInput("#define two five\n", NL);
+ testInput("one /* five */\n", NL, I("five"), WHITESPACE, CCOMMENT);
+ testInput("#undef two\n", NL);
+ testInput("one /* two */\n", NL, I("two"), WHITESPACE, CCOMMENT);
+ testInput("#undef one\n", NL);
+ testInput("#define one four\n", NL);
+ testInput("one /* four */\n", NL, I("four"), WHITESPACE, CCOMMENT);
+ testInput("#undef one\n", NL);
+ testInput("#define one one\n", NL);
+ testInput("one /* one */\n", NL, I("one"), WHITESPACE, CCOMMENT);
+
+ /* Variadic macros. */
+ testInput("#define var(x...) a x b\n", NL);
+ testInput("var(e, f, g)\n", NL,
+ I("a"), WHITESPACE,
+ I("e"), ',', WHITESPACE,
+ I("f"), ',', WHITESPACE,
+ I("g"), WHITESPACE,
+ I("b")
+ );
+
+ testInput("#define _Widen(x) L ## x\n", NL);
+ testInput("#define Widen(x) _Widen(x)\n", NL);
+ testInput("#define LStr(x) _Widen(#x)\n", NL);
+ testInput("LStr(x);\n", NL, I("L"), "x");
+
+ writer.close();
+
+ Token t;
+ do {
+ t = p.token();
+ System.out.println("Remaining token " + t);
+ } while (t.getType() != EOF);
+ }
+
+ private void testInput(String in, Object... out)
+ throws Exception {
+ System.out.print("Input: " + in);
+ writer.write(in);
+ writer.flush();
+ for (int i = 0; i < out.length; i++) {
+ Token t = p.token();
+ System.out.println(t);
+ Object v = out[i];
+ if (v instanceof String) {
+ if (t.getType() != STRING)
+ fail("Expected STRING, but got " + t);
+ assertEquals((String) v, (String) t.getValue());
+ } else if (v instanceof I) {
+ if (t.getType() != IDENTIFIER)
+ fail("Expected IDENTIFIER " + v + ", but got " + t);
+ assertEquals(((I) v).getText(), (String) t.getText());
+ } else if (v instanceof Character)
+ assertEquals((int) ((Character) v).charValue(), t.getType());
+ else if (v instanceof Integer)
+ assertEquals(((Integer) v).intValue(), t.getType());
+ else
+ fail("Bad object " + v.getClass());
+ }
+ }
+}
diff --git a/src/input/test0.c b/src/test/resources/test0.c
index 7e91637..7e91637 100644
--- a/src/input/test0.c
+++ b/src/test/resources/test0.c
diff --git a/src/input/test0.h b/src/test/resources/test0.h
index b6697c6..b6697c6 100644
--- a/src/input/test0.h
+++ b/src/test/resources/test0.h
diff --git a/src/input/test1.c b/src/test/resources/test1.c
index 3e6fbda..3e6fbda 100644
--- a/src/input/test1.c
+++ b/src/test/resources/test1.c
diff --git a/src/input/test1.h b/src/test/resources/test1.h
index 0b690f7..0b690f7 100644
--- a/src/input/test1.h
+++ b/src/test/resources/test1.h
diff --git a/src/input/trigraph.c b/src/test/resources/trigraph.c
index 89615fe..89615fe 100644
--- a/src/input/trigraph.c
+++ b/src/test/resources/trigraph.c
diff --git a/src/tests/AutoTestSuite.java b/src/tests/AutoTestSuite.java
deleted file mode 100644
index 894a365..0000000
--- a/src/tests/AutoTestSuite.java
+++ /dev/null
@@ -1,121 +0,0 @@
-import java.lang.reflect.Modifier;
-
-import java.io.File;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import junit.framework.TestSuite;
-import junit.framework.TestCase;
-import junit.framework.Test;
-
-public class AutoTestSuite extends TestSuite {
- private String testPackage;
- private Set<String> testCases;
- private boolean testAll;
- private File root;
-
- public AutoTestSuite() {
- this.testPackage = System.getProperty("test.package");
- String tcase = System.getProperty("test.case");
- if (tcase != null && tcase.length() > 0) {
- this.testCases = new HashSet(Arrays.asList(
- tcase.split("[,:]")
- ));
- }
- else {
- this.testCases = null;
- }
- this.testAll = System.getProperty("test.all") != null;
- this.root = new File(System.getProperty("test.root"));
-
- Set<Class> tests = new HashSet();
-
- findClasses("", root, tests);
-
- Iterator<Class> i = tests.iterator();
-
- while(i.hasNext()) {
- addTestSuite(i.next());
- }
- }
-
- public void addTestSuite(Class clazz) {
- if (testPackage != null) {
- String name = clazz.getPackage().getName();
- if (!name.startsWith(testPackage)) {
- /*
- System.out.println("Skipping test in package '" +
- name + "' - does not start with '" +
- testPackage + "'");
- */
- return;
- }
- }
- if (testCases != null) {
- String name = clazz.getName();
- name = name.substring(name.lastIndexOf('.') + 1);
- if (!testCases.contains(name)) {
- /*
- System.out.println("Skipping test in class '" +
- name + "' - does not start with '" +
- testCases + "'");
- */
- return;
- }
- }
- /*
- if (
- testCases == null &&
- testPackage == null &&
- !testAll &&
- Optional.class.isAssignableFrom(clazz)
- )
- {
- return;
- }
- */
- System.out.println("Adding test class '" + clazz + "'");
- super.addTestSuite(clazz);
- }
-
- public static Test suite() {
- return new AutoTestSuite();
- }
-
- private final void findClasses(String pkg, File root, Set<Class> result) {
- File[] children = root.listFiles();
- for(int i = 0; i<children.length; i++) {
- File child = children[i];
- if(child.isDirectory()) {
- findClasses(
- pkg + child.getName() + ".",
- child,
- result
- );
- } else if(child.isFile()) {
- String name = child.getName();
- // System.out.println("Checking: " + pkg + name);
- if(name.endsWith(".class") && name.indexOf('$') == -1) {
- try {
- Class test = Class.forName(pkg +
- name.substring(0,name.length() - 6));
- int modifiers = test.getModifiers();
- if(
- (modifiers & Modifier.ABSTRACT) > 0 ||
- (modifiers & Modifier.INTERFACE) > 0 ||
- !TestCase.class.isAssignableFrom(test) ||
- TestSuite.class.isAssignableFrom(test)
- )
- continue;
- result.add(test);
- } catch (ClassNotFoundException cnfe) {
- cnfe.printStackTrace();
- }
- }
- }
- }
- }
-}
diff --git a/src/tests/org/anarres/cpp/BaseTestCase.java b/src/tests/org/anarres/cpp/BaseTestCase.java
deleted file mode 100644
index ad6ae6a..0000000
--- a/src/tests/org/anarres/cpp/BaseTestCase.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.anarres.cpp;
-
-import junit.framework.TestCase;
-
-public abstract class BaseTestCase extends TestCase {
-}
diff --git a/src/tests/org/anarres/cpp/CppReaderTestCase.java b/src/tests/org/anarres/cpp/CppReaderTestCase.java
deleted file mode 100644
index df3aeb5..0000000
--- a/src/tests/org/anarres/cpp/CppReaderTestCase.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.anarres.cpp;
-
-import java.util.Collections;
-
-import java.io.StringReader;
-import java.io.BufferedReader;
-
-import junit.framework.Test;
-
-public class CppReaderTestCase extends BaseTestCase implements Test {
-
- private void testCppReader(String in, String out)
- throws Exception {
- System.out.println("Testing " + in + " => " + out);
- StringReader r = new StringReader(in);
- CppReader p = new CppReader(r);
- p.getPreprocessor().setSystemIncludePath(
- Collections.singletonList("src/input")
- );
- p.getPreprocessor().getFeatures().add(Feature.LINEMARKERS);
- BufferedReader b = new BufferedReader(p);
-
- String line;
- while ((line = b.readLine()) != null) {
- System.out.println(" >> " + line);
- }
- }
-
- public void testCppReader()
- throws Exception {
- testCppReader("#include <test0.h>\n", "ab");
- }
-
-}
diff --git a/src/tests/org/anarres/cpp/ErrorTestCase.java b/src/tests/org/anarres/cpp/ErrorTestCase.java
deleted file mode 100644
index cec1dc7..0000000
--- a/src/tests/org/anarres/cpp/ErrorTestCase.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.anarres.cpp;
-
-import java.io.*;
-
-import junit.framework.Test;
-
-import static org.anarres.cpp.Token.*;
-
-public class ErrorTestCase extends BaseTestCase {
-
- private boolean testError(Preprocessor p)
- throws LexerException,
- IOException {
- for (;;) {
- Token tok = p.token();
- if (tok.getType() == EOF)
- break;
- if (tok.getType() == INVALID)
- return true;
- }
- return false;
- }
-
- private void testError(String input) throws Exception {
- StringLexerSource sl;
- PreprocessorListener pl;
- Preprocessor p;
-
- /* Without a PreprocessorListener, throws an exception. */
- sl = new StringLexerSource(input, true);
- p = new Preprocessor();
- p.addFeature(Feature.CSYNTAX);
- p.addInput(sl);
- try {
- assertTrue(testError(p));
- fail("Lexing unexpectedly succeeded without listener.");
- }
- catch (LexerException e) {
- /* required */
- }
-
- /* With a PreprocessorListener, records the error. */
- sl = new StringLexerSource(input, true);
- p = new Preprocessor();
- p.addFeature(Feature.CSYNTAX);
- p.addInput(sl);
- pl = new PreprocessorListener();
- p.setListener(pl);
- assertNotNull("CPP has listener", p.getListener());
- assertTrue(testError(p));
- assertTrue("Listener has errors", pl.getErrors() > 0);
-
- /* Without CSYNTAX, works happily. */
- sl = new StringLexerSource(input, true);
- p = new Preprocessor();
- p.addInput(sl);
- assertTrue(testError(p));
- }
-
- public void testErrors() throws Exception {
- testError("\"");
- testError("'");
- // testError("''");
- }
-
-}
diff --git a/src/tests/org/anarres/cpp/JavaFileSystemTestCase.java b/src/tests/org/anarres/cpp/JavaFileSystemTestCase.java
deleted file mode 100644
index 4f68d68..0000000
--- a/src/tests/org/anarres/cpp/JavaFileSystemTestCase.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.anarres.cpp;
-
-import java.io.*;
-
-import junit.framework.Test;
-
-import static org.anarres.cpp.Token.*;
-
-public class JavaFileSystemTestCase extends BaseTestCase {
-
- public void testJavaFileSystem() throws Exception {
- JavaFileSystem fs = new JavaFileSystem();
- VirtualFile f;
-
- /* Anyone who has this file on their Unix box is messed up. */
- f = fs.getFile("/foo/bar baz");
- try {
- f.getSource(); /* drop on floor */
- assertTrue("Got a source for a non-file", f.isFile());
- }
- catch (FileNotFoundException e) {
- assertFalse("Got no source for a file", f.isFile());
- }
-
- /* We hope we have this. */
- f = fs.getFile("/usr/include/stdio.h");
- try {
- f.getSource(); /* drop on floor */
- System.out.println("Opened stdio.h");
- assertTrue("Got a source for a non-file", f.isFile());
- }
- catch (FileNotFoundException e) {
- System.out.println("Failed to open stdio.h");
- assertFalse("Got no source for a file", f.isFile());
- }
-
- }
-
-}
diff --git a/src/tests/org/anarres/cpp/JoinReaderTestCase.java b/src/tests/org/anarres/cpp/JoinReaderTestCase.java
deleted file mode 100644
index 6c11449..0000000
--- a/src/tests/org/anarres/cpp/JoinReaderTestCase.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.anarres.cpp;
-
-import java.io.StringReader;
-
-import junit.framework.Test;
-
-public class JoinReaderTestCase extends BaseTestCase implements Test {
-
- private void testJoinReader(String in, String out, boolean tg)
- throws Exception {
- System.out.println("Testing " + in + " => " + out);
- StringReader r = new StringReader(in);
- JoinReader j = new JoinReader(r, tg);
-
- for (int i = 0; i < out.length(); i++) {
- int c = j.read();
- System.out.println("At offset " + i + ": " + (char)c);
- assertEquals((char)out.charAt(i), c);
- }
- assertEquals(-1, j.read());
- assertEquals(-1, j.read());
- }
-
- private void testJoinReader(String in, String out)
- throws Exception {
- testJoinReader(in, out, true);
- testJoinReader(in, out, false);
- }
-
- public void testJoinReader()
- throws Exception {
- testJoinReader("ab", "ab");
- testJoinReader("a\\b", "a\\b");
- testJoinReader("a\nb", "a\nb");
- testJoinReader("a\\\nb", "ab\n");
- testJoinReader("foo??(bar", "foo[bar", true);
- testJoinReader("foo??/\nbar", "foobar\n", true);
- }
-
-}
diff --git a/src/tests/org/anarres/cpp/LexerSourceTestCase.java b/src/tests/org/anarres/cpp/LexerSourceTestCase.java
deleted file mode 100644
index d554814..0000000
--- a/src/tests/org/anarres/cpp/LexerSourceTestCase.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.anarres.cpp;
-
-import java.io.StringReader;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-import junit.framework.Test;
-
-import static org.anarres.cpp.Token.*;
-
-public class LexerSourceTestCase extends BaseTestCase implements Test {
-
- private void testLexerSource(String in, int... out)
- throws Exception {
- System.out.println("Testing '" + in + "' => " +
- Arrays.toString(out));
- StringLexerSource s = new StringLexerSource(in);
-
- int col = 0;
- for (int i = 0; i < out.length; i++) {
- Token tok = s.token();
- System.out.println("Token is " + tok);
- assertEquals(out[i], tok.getType());
- assertEquals(col, tok.getColumn());
- col += tok.getText().length();
- }
- assertEquals(EOF, s.token().getType());
- }
-
- public void testLexerSource()
- throws Exception {
-
- testLexerSource("int a = 5;",
- IDENTIFIER, WHITESPACE, IDENTIFIER, WHITESPACE,
- '=', WHITESPACE, NUMBER, ';', EOF
- );
-
- // \n is WHITESPACE because ppvalid = false
- testLexerSource("# # \r\n\n\r \rfoo",
- HASH, WHITESPACE, '#', WHITESPACE, IDENTIFIER
- );
-
- testLexerSource("%:%:", PASTE);
- testLexerSource("%:?", '#', '?');
- testLexerSource("%:%=", '#', MOD_EQ);
- testLexerSource("0x1234ffdUL 0765I",
- NUMBER, WHITESPACE, NUMBER);
-
- testLexerSource("+= -= *= /= %= <= >= >>= <<= &= |= ^= x",
- PLUS_EQ, WHITESPACE,
- SUB_EQ, WHITESPACE,
- MULT_EQ, WHITESPACE,
- DIV_EQ, WHITESPACE,
- MOD_EQ, WHITESPACE,
- LE, WHITESPACE,
- GE, WHITESPACE,
- RSH_EQ, WHITESPACE,
- LSH_EQ, WHITESPACE,
- AND_EQ, WHITESPACE,
- OR_EQ, WHITESPACE,
- XOR_EQ, WHITESPACE,
- IDENTIFIER);
-
- testLexerSource("/**/", CCOMMENT);
- testLexerSource("/* /**/ */", CCOMMENT, WHITESPACE, '*', '/');
- testLexerSource("/** ** **/", CCOMMENT);
- testLexerSource("//* ** **/", CPPCOMMENT);
- testLexerSource("'\\r' '\\xf' '\\xff' 'x' 'aa' ''",
- CHARACTER, WHITESPACE,
- CHARACTER, WHITESPACE,
- CHARACTER, WHITESPACE,
- CHARACTER, WHITESPACE,
- SQSTRING, WHITESPACE,
- SQSTRING);
-
- testLexerSource("1i1I1l1L1ui1ul",
- NUMBER, NUMBER,
- NUMBER, NUMBER,
- NUMBER, NUMBER);
-
- testLexerSource("'' 'x' 'xx'",
- SQSTRING, WHITESPACE, CHARACTER, WHITESPACE, SQSTRING);
- }
-
-}
diff --git a/src/tests/org/anarres/cpp/MainTestCase.java b/src/tests/org/anarres/cpp/MainTestCase.java
deleted file mode 100644
index 313a463..0000000
--- a/src/tests/org/anarres/cpp/MainTestCase.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.anarres.cpp;
-
-import java.io.*;
-
-import junit.framework.Test;
-
-import static org.anarres.cpp.Token.*;
-
-public class MainTestCase extends BaseTestCase {
-
- public void testMain() throws Exception {
- Main.main(new String[] { "--version" });
- }
-
-}
diff --git a/src/tests/org/anarres/cpp/PreprocessorTestCase.java b/src/tests/org/anarres/cpp/PreprocessorTestCase.java
deleted file mode 100644
index 217659a..0000000
--- a/src/tests/org/anarres/cpp/PreprocessorTestCase.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package org.anarres.cpp;
-
-import java.io.*;
-
-import junit.framework.Test;
-
-import static org.anarres.cpp.Token.*;
-
-public class PreprocessorTestCase extends BaseTestCase {
- private OutputStreamWriter writer;
- private Preprocessor p;
-
- public void setUp() throws Exception {
- final PipedOutputStream po = new PipedOutputStream();
- writer = new OutputStreamWriter(po);
-
- p = new Preprocessor();
- p.addInput(
- new LexerSource(
- new InputStreamReader(
- new PipedInputStream(po)
- ),
- true
- )
- );
- }
-
- private static class I {
- private String t;
- public I(String t) {
- this.t = t;
- }
- public String getText() {
- return t;
- }
- public String toString() {
- return getText();
- }
- }
-
- private static I I(String t) {
- return new I(t);
- }
-
-/*
- * When writing tests in this file, remember the preprocessor
- * stashes NLs, so you won't see an immediate NL at the end of any
- * input line. You will see it right before the next nonblank on
- * the following input line.
- */
-
- public void testPreprocessor() throws Exception {
- /* Magic macros */
- testInput("line = __LINE__\n",
- I("line"), WHITESPACE, '=', WHITESPACE, NUMBER
- /*, NL - all nls deferred so as not to block the reader */
- );
- testInput("file = __FILE__\n", NL, /* from before, etc */
- I("file"), WHITESPACE, '=', WHITESPACE, STRING
- );
-
- /* Simple definitions */
- testInput("#define A a /* a defined */\n", NL);
- testInput("#define B b /* b defined */\n", NL);
- testInput("#define C c /* c defined */\n", NL);
-
- /* Expansion of arguments */
- testInput("#define EXPAND(x) x\n", NL);
- testInput("EXPAND(a)\n", NL, I("a"));
- testInput("EXPAND(A)\n", NL, I("a"));
-
- /* Stringification */
- testInput("#define _STRINGIFY(x) #x\n", NL);
- testInput("_STRINGIFY(A)\n", NL, "A");
- testInput("#define STRINGIFY(x) _STRINGIFY(x)\n", NL);
- testInput("STRINGIFY(b)\n", NL, "b");
- testInput("STRINGIFY(A)\n", NL, "a");
-
- /* Concatenation */
- testInput("#define _CONCAT(x, y) x ## y\n", NL);
- testInput("_CONCAT(A, B)\n", NL, I("AB"));
- testInput("#define A_CONCAT done_a_concat\n", NL);
- testInput("_CONCAT(A, _CONCAT(B, C))\n", NL,
- I("done_a_concat"), '(', I("b"), ',', WHITESPACE, I("c"), ')'
- );
- testInput("#define CONCAT(x, y) _CONCAT(x, y)\n", NL);
- testInput("CONCAT(A, CONCAT(B, C))\n", NL, I("abc"));
- testInput("#define _CONCAT3(x, y, z) x ## y ## z\n", NL);
- testInput("_CONCAT3(a, b, c)\n", NL, I("abc"));
- testInput("_CONCAT3(A, B, C)\n", NL, I("ABC"));
-
-/* Redefinitions, undefinitions. */
-testInput("#define two three\n", NL);
-testInput("one /* one */\n", NL, I("one"), WHITESPACE, CCOMMENT);
-testInput("#define one two\n", NL);
-testInput("one /* three */\n", NL, I("three"), WHITESPACE, CCOMMENT);
-testInput("#undef two\n", NL);
-testInput("#define two five\n", NL);
-testInput("one /* five */\n", NL, I("five"), WHITESPACE, CCOMMENT);
-testInput("#undef two\n", NL);
-testInput("one /* two */\n", NL, I("two"), WHITESPACE, CCOMMENT);
-testInput("#undef one\n", NL);
-testInput("#define one four\n", NL);
-testInput("one /* four */\n", NL, I("four"), WHITESPACE, CCOMMENT);
-testInput("#undef one\n", NL);
-testInput("#define one one\n", NL);
-testInput("one /* one */\n", NL, I("one"), WHITESPACE, CCOMMENT);
-
- /* Variadic macros. */
- testInput("#define var(x...) a x b\n", NL);
- testInput("var(e, f, g)\n", NL,
- I("a"), WHITESPACE,
- I("e"), ',', WHITESPACE,
- I("f"), ',', WHITESPACE,
- I("g"), WHITESPACE,
- I("b")
- );
-
- testInput("#define _Widen(x) L ## x\n", NL);
- testInput("#define Widen(x) _Widen(x)\n", NL);
- testInput("#define LStr(x) _Widen(#x)\n", NL);
- testInput("LStr(x);\n", NL, I("L"), "x");
-
- writer.close();
-
- Token t;
- do {
- t = p.token();
- System.out.println("Remaining token " + t);
- } while(t.getType() != EOF);
- }
-
- private void testInput(String in, Object... out)
- throws Exception {
- System.out.print("Input: " + in);
- writer.write(in);
- writer.flush();
- for (int i = 0; i < out.length; i++) {
- Token t = p.token();
- System.out.println(t);
- Object v = out[i];
- if (v instanceof String) {
- if (t.getType() != STRING)
- fail("Expected STRING, but got " + t);
- assertEquals((String)v, (String)t.getValue());
- }
- else if (v instanceof I) {
- if (t.getType() != IDENTIFIER)
- fail("Expected IDENTIFIER " + v + ", but got " + t);
- assertEquals( ((I)v).getText(), (String)t.getText());
- }
- else if (v instanceof Character)
- assertEquals( (int)((Character)v).charValue(), t.getType());
- else if (v instanceof Integer)
- assertEquals( ((Integer)v).intValue(), t.getType());
- else
- fail("Bad object " + v.getClass());
- }
- }
-}