diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index 8bd2e363e..028ac92d9 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -8,11 +8,12 @@ import dk.camelot64.kickc.model.statements.StatementSource; import dk.camelot64.kickc.model.symbols.Procedure; import dk.camelot64.kickc.model.values.SymbolRef; import dk.camelot64.kickc.parser.CParser; -import dk.camelot64.kickc.parser.KickCLexer; import dk.camelot64.kickc.parser.KickCParser; import dk.camelot64.kickc.passes.*; import dk.camelot64.kickc.preprocessor.CPreprocessor; -import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.RuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenSource; import java.io.File; import java.nio.file.Path; @@ -145,10 +146,7 @@ public class Compiler { CParser cParser = new CParser(program); if(defines != null) { for(String macroName : defines.keySet()) { - final String macroBodyText = "#define " + macroName + " " + defines.get(macroName) + "\n"; - final CodePointCharStream macroCharStream = CharStreams.fromString(macroBodyText); - final KickCLexer macroLexer = cParser.makeLexer(macroCharStream); - cParser.addSourceFirst(macroLexer); + cParser.define(macroName, defines.get(macroName)); } } for(Path cFile : cFiles) { diff --git a/src/main/java/dk/camelot64/kickc/KickC.java b/src/main/java/dk/camelot64/kickc/KickC.java index e096c4cb4..814a4818b 100644 --- a/src/main/java/dk/camelot64/kickc/KickC.java +++ b/src/main/java/dk/camelot64/kickc/KickC.java @@ -349,10 +349,16 @@ public class KickC implements Callable { StringBuilder CFileNames = new StringBuilder(); cFiles.stream().forEach(path -> CFileNames.append(path.toString()).append(" ")); + Map effectiveDefines = new LinkedHashMap<>(); + if(defines!=null) + effectiveDefines.putAll(defines); + if(program.getTargetPlatform().getDefines()!=null) + effectiveDefines.putAll(program.getTargetPlatform().getDefines()); + if(preprocess) { System.out.println("Preprocessing " + CFileNames); try { - compiler.preprocess(cFiles, defines); + compiler.preprocess(cFiles, effectiveDefines); } catch(CompileError e) { // Print the error and exit with compile error System.err.println(e.getMessage()); @@ -363,7 +369,7 @@ public class KickC implements Callable { System.out.println("Compiling " + CFileNames); try { - compiler.compile(cFiles, defines); + compiler.compile(cFiles, effectiveDefines); } catch(CompileError e) { // Print the error and exit with compile error System.err.println(e.getMessage()); diff --git a/src/main/java/dk/camelot64/kickc/model/TargetPlatform.java b/src/main/java/dk/camelot64/kickc/model/TargetPlatform.java index 83fd59d50..68386c526 100644 --- a/src/main/java/dk/camelot64/kickc/model/TargetPlatform.java +++ b/src/main/java/dk/camelot64/kickc/model/TargetPlatform.java @@ -6,14 +6,17 @@ import dk.camelot64.kickc.model.statements.StatementSource; import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonReader; +import javax.json.JsonValue; import javax.json.stream.JsonParsingException; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Path; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; /** * The target platform the compiler is creating a program for. @@ -113,6 +116,17 @@ public class TargetPlatform { final String emulatorCommand = platformJson.getString("emulator", null); if(emulatorCommand != null) targetPlatform.setEmulatorCommand(emulatorCommand); + final JsonObject defines = platformJson.getJsonObject("defines"); + if(defines!=null) { + final Set macroNames = defines.keySet(); + final LinkedHashMap macros = new LinkedHashMap<>(); + for(String macroName : macroNames) { + final JsonValue jsonValue = defines.get(macroName); + final String macroBody = jsonValue.toString(); + macros.put(macroName, macroBody); + } + targetPlatform.setDefines(macros); + } program.setTargetPlatform(targetPlatform); } catch(CompileError | IOException | JsonParsingException e) { throw new CompileError("Error parsing target platform file " + platformFile.getAbsolutePath() + "\n"+e.getMessage(), statementSource); diff --git a/src/main/java/dk/camelot64/kickc/parser/CParser.java b/src/main/java/dk/camelot64/kickc/parser/CParser.java index 2e5d6c644..c118b9600 100644 --- a/src/main/java/dk/camelot64/kickc/parser/CParser.java +++ b/src/main/java/dk/camelot64/kickc/parser/CParser.java @@ -101,6 +101,26 @@ public class CParser { return preprocessor; } + /** + * Define a new macro + * @param macroName The macro name + * @param macroBody The macro body + */ + public void define(String macroName, String macroBody) { + final String macroBodyText = "#define " + macroName + " " + macroBody + "\n"; + final CodePointCharStream macroCharStream = CharStreams.fromString(macroBodyText); + final KickCLexer macroLexer = makeLexer(macroCharStream); + addSourceFirst(macroLexer); + } + + /** + * Undef a macro + * @param macroName The macro name + */ + public void undef(String macroName) { + getPreprocessor().undef(macroName); + } + /** * Get the token stream containing tokens after the preprocessor. * diff --git a/src/main/java/dk/camelot64/kickc/parser/KickCParser.g4 b/src/main/java/dk/camelot64/kickc/parser/KickCParser.g4 index 38f529f64..18108b8f3 100644 --- a/src/main/java/dk/camelot64/kickc/parser/KickCParser.g4 +++ b/src/main/java/dk/camelot64/kickc/parser/KickCParser.g4 @@ -146,12 +146,12 @@ parameterDecl ; globalDirective - : (PRAGMA RESERVE) PAR_BEGIN NUMBER ( COMMA NUMBER )* PAR_END #globalDirectiveReserve - | (PRAGMA PC) PAR_BEGIN NUMBER PAR_END #globalDirectivePc - | (PRAGMA TARGET) PAR_BEGIN NAME PAR_END #globalDirectivePlatform + : (PRAGMA TARGET) PAR_BEGIN NAME PAR_END #globalDirectivePlatform | (PRAGMA CPU) PAR_BEGIN NAME PAR_END #globalDirectiveCpu | (PRAGMA LINK) PAR_BEGIN STRING PAR_END #globalDirectiveLinkScript | (PRAGMA EMULATOR) PAR_BEGIN STRING PAR_END #globalDirectiveEmulator + | (PRAGMA RESERVE) PAR_BEGIN NUMBER ( COMMA NUMBER )* PAR_END #globalDirectiveReserve + | (PRAGMA PC) PAR_BEGIN NUMBER PAR_END #globalDirectivePc | (PRAGMA CODESEG) PAR_BEGIN NAME PAR_END #globalDirectiveCodeSeg | (PRAGMA DATASEG) PAR_BEGIN NAME PAR_END #globalDirectiveDataSeg | (PRAGMA ENCODING) PAR_BEGIN NAME PAR_END #globalDirectiveEncoding diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java b/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java index 13ae7b2ca..556f616ca 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass0GenerateStatementSequence.java @@ -182,7 +182,17 @@ public class Pass0GenerateStatementSequence extends KickCParserBaseVisitor ws = skipWhitespace(cTokenSource); + final Token pragmaType = cTokenSource.nextToken(); + + /* + if(KickCLexer.TARGET == pragmaType.getType()) { + skipWhitespace(cTokenSource); + nextToken(cTokenSource, KickCLexer.PAR_BEGIN); + skipWhitespace(cTokenSource); + final String targetName = nextToken(cTokenSource, KickCLexer.NAME).getText(); + skipWhitespace(cTokenSource); + nextToken(cTokenSource, KickCLexer.PAR_END); + //TargetPlatform.setTargetPlatform(targetName, null, ); + throw new InternalError("TODO: Implement #pragma target"); + // return true; + } + */ + + // Pass on the #pragma to the parser + final ArrayList pragmaTokens = new ArrayList<>(); + pragmaTokens.add(new PragmaToken(inputToken)); + pragmaTokens.addAll(ws); + pragmaTokens.add(pragmaType); + cTokenSource.addSourceFirst(new ListTokenSource(pragmaTokens)); + return true; + } + /** * Define a macro. * @@ -258,7 +297,7 @@ 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.parameters.contains(macroBodyToken.getText())) { // body token is a parameter name - replace with expanded parameter value final int paramIndex = macro.parameters.indexOf(macroBodyToken.getText()); final List expandedParamValue = paramValues.get(paramIndex); @@ -278,6 +317,7 @@ public class CPreprocessor implements TokenSource { /** * Add a macro token to the exapnded macro body. Keeps track of which macros has been used to expand the token using {@link ExpansionToken} + * * @param macroBodyToken The macro body token to add * @param macroNameToken The token containing the macro name. Used to get the name and as a source for copying token properties (ensuring file name, line etc. are OK). * @param expandedBody The expanded macro body to add the token to @@ -305,6 +345,15 @@ public class CPreprocessor implements TokenSource { // #undef a new macro - find the name skipWhitespace(cTokenSource); String macroName = nextToken(cTokenSource, KickCLexer.NAME).getText(); + undef(macroName); + } + + /** + * Undefine a macro. + * + * @param macroName The macro name + */ + public void undef(String macroName) { this.defines.remove(macroName); } @@ -592,16 +641,21 @@ public class CPreprocessor implements TokenSource { * Skip whitespace tokens (except newlines), positioning iterator at the next non-whitespace * * @param cTokenSource The token iterator + * @return The skipped tokens */ - private void skipWhitespace(CTokenSource cTokenSource) { + private List skipWhitespace(CTokenSource cTokenSource) { + List ws = new ArrayList<>(); while(true) { final Token token = cTokenSource.peekToken(); if(token.getChannel() != CParser.CHANNEL_WHITESPACE) break; if(token.getText().contains("\n")) break; + // The token is whitespace + ws.add(token); cTokenSource.nextToken(); } + return ws; } /** @@ -676,5 +730,69 @@ public class CPreprocessor implements TokenSource { } } + /** + * A pragma token that should be handled in the parser (not in the lexer) + * Used to allow the lexer to ensure the preprocessor skips it the second time around. + **/ + public static class PragmaToken implements Token { + + /** The underlying pragma token. */ + private Token subToken; + + PragmaToken(Token subToken) { + this.subToken = subToken; + } + + @Override + public String getText() { + return subToken.getText(); + } + + @Override + public int getType() { + return subToken.getType(); + } + + @Override + public int getLine() { + return subToken.getLine(); + } + + @Override + public int getCharPositionInLine() { + return subToken.getCharPositionInLine(); + } + + @Override + public int getChannel() { + return subToken.getChannel(); + } + + @Override + public int getTokenIndex() { + return subToken.getTokenIndex(); + } + + @Override + public int getStartIndex() { + return subToken.getStartIndex(); + } + + @Override + public int getStopIndex() { + return subToken.getStopIndex(); + } + + @Override + public TokenSource getTokenSource() { + return subToken.getTokenSource(); + } + + @Override + public CharStream getInputStream() { + return subToken.getInputStream(); + } + } + } diff --git a/src/main/kc/target/c64.tgt b/src/main/kc/target/c64.tgt index 63fa55fdd..b38e4842c 100644 --- a/src/main/kc/target/c64.tgt +++ b/src/main/kc/target/c64.tgt @@ -1,5 +1,8 @@ { "link": "c64.ld", "cpu": "MOS6502X", - "emulator": "x64sc" + "emulator": "x64sc", + "defines": { + "__C64__": 1 + } } \ No newline at end of file diff --git a/src/main/kc/target/c64basic.tgt b/src/main/kc/target/c64basic.tgt index e09c5d86c..b81829981 100644 --- a/src/main/kc/target/c64basic.tgt +++ b/src/main/kc/target/c64basic.tgt @@ -1,5 +1,8 @@ { "link": "c64basic.ld", "cpu": "MOS6502X", - "emulator": "x64sc" + "emulator": "x64sc", + "defines": { + "__C64__": 1 + } } \ No newline at end of file diff --git a/src/main/kc/target/plus4.tgt b/src/main/kc/target/plus4.tgt index 8c07e113e..d322ba271 100644 --- a/src/main/kc/target/plus4.tgt +++ b/src/main/kc/target/plus4.tgt @@ -1,5 +1,8 @@ { "link": "plus4.ld", "cpu": "MOS6502X", - "emulator": "xplus4" + "emulator": "xplus4", + "defines": { + "__PLUS4__": 1 + } } \ No newline at end of file diff --git a/src/main/kc/target/plus4basic.tgt b/src/main/kc/target/plus4basic.tgt index 746abd8b5..fa8f25e94 100644 --- a/src/main/kc/target/plus4basic.tgt +++ b/src/main/kc/target/plus4basic.tgt @@ -1,5 +1,8 @@ { "link": "plus4basic.ld", "cpu": "MOS6502X", - "emulator": "xplus4" + "emulator": "xplus4", + "defines": { + "__PLUS4__": 1 + } } \ No newline at end of file diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index 210bbe9aa..1860e118a 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -42,6 +42,16 @@ public class TestPrograms { public TestPrograms() { } + //@Test + //public void testPlus4Define() throws IOException, URISyntaxException { + // compileAndCompare("plus4-define.c", log()); + //} + + @Test + public void testIncludeDefine() throws IOException, URISyntaxException { + compileAndCompare("include-define.c"); + } + @Test public void testStructPointerInts() throws IOException, URISyntaxException { compileAndCompare("struct-pointer-ints.c"); @@ -82,8 +92,6 @@ public class TestPrograms { compileAndCompare("stars-1.c"); } - - /* TODO: Add support for var*var @Test public void testMultiply3() throws IOException, URISyntaxException { @@ -4314,7 +4322,7 @@ public class TestPrograms { files.add(filePath); Program program = compiler.getProgram(); TargetPlatform.setTargetPlatform(TargetPlatform.DEFAULT_NAME, filePath, program, null); - compiler.compile(files, null); + compiler.compile(files, program.getTargetPlatform().getDefines()); compileAsm(fileName, program); boolean success = true; ReferenceHelper helper = new ReferenceHelperFolder(refPath); diff --git a/src/test/kc/include-define-sub.h b/src/test/kc/include-define-sub.h new file mode 100644 index 000000000..fb19417f8 --- /dev/null +++ b/src/test/kc/include-define-sub.h @@ -0,0 +1,4 @@ +// Test including a files with a #define and using it +// This file is included and contains a #define + +#define DEFX char x='a' diff --git a/src/test/kc/include-define.c b/src/test/kc/include-define.c new file mode 100644 index 000000000..3de6bb8f4 --- /dev/null +++ b/src/test/kc/include-define.c @@ -0,0 +1,8 @@ +// Test including a files with a #define and using it + +#include "include-define-sub.h" +DEFX; +char * const SCREEN = 0x0400; +void main() { + SCREEN[0] = x; +} \ No newline at end of file diff --git a/src/test/ref/include-define.asm b/src/test/ref/include-define.asm new file mode 100644 index 000000000..2179c67a8 --- /dev/null +++ b/src/test/ref/include-define.asm @@ -0,0 +1,13 @@ +// Test including a files with a #define and using it +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + .const x = 'a' + .label SCREEN = $400 +main: { + // SCREEN[0] = x + lda #x + sta SCREEN + // } + rts +} diff --git a/src/test/ref/include-define.cfg b/src/test/ref/include-define.cfg new file mode 100644 index 000000000..c29f498a8 --- /dev/null +++ b/src/test/ref/include-define.cfg @@ -0,0 +1,17 @@ +@begin: scope:[] from + [0] phi() + to:@1 +@1: scope:[] from @begin + [1] phi() + [2] call main + to:@end +@end: scope:[] from @1 + [3] phi() + +(void()) main() +main: scope:[main] from @1 + [4] *((const nomodify byte*) SCREEN) ← (const byte) x + to:main::@return +main::@return: scope:[main] from main + [5] return + to:@return diff --git a/src/test/ref/include-define.log b/src/test/ref/include-define.log new file mode 100644 index 000000000..9a1d12c9c --- /dev/null +++ b/src/test/ref/include-define.log @@ -0,0 +1,221 @@ + +CONTROL FLOW GRAPH SSA +@begin: scope:[] from + to:@1 + +(void()) main() +main: scope:[main] from @1 + *((const nomodify byte*) SCREEN + (number) 0) ← (const byte) x + to:main::@return +main::@return: scope:[main] from main + return + to:@return +@1: scope:[] from @begin + call main + to:@2 +@2: scope:[] from @1 + to:@end +@end: scope:[] from @2 + +SYMBOL TABLE SSA +(label) @1 +(label) @2 +(label) @begin +(label) @end +(const nomodify byte*) SCREEN = (byte*)(number) $400 +(void()) main() +(label) main::@return +(const byte) x = (byte) 'a' + +Adding number conversion cast (unumber) 0 in *((const nomodify byte*) SCREEN + (number) 0) ← (const byte) x +Successful SSA optimization PassNAddNumberTypeConversions +Simplifying constant pointer cast (byte*) 1024 +Simplifying constant integer cast 0 +Successful SSA optimization PassNCastSimplification +Finalized unsigned number type (byte) 0 +Successful SSA optimization PassNFinalizeNumberTypeConversions +Simplifying expression containing zero SCREEN in [0] *((const nomodify byte*) SCREEN + (byte) 0) ← (const byte) x +Successful SSA optimization PassNSimplifyExpressionWithZero +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @2 +Adding NOP phi() at start of @end +CALL GRAPH +Calls in [] to main:2 + +Created 0 initial phi equivalence classes +Coalesced down to 0 phi equivalence classes +Culled Empty Block (label) @2 +Adding NOP phi() at start of @begin +Adding NOP phi() at start of @1 +Adding NOP phi() at start of @end + +FINAL CONTROL FLOW GRAPH +@begin: scope:[] from + [0] phi() + to:@1 +@1: scope:[] from @begin + [1] phi() + [2] call main + to:@end +@end: scope:[] from @1 + [3] phi() + +(void()) main() +main: scope:[main] from @1 + [4] *((const nomodify byte*) SCREEN) ← (const byte) x + to:main::@return +main::@return: scope:[main] from main + [5] return + to:@return + + +VARIABLE REGISTER WEIGHTS +(void()) main() + +Initial phi equivalence classes +Complete equivalence classes + +INITIAL ASM +Target platform is c64basic / MOS6502X + // File Comments +// Test including a files with a #define and using it + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .const x = 'a' + .label SCREEN = $400 + // @begin +__bbegin: + // [1] phi from @begin to @1 [phi:@begin->@1] +__b1_from___bbegin: + jmp __b1 + // @1 +__b1: + // [2] call main + jsr main + // [3] phi from @1 to @end [phi:@1->@end] +__bend_from___b1: + jmp __bend + // @end +__bend: + // main +main: { + // [4] *((const nomodify byte*) SCREEN) ← (const byte) x -- _deref_pbuc1=vbuc2 + lda #x + sta SCREEN + jmp __breturn + // main::@return + __breturn: + // [5] return + rts +} + // File Data + +REGISTER UPLIFT POTENTIAL REGISTERS +Statement [4] *((const nomodify byte*) SCREEN) ← (const byte) x [ ] ( main:2 [ ] { } ) always clobbers reg byte a + +REGISTER UPLIFT SCOPES +Uplift Scope [main] +Uplift Scope [] + +Uplifting [main] best 27 combination +Uplifting [] best 27 combination + +ASSEMBLER BEFORE OPTIMIZATION + // File Comments +// Test including a files with a #define and using it + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .const x = 'a' + .label SCREEN = $400 + // @begin +__bbegin: + // [1] phi from @begin to @1 [phi:@begin->@1] +__b1_from___bbegin: + jmp __b1 + // @1 +__b1: + // [2] call main + jsr main + // [3] phi from @1 to @end [phi:@1->@end] +__bend_from___b1: + jmp __bend + // @end +__bend: + // main +main: { + // [4] *((const nomodify byte*) SCREEN) ← (const byte) x -- _deref_pbuc1=vbuc2 + lda #x + sta SCREEN + jmp __breturn + // main::@return + __breturn: + // [5] return + rts +} + // File Data + +ASSEMBLER OPTIMIZATIONS +Removing instruction jmp __b1 +Removing instruction jmp __bend +Removing instruction jmp __breturn +Succesful ASM optimization Pass5NextJumpElimination +Removing instruction __b1_from___bbegin: +Removing instruction __b1: +Removing instruction __bend_from___b1: +Succesful ASM optimization Pass5RedundantLabelElimination +Removing instruction __bbegin: +Removing instruction __bend: +Removing instruction __breturn: +Succesful ASM optimization Pass5UnusedLabelElimination +Removing instruction jsr main +Succesful ASM optimization Pass5SkipBegin + +FINAL SYMBOL TABLE +(label) @1 +(label) @begin +(label) @end +(const nomodify byte*) SCREEN = (byte*) 1024 +(void()) main() +(label) main::@return +(const byte) x = (byte) 'a' + + + +FINAL ASSEMBLER +Score: 12 + + // File Comments +// Test including a files with a #define and using it + // Upstart +.pc = $801 "Basic" +:BasicUpstart(main) +.pc = $80d "Program" + // Global Constants & labels + .const x = 'a' + .label SCREEN = $400 + // @begin + // [1] phi from @begin to @1 [phi:@begin->@1] + // @1 + // [2] call main + // [3] phi from @1 to @end [phi:@1->@end] + // @end + // main +main: { + // SCREEN[0] = x + // [4] *((const nomodify byte*) SCREEN) ← (const byte) x -- _deref_pbuc1=vbuc2 + lda #x + sta SCREEN + // main::@return + // } + // [5] return + rts +} + // File Data + diff --git a/src/test/ref/include-define.sym b/src/test/ref/include-define.sym new file mode 100644 index 000000000..6a46f414b --- /dev/null +++ b/src/test/ref/include-define.sym @@ -0,0 +1,8 @@ +(label) @1 +(label) @begin +(label) @end +(const nomodify byte*) SCREEN = (byte*) 1024 +(void()) main() +(label) main::@return +(const byte) x = (byte) 'a' +