From dbd8a3cbacae2cad695c5a9f65a2d045613c11d0 Mon Sep 17 00:00:00 2001 From: jespergravgaard Date: Mon, 2 Aug 2021 07:52:50 +0200 Subject: [PATCH] Fixed problem with macros with empty parameter lists. Closes #688 --- .../kickc/preprocessor/CPreprocessor.java | 30 ++++- .../parsing/macros/TestPreprocessor.java | 2 + .../kickc/test/TestProgramsFast.java | 5 + src/test/kc/preprocessor-16.c | 8 ++ src/test/ref/preprocessor-16.asm | 14 ++ src/test/ref/preprocessor-16.cfg | 8 ++ src/test/ref/preprocessor-16.log | 121 ++++++++++++++++++ src/test/ref/preprocessor-16.sym | 2 + 8 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 src/test/kc/preprocessor-16.c create mode 100644 src/test/ref/preprocessor-16.asm create mode 100644 src/test/ref/preprocessor-16.cfg create mode 100644 src/test/ref/preprocessor-16.log create mode 100644 src/test/ref/preprocessor-16.sym diff --git a/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java b/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java index f9f7fcccb..3581fd5da 100644 --- a/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java +++ b/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java @@ -38,7 +38,7 @@ public class CPreprocessor implements TokenSource { static class Macro { /** The name of the define. */ final String name; - /** The parameters. Empty if there are no parameters. */ + /** The parameters. Null if there are no parameters. */ final List parameters; /** The body. */ final List body; @@ -48,6 +48,25 @@ public class CPreprocessor implements TokenSource { this.parameters = parameters; this.body = body; } + + /** Does the macro have any parameters */ + boolean hasParameters() { return parameters!=null; } + + /** Does the macro have a specific parameter */ + boolean hasParameter(String param) { + return parameters!=null && parameters.contains(param); + } + + /** + * Get the index of a specific parameter in the parameter list + * @param param The parameter name to look for + * @return The index of the parameter in the list. -1 if the passed name is not a parameter. + */ + private int getParameterIndex(String param) { + if(parameters==null) + return -1; + return parameters.indexOf(param); + } } public CPreprocessor(CParser cParser, TokenSource input, Map defines) { @@ -275,8 +294,9 @@ public class CPreprocessor implements TokenSource { skipWhitespace(cTokenSource); String macroName = nextToken(cTokenSource, KickCLexer.NAME).getText(); // Examine whether the macro has parameters - List macroParameters = new ArrayList<>(); + List macroParameters = null; if(cTokenSource.peekToken().getType() == KickCLexer.PAR_BEGIN) { + macroParameters = new ArrayList<>(); // Read past the '(' cTokenSource.nextToken(); // Macro has parameters - find parameter name list @@ -327,7 +347,7 @@ public class CPreprocessor implements TokenSource { if(macro != null) { // Handle parameters List> paramValues = new ArrayList<>(); - if(!macro.parameters.isEmpty()) { + if(macro.hasParameters()) { // Parse parameter value list { // Skip '(' @@ -383,9 +403,9 @@ public class CPreprocessor implements TokenSource { List expandedBody = new ArrayList<>(); final List macroBody = macro.body; for(Token macroBodyToken : macroBody) { - if(macroBodyToken.getType() == KickCLexer.NAME && macro.parameters.contains(macroBodyToken.getText())) { + if(macroBodyToken.getType() == KickCLexer.NAME && macro.hasParameter(macroBodyToken.getText())) { // body token is a parameter name - replace with expanded parameter value - final int paramIndex = macro.parameters.indexOf(macroBodyToken.getText()); + final int paramIndex = macro.getParameterIndex(macroBodyToken.getText()); final List expandedParamValue = paramValues.get(paramIndex); for(Token expandedParamValueToken : expandedParamValue) { addTokenToExpandedBody(expandedParamValueToken, 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 e47d3874f..29f4bc763 100644 --- a/src/test/java/dk/camelot64/kickc/parsing/macros/TestPreprocessor.java +++ b/src/test/java/dk/camelot64/kickc/parsing/macros/TestPreprocessor.java @@ -182,6 +182,8 @@ public class TestPreprocessor { */ @Test public void testDefineParams() { + // A simple define with an empty parameter + assertEquals("+(name:a,num:1);", parse("#define A() a+1\nA();")); // A simple define with one parameter assertEquals("+(name:b,num:1);", parse("#define A(a) a+1\nA(b);")); // A simple define with one parameter used twice diff --git a/src/test/java/dk/camelot64/kickc/test/TestProgramsFast.java b/src/test/java/dk/camelot64/kickc/test/TestProgramsFast.java index b464b4127..69425611f 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestProgramsFast.java +++ b/src/test/java/dk/camelot64/kickc/test/TestProgramsFast.java @@ -929,6 +929,11 @@ public class TestProgramsFast extends TestPrograms { compileAndCompare("cstyle-decl-function.c"); } + @Test + public void testPreprocessor16() throws IOException { + compileAndCompare("preprocessor-16.c"); + } + @Test public void testPreprocessor15() throws IOException { assertError("preprocessor-15.c", "Error parsing file: extraneous input 'X' "); diff --git a/src/test/kc/preprocessor-16.c b/src/test/kc/preprocessor-16.c new file mode 100644 index 000000000..c254b8005 --- /dev/null +++ b/src/test/kc/preprocessor-16.c @@ -0,0 +1,8 @@ +// Demonstrates a problem with the preprocessor where a macro with an empty parameter list does not accept an empty list of parameters + +#define CLEAR() a=0 + +void main() { + char a=1; + CLEAR(); +} \ No newline at end of file diff --git a/src/test/ref/preprocessor-16.asm b/src/test/ref/preprocessor-16.asm new file mode 100644 index 000000000..a2b3cee57 --- /dev/null +++ b/src/test/ref/preprocessor-16.asm @@ -0,0 +1,14 @@ +// Demonstrates a problem with the preprocessor where a macro with an empty parameter list does not accept an empty list of parameters + // Commodore 64 PRG executable file +.file [name="preprocessor-16.prg", type="prg", segments="Program"] +.segmentdef Program [segments="Basic, Code, Data"] +.segmentdef Basic [start=$0801] +.segmentdef Code [start=$80d] +.segmentdef Data [startAfter="Code"] +.segment Basic +:BasicUpstart(main) +.segment Code +main: { + // } + rts +} diff --git a/src/test/ref/preprocessor-16.cfg b/src/test/ref/preprocessor-16.cfg new file mode 100644 index 000000000..8e3f65ffc --- /dev/null +++ b/src/test/ref/preprocessor-16.cfg @@ -0,0 +1,8 @@ + +void main() +main: scope:[main] from + [0] phi() + to:main::@return +main::@return: scope:[main] from main + [1] return + to:@return diff --git a/src/test/ref/preprocessor-16.log b/src/test/ref/preprocessor-16.log new file mode 100644 index 000000000..d014ae7ef --- /dev/null +++ b/src/test/ref/preprocessor-16.log @@ -0,0 +1,121 @@ + +CONTROL FLOW GRAPH SSA + +void main() +main: scope:[main] from __start + to:main::@return +main::@return: scope:[main] from main + return + to:@return + +void __start() +__start: scope:[__start] from + call main + to:__start::@1 +__start::@1: scope:[__start] from __start + to:__start::@return +__start::@return: scope:[__start] from __start::@1 + return + to:@return + +SYMBOL TABLE SSA +void __start() +void main() + +Removing unused procedure __start +Removing unused procedure block __start +Removing unused procedure block __start::@1 +Removing unused procedure block __start::@return +Successful SSA optimization PassNEliminateEmptyStart +Adding NOP phi() at start of main +CALL GRAPH + +Created 0 initial phi equivalence classes +Coalesced down to 0 phi equivalence classes +Adding NOP phi() at start of main + +FINAL CONTROL FLOW GRAPH + +void main() +main: scope:[main] from + [0] phi() + to:main::@return +main::@return: scope:[main] from main + [1] return + to:@return + + +VARIABLE REGISTER WEIGHTS +void main() + +Initial phi equivalence classes +Complete equivalence classes +REGISTER UPLIFT POTENTIAL REGISTERS + +REGISTER UPLIFT SCOPES +Uplift Scope [main] +Uplift Scope [] + +Uplifting [main] best 36 combination +Uplifting [] best 36 combination + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// Demonstrates a problem with the preprocessor where a macro with an empty parameter list does not accept an empty list of parameters + // Upstart + // Commodore 64 PRG executable file +.file [name="preprocessor-16.prg", type="prg", segments="Program"] +.segmentdef Program [segments="Basic, Code, Data"] +.segmentdef Basic [start=$0801] +.segmentdef Code [start=$80d] +.segmentdef Data [startAfter="Code"] +.segment Basic +:BasicUpstart(main) + // Global Constants & labels +.segment Code + // main +main: { + jmp __breturn + // main::@return + __breturn: + // [1] return + rts +} + // File Data + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp __breturn +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction __breturn: +Succesful ASM optimization Pass5UnusedLabelElimination + +FINAL SYMBOL TABLE +void main() + + + +FINAL ASSEMBLER +Score: 6 + + // File Comments +// Demonstrates a problem with the preprocessor where a macro with an empty parameter list does not accept an empty list of parameters + // Upstart + // Commodore 64 PRG executable file +.file [name="preprocessor-16.prg", type="prg", segments="Program"] +.segmentdef Program [segments="Basic, Code, Data"] +.segmentdef Basic [start=$0801] +.segmentdef Code [start=$80d] +.segmentdef Data [startAfter="Code"] +.segment Basic +:BasicUpstart(main) + // Global Constants & labels +.segment Code + // main +main: { + // main::@return + // } + // [1] return + rts +} + // File Data + diff --git a/src/test/ref/preprocessor-16.sym b/src/test/ref/preprocessor-16.sym new file mode 100644 index 000000000..f7dad82f1 --- /dev/null +++ b/src/test/ref/preprocessor-16.sym @@ -0,0 +1,2 @@ +void main() +