diff --git a/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java b/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java index 8bad96ab4..dbcfe1f16 100644 --- a/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java +++ b/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java @@ -349,7 +349,7 @@ public class CPreprocessor implements TokenSource { } if(macro != null) { // Handle parameters - List> paramValues = new ArrayList<>(); + List> paramValues = new ArrayList<>(); if(macro.hasParameters()) { // Parse parameter value list { @@ -364,10 +364,9 @@ public class CPreprocessor implements TokenSource { cTokenSource.nextToken(); } // Read parameter values - List paramValue = new ArrayList<>(); + LinkedList paramValue = new LinkedList<>(); int nesting = 1; while(true) { - //skipWhitespace(cTokenSource); final Token paramToken = cTokenSource.nextToken(); if(paramToken.getType() == KickCLexer.PAR_END && nesting == 1) { // We reached the end of the parameters - add the current param unless it is an empty parameter alone in the list @@ -377,7 +376,7 @@ public class CPreprocessor implements TokenSource { } else if(paramToken.getType() == KickCLexer.COMMA && nesting == 1) { // We have reached the next parameter value paramValues.add(paramValue); - paramValue = new ArrayList<>(); + paramValue = new LinkedList<>(); } else { // We are reading a parameter value - handle nesting and store it if(paramToken.getType() == KickCLexer.PAR_BEGIN) @@ -393,9 +392,9 @@ public class CPreprocessor implements TokenSource { throw new CompileError("Error! Wrong number of macro parameters. Expected " + macro.parameters.size() + " was " + paramValues.size(), macroNameToken); } // Expand parameter values - List> expandedParamValues = new ArrayList<>(); + List> expandedParamValues = new ArrayList<>(); for(List paramTokens : paramValues) { - List expandedParamValue = new ArrayList<>(); + LinkedList expandedParamValue = new LinkedList<>(); CPreprocessor subPreprocessor = new CPreprocessor(cParser, new ListTokenSource(paramTokens), new HashMap<>(defines)); while(true) { final Token expandedToken = subPreprocessor.nextToken(); @@ -404,6 +403,11 @@ public class CPreprocessor implements TokenSource { else expandedParamValue.add(expandedToken); } + // Remove leading and trailing whitespace in each paramValue + if(expandedParamValue.peekFirst().getType() == KickCLexer.WS) + expandedParamValue.pollFirst(); + if(expandedParamValue.peekLast().getType() == KickCLexer.WS) + expandedParamValue.pollLast(); expandedParamValues.add(expandedParamValue); paramValues = expandedParamValues; } @@ -422,21 +426,21 @@ public class CPreprocessor implements TokenSource { } } else if(macroBodyToken.getType() == KickCLexer.TOKEN_STRINGIZE) { final String paramName = macroBodyToken.getText().substring(1); - if(!macro.hasParameter(paramName)) throw new CompileError("Expected macro parameter name after '#'", macroBodyToken); + if(!macro.hasParameter(paramName)) + throw new CompileError("Expected macro parameter name after '#'", macroBodyToken); // body token is a parameter name - replace with stringized expanded parameter value final int paramIndex = macro.getParameterIndex(paramName); final List expandedParamValue = paramValues.get(paramIndex); StringBuilder stringized = new StringBuilder(); for(Token expandedParamValueToken : expandedParamValue) { - //if(stringized.length()>0) stringized.append(" "); stringized.append(expandedParamValueToken.getText()); } CommonToken stringToken = new CommonToken(KickCLexer.STRING); - stringToken.setText("\""+stringized.toString()+"\""); + stringToken.setText("\"" + stringized.toString() + "\""); stringToken.setChannel(macroBodyToken.getChannel()); addTokenToExpandedBody(stringToken, macroNameToken, expandedBody); } else { - // body token is a normal token + // body token is a normal token addTokenToExpandedBody(macroBodyToken, macroNameToken, expandedBody); } } diff --git a/src/test/java/dk/camelot64/kickc/parsing/macros/TestPreprocessor.java b/src/test/java/dk/camelot64/kickc/parsing/macros/TestPreprocessor.java index 47f9ec1c0..662d50d2b 100644 --- a/src/test/java/dk/camelot64/kickc/parsing/macros/TestPreprocessor.java +++ b/src/test/java/dk/camelot64/kickc/parsing/macros/TestPreprocessor.java @@ -223,14 +223,16 @@ public class TestPreprocessor { public void testStringize() { // A simple define with one stringized parameter assertEquals("str:\"b\";", parse("#define A(a) #a \nA(b);")); - // A simple define with one stringized parameter + // A spaced stringized parameter assertEquals("str:\"two words\";", parse("#define A(a) #a \nA(two words);")); - // A simple define with one spaced stringized parameter - assertEquals("str:\" two words \";", parse("#define A(a) #a \nA( two words );")); + // A spaced stringized parameter - leading/trailing spaces removed + assertEquals("str:\"two words\";", parse("#define A(a) #a \nA( two words );")); // Concatenating string with stringized macro - assertEquals("str:\"qwe\"\"asd\";", parse("#define A(a) #a \n \"qwe\" A(asd);")); + assertEquals("str:\"qwe\"\"asd\";", parse("#define A(a) #a \n A(qwe) A(asd);")); // Complex concat with a path - from Kernighan&Ritchie assertEquals("str:\"/usr/tmp\"\"%s\";", parse("#define tempfile(dir) #dir \"%s\" \n tempfile(/usr/tmp);")); + // Complex stringize example - from https://gcc.gnu.org/onlinedocs/gcc-3.4.6/cpp/Stringification.html + assertEquals("str:\"4\";", parse("#define str(s) #s \n #define foo 4 \n str (foo);")); } private void assertError(String program, String expectError, boolean expectLineNumber) {