diff --git a/src/main/java/dk/camelot64/kickc/Compiler.java b/src/main/java/dk/camelot64/kickc/Compiler.java index 6b5114964..58d6eec07 100644 --- a/src/main/java/dk/camelot64/kickc/Compiler.java +++ b/src/main/java/dk/camelot64/kickc/Compiler.java @@ -8,6 +8,7 @@ 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 org.antlr.v4.runtime.RuleContext; @@ -137,11 +138,16 @@ public class Compiler { program.getImportPaths().add(path); } - public Program compile(String fileName) { - if(fileName.endsWith(".kc")) { - fileName = fileName.substring(0, fileName.length() - 3); + public Program compile(List files) { + if(files.size()==0) + throw new CompileError("Error! You must supply at least one file to compile!"); + + final Path primaryFile = files.get(0); + String primaryFileName = primaryFile.toString(); + if(primaryFileName.endsWith(".kc")) { + primaryFileName = primaryFileName.substring(0, primaryFileName.length() - 3); } - program.setFileName(fileName); + program.setPrimaryFileName(primaryFileName); try { Path currentPath = new File(".").toPath(); @@ -150,7 +156,13 @@ public class Compiler { } program.setStatementSequence(new StatementSequence()); CParser cParser = new CParser(program); - KickCParser.FileContext cFileContext = cParser.loadAndParseCFile(fileName, currentPath); + for(Path file : files) { + final KickCLexer fileLexer = cParser.loadCFile(file.toString(), currentPath); + cParser.addSourceLast(fileLexer); + } + + // Parse the files + KickCParser.FileContext cFileContext = cParser.getParser().file(); if(variableBuilderConfig == null) { VariableBuilderConfig config = new VariableBuilderConfig(); @@ -159,7 +171,7 @@ public class Compiler { this.variableBuilderConfig = config; } - if(callingConvention==null) { + if(callingConvention == null) { callingConvention = Procedure.CallingConvention.PHI_CALL; } @@ -321,14 +333,26 @@ public class Compiler { optimizations.add(new PassNTypeIdSimplification(program)); optimizations.add(new PassNSizeOfSimplification(program)); optimizations.add(new PassNStatementIndices(program)); - optimizations.add(() -> { program.clearVariableReferenceInfos(); return false; }); + optimizations.add(() -> { + program.clearVariableReferenceInfos(); + return false; + }); optimizations.add(new Pass2UnaryNotSimplification(program)); optimizations.add(new Pass2AliasElimination(program)); optimizations.add(new Pass2IdenticalPhiElimination(program)); optimizations.add(new Pass2DuplicateRValueIdentification(program)); - optimizations.add(() -> { program.clearStatementIndices(); return false; }); - optimizations.add(() -> { program.clearVariableReferenceInfos();return false; }); - optimizations.add(() -> { program.clearStatementInfos(); return false; }); + optimizations.add(() -> { + program.clearStatementIndices(); + return false; + }); + optimizations.add(() -> { + program.clearVariableReferenceInfos(); + return false; + }); + optimizations.add(() -> { + program.clearStatementInfos(); + return false; + }); optimizations.add(new PassNStatementIndices(program)); optimizations.add(new Pass2ConditionalJumpSimplification(program)); optimizations.add(new Pass2ConditionalAndOrRewriting(program)); @@ -352,10 +376,19 @@ public class Compiler { optimizations.add(new Pass2EliminateUnusedBlocks(program)); if(enableLoopHeadConstant) { optimizations.add(new PassNStatementIndices(program)); - optimizations.add(() -> { program.clearDominators(); return false; }); - optimizations.add(() -> { program.clearLoopSet(); return false; }); + optimizations.add(() -> { + program.clearDominators(); + return false; + }); + optimizations.add(() -> { + program.clearLoopSet(); + return false; + }); optimizations.add(new Pass2LoopHeadConstantIdentification(program)); - optimizations.add(() -> { program.clearStatementIndices(); return false; }); + optimizations.add(() -> { + program.clearStatementIndices(); + return false; + }); } return optimizations; } diff --git a/src/main/java/dk/camelot64/kickc/KickC.java b/src/main/java/dk/camelot64/kickc/KickC.java index 658e8cfea..134a18db1 100644 --- a/src/main/java/dk/camelot64/kickc/KickC.java +++ b/src/main/java/dk/camelot64/kickc/KickC.java @@ -37,8 +37,8 @@ import java.util.stream.Collectors; ) public class KickC implements Callable { - @CommandLine.Parameters(index = "0", arity = "0..1", description = "The KickC source file to compile.") - private Path kcFile = null; + @CommandLine.Parameters(index = "0", arity = "0..n", description = "The KickC source files to compile.") + private List kcFiles = null; @CommandLine.Option(names = {"-I", "-libdir"}, description = "Path to a library folder, where the compiler looks for included files. This option can be repeated to add multiple library folders.") private List libDir = null; @@ -46,8 +46,8 @@ public class KickC implements Callable { @CommandLine.Option(names = {"-F", "-fragmentdir"}, description = "Path to the ASM fragment folder, where the compiler looks for ASM fragments.") private Path fragmentDir = null; - @CommandLine.Option(names = {"-o", "-output"}, description = "Name of the output assembler file. By default it is the same as the input file with extension .asm") - private String asmFileName = null; + @CommandLine.Option(names = {"-o", "-output"}, description = "Name of the output file. By default it is the same as the first input file with the proper extension.") + private String outputFileName = null; @CommandLine.Option(names = {"-odir"}, description = "Path to the output folder, where the compiler places all generated files. By default the folder of the output file is used.") private Path outputDir = null; @@ -238,11 +238,12 @@ public class KickC implements Callable { } } - if(kcFile != null) { + if(kcFiles != null && !kcFiles.isEmpty()) { - String fileBaseName = getFileBaseName(kcFile); + final Path primaryKcFile = kcFiles.get(0); + String primaryFileBaseName = getFileBaseName(primaryKcFile); - Path kcFileDir = kcFile.getParent(); + Path kcFileDir = primaryKcFile.getParent(); if(kcFileDir == null) { kcFileDir = FileSystems.getDefault().getPath("."); } @@ -254,8 +255,15 @@ public class KickC implements Callable { Files.createDirectory(outputDir); } - if(asmFileName == null) { - asmFileName = fileBaseName + ".asm"; + String outputFileNameBase; + if(outputFileName == null) { + outputFileNameBase = primaryFileBaseName; + } else { + final int extensionIdx = outputFileName.lastIndexOf('.'); + if(extensionIdx>0) + outputFileNameBase = outputFileName.substring(0, extensionIdx); + else + outputFileNameBase = outputFileName; } if(optimizeNoUplift) { @@ -317,16 +325,19 @@ public class KickC implements Callable { compiler.setCallingConvention(callingConvention); } - System.out.println("Compiling " + kcFile); + StringBuilder kcFileNames = new StringBuilder(); + kcFiles.stream().forEach(path -> kcFileNames.append(path.toString()).append(" ")); + System.out.println("Compiling " + kcFileNames); Program program = null; try { - program = compiler.compile(kcFile.toString()); + program = compiler.compile(kcFiles); } catch(CompileError e) { // Print the error and exit with compile error System.err.println(e.getMessage()); System.exit(COMPILE_ERROR); } + String asmFileName = outputFileNameBase + ".asm"; Path asmPath = outputDir.resolve(asmFileName); System.out.println("Writing asm file " + asmPath); FileOutputStream asmOutputStream = new FileOutputStream(asmPath.toFile()); @@ -352,9 +363,10 @@ public class KickC implements Callable { } // Assemble the asm-file if instructed - Path prgPath = outputDir.resolve(fileBaseName + ".prg"); + String prgFileName = outputFileNameBase +".prg"; + Path prgPath = outputDir.resolve(prgFileName); if(assemble || execute || debug) { - Path kasmLogPath = outputDir.resolve(fileBaseName + ".klog"); + Path kasmLogPath = outputDir.resolve(outputFileNameBase + ".klog"); System.out.println("Assembling to " + prgPath.toString()); String[] assembleCommand = {asmPath.toString(), "-log", kasmLogPath.toString(), "-o", prgPath.toString(), "-vicesymbols", "-showmem", "-debugdump"}; if(verbose) { @@ -385,7 +397,7 @@ public class KickC implements Callable { // Debug the prg-file if instructed if(debug) { System.out.println("Debugging " + prgPath); - Path viceSymbolsPath = outputDir.resolve(fileBaseName + ".vs"); + Path viceSymbolsPath = outputDir.resolve(outputFileNameBase + ".vs"); String debugCommand = "C64Debugger " + "-symbols " + viceSymbolsPath + " -wait 2500" + " -prg " + prgPath.toString(); if(verbose) { System.out.println("Debugging command: " + debugCommand); @@ -397,7 +409,7 @@ public class KickC implements Callable { // Execute the prg-file if instructed if(execute) { System.out.println("Executing " + prgPath); - Path viceSymbolsPath = outputDir.resolve(fileBaseName + ".vs"); + Path viceSymbolsPath = outputDir.resolve(outputFileNameBase + ".vs"); String executeCommand = "x64sc " + "-moncommands " + viceSymbolsPath + " " + prgPath.toString(); if(verbose) { System.out.println("Executing command: " + executeCommand); diff --git a/src/main/java/dk/camelot64/kickc/model/Program.java b/src/main/java/dk/camelot64/kickc/model/Program.java index 0ee75e0b1..792ad559d 100644 --- a/src/main/java/dk/camelot64/kickc/model/Program.java +++ b/src/main/java/dk/camelot64/kickc/model/Program.java @@ -19,8 +19,8 @@ public class Program { /** The log containing information about the compilation process. */ private CompileLog log; - /** The name of the file being compiled. PASS 0-5 (STATIC) */ - private String fileName; + /** The name of the primary file being compiled. PASS 0-5 (STATIC) */ + private String primaryFileName; /** Paths used for importing files. PASS 0 (STATIC) */ private List importPaths; /** Imported files. PASS 0 (STATIC) */ @@ -460,12 +460,12 @@ public class Program { } - public void setFileName(String fileName) { - this.fileName = fileName; + public void setPrimaryFileName(String primaryFileName) { + this.primaryFileName = primaryFileName; } - public String getFileName() { - return fileName; + public String getPrimaryFileName() { + return primaryFileName; } /** diff --git a/src/main/java/dk/camelot64/kickc/parser/CParser.java b/src/main/java/dk/camelot64/kickc/parser/CParser.java index c93c4bb07..2aba8803b 100644 --- a/src/main/java/dk/camelot64/kickc/parser/CParser.java +++ b/src/main/java/dk/camelot64/kickc/parser/CParser.java @@ -97,19 +97,6 @@ public class CParser { return tokenStream; } - /** - * Load initial C-file and start parsing it. - * This may recursively load other C-files (if they are imported) - * - * @param fileName The file name to look for (in the search path) - * @param currentPath The current path (searched before the search path) - * @return The parse result - */ - public KickCParser.FileContext loadAndParseCFile(String fileName, Path currentPath) { - loadCFile(fileName, currentPath); - return this.parser.file(); - } - /** * Get the C parser * @@ -149,23 +136,25 @@ public class CParser { /** * Loads a C-file (if it has not already been loaded). + * * The C-file is inserted into the C token stream at the current parse-point - so the parser will parse the entire content of the file before moving on. * * @param fileName The file name of the file */ - public void loadCFile(String fileName, boolean isSystem) { + public void includeCFile(String fileName, boolean isSystem) { final Path currentSourceFolderPath = isSystem ? null : getCurrentSourceFolderPath(); - loadCFile(fileName, currentSourceFolderPath); + final KickCLexer lexer = loadCFile(fileName, currentSourceFolderPath); + addSourceFirst(lexer); } /** * Loads a C-file (if it has not already been loaded). - * The C-file is inserted into the C token stream at the current parse-point - so the parser will parse the entire content of the file before moving on. * * @param fileName The file name of the file * @param currentPath The path of the current folder (searched before the search path). + * @return The lexer to be inserted into the source-list using onw of the {@link #addSourceFirst(KickCLexer)} methods. */ - private void loadCFile(String fileName, Path currentPath) { + public KickCLexer loadCFile(String fileName, Path currentPath) { try { if(fileName.startsWith("\"") || fileName.startsWith("<")) { fileName = fileName.substring(1, fileName.length() - 1); @@ -176,7 +165,7 @@ public class CParser { File file = SourceLoader.loadFile(fileName, currentPath, program); List imported = program.getImported(); if(imported.contains(file.getAbsolutePath())) { - return; + return null; } final CharStream fileStream = CharStreams.fromPath(file.toPath().toAbsolutePath()); imported.add(file.getAbsolutePath()); @@ -184,21 +173,22 @@ public class CParser { program.getLog().append("PARSING " + file.getPath().replace("\\", "/")); program.getLog().append(fileStream.toString()); } - KickCLexer lexer = addSource(fileStream); + KickCLexer lexer = makeLexer(fileStream); CFile cFile = new CFile(file, lexer); cFiles.put(file.getAbsolutePath(), cFile); + return lexer; } catch(IOException e) { throw new CompileError("Error parsing file " + fileName, e); } } /** - * Add source code at the start of the token stream being parsed. + * Make a lexer for a char stream * - * @param charStream The char stream containing the source code to add - * @return The lexer for reading the source tokens of the added source + * @param charStream the char stream + * @return the lexer */ - public KickCLexer addSource(CharStream charStream) { + public KickCLexer makeLexer(CharStream charStream) { KickCLexer lexer = new KickCLexer(charStream, this); lexer.addErrorListener(new BaseErrorListener() { @Override @@ -212,8 +202,29 @@ public class CParser { throw new CompileError("Error parsing file " + charStream.getSourceName() + "\n - Line: " + line + "\n - Message: " + msg); } }); - cTokenSource.addSource(lexer); return lexer; } + + /** + * Add source code at the start of the token stream being parsed. + * + * @param lexer The lexer for reading the source tokens + */ + public void addSourceFirst(KickCLexer lexer) { + if(lexer != null) + cTokenSource.addSourceFirst(lexer); + } + + /** + * Add source code at the end of the token stream being parsed. + * + * @param lexer The lexer for reading the source tokens + */ + public void addSourceLast(KickCLexer lexer) { + if(lexer != null) + cTokenSource.addSourceLast(lexer); + } + + } diff --git a/src/main/java/dk/camelot64/kickc/parser/CTokenSource.java b/src/main/java/dk/camelot64/kickc/parser/CTokenSource.java index eeb0d55be..1ad209e0f 100644 --- a/src/main/java/dk/camelot64/kickc/parser/CTokenSource.java +++ b/src/main/java/dk/camelot64/kickc/parser/CTokenSource.java @@ -21,19 +21,28 @@ public class CTokenSource implements TokenSource { public CTokenSource(TokenSource tokenSource) { this.subSources = new LinkedList<>(); - addSource(tokenSource); + addSourceFirst(tokenSource); } /** - * Pushes a token source at the current location ). + * Pushes a token source at the current location. * The pushed source will immediately be used for tokens and only when it is exhausted will tokens resume from the current source * * @param source The source to push */ - public void addSource(TokenSource source) { + public void addSourceFirst(TokenSource source) { subSources.addFirst(source); } + /** + * Adds a token source at the end of the source. + * + * @param source The source to add + */ + public void addSourceLast(TokenSource source) { + subSources.addLast(source); + } + public TokenSource getCurrentSource() { return subSources.peekFirst(); } @@ -49,7 +58,7 @@ public class CTokenSource implements TokenSource { // And push it back to the front of the stack final ArrayList tokens = new ArrayList<>(); tokens.add(token); - addSource(new ListTokenSource(tokens)); + addSourceFirst(new ListTokenSource(tokens)); return token; } diff --git a/src/main/java/dk/camelot64/kickc/parser/KickCLexer.g4 b/src/main/java/dk/camelot64/kickc/parser/KickCLexer.g4 index 95f5db47d..215cfdad6 100644 --- a/src/main/java/dk/camelot64/kickc/parser/KickCLexer.g4 +++ b/src/main/java/dk/camelot64/kickc/parser/KickCLexer.g4 @@ -222,8 +222,8 @@ ASM_COMMENT_BLOCK : '/*' .*? '*/' -> channel(2); // MODE FOR INCLUDE FILES mode IMPORT_MODE; -IMPORT_SYSTEMFILE : '<' [a-zA-Z0-9_./\\\-]+ '>' { popMode();cParser.loadCFile(getText(), true); } ; -IMPORT_LOCALFILE : '"' ('\\"' | ~'"')* '"' { popMode(); cParser.loadCFile(getText(), false); } ; +IMPORT_SYSTEMFILE : '<' [a-zA-Z0-9_./\\\-]+ '>' { popMode();cParser.includeCFile(getText(), true); } ; +IMPORT_LOCALFILE : '"' ('\\"' | ~'"')* '"' { popMode(); cParser.includeCFile(getText(), false); } ; // White space on hidden channel 1 IMPORT_WS : [ \t\r\n\u00a0]+ -> channel(1); // Comments on hidden channel 2 diff --git a/src/main/java/dk/camelot64/kickc/parser/KickCLexer.java b/src/main/java/dk/camelot64/kickc/parser/KickCLexer.java index 83c4eb797..e2dd79160 100644 --- a/src/main/java/dk/camelot64/kickc/parser/KickCLexer.java +++ b/src/main/java/dk/camelot64/kickc/parser/KickCLexer.java @@ -301,14 +301,14 @@ public class KickCLexer extends Lexer { private void IMPORT_SYSTEMFILE_action(RuleContext _localctx, int actionIndex) { switch (actionIndex) { case 7: - popMode();cParser.loadCFile(getText(), true); + popMode();cParser.includeCFile(getText(), true); break; } } private void IMPORT_LOCALFILE_action(RuleContext _localctx, int actionIndex) { switch (actionIndex) { case 8: - popMode(); cParser.loadCFile(getText(), false); + popMode(); cParser.includeCFile(getText(), false); break; } } diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java b/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java index 2061ce226..db27cd8a7 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass4CodeGeneration.java @@ -80,7 +80,6 @@ public class Pass4CodeGeneration { asm.startChunk(currentScope, null, "File Comments"); generateComments(asm, program.getFileComments()); - String outputPrgPath = new File(program.getFileName()).getName() + ".prg"; asm.startChunk(currentScope, null, "Upstart"); Number programPc = program.getProgramPc(); if(TargetPlatform.C64BASIC.equals(program.getTargetPlatform())) { @@ -97,7 +96,7 @@ public class Pass4CodeGeneration { useSegments = true; String linkScriptBody = program.getLinkScriptBody(); if(linkScriptBody != null) { - String outputFileName = new File(program.getFileName()).getName(); + String outputFileName = new File(program.getPrimaryFileName()).getName(); linkScriptBody = linkScriptBody.replace("%O", outputFileName); linkScriptBody = linkScriptBody.replace("%_O", outputFileName.toLowerCase()); linkScriptBody = linkScriptBody.replace("%^O", outputFileName.toUpperCase()); diff --git a/src/main/java/dk/camelot64/kickc/passes/Pass5FixLongBranches.java b/src/main/java/dk/camelot64/kickc/passes/Pass5FixLongBranches.java index d7e970f98..b92eae40f 100644 --- a/src/main/java/dk/camelot64/kickc/passes/Pass5FixLongBranches.java +++ b/src/main/java/dk/camelot64/kickc/passes/Pass5FixLongBranches.java @@ -60,12 +60,12 @@ public class Pass5FixLongBranches extends Pass5AsmOptimization { } // Generate the ASM file - String fileName = getProgram().getFileName(); + String outputFileName = getProgram().getPrimaryFileName(); try { //getLog().append("ASM"); //getLog().append(getProgram().getAsm().toString(false, true)); - writeOutputFile(fileName, ".asm", getProgram().getAsm().toString(new AsmProgram.AsmPrintState(false), null)); + writeOutputFile(outputFileName, ".asm", getProgram().getAsm().toString(new AsmProgram.AsmPrintState(false), null)); // Copy Resource Files for(Path asmResourceFile : getProgram().getAsmResourceFiles()) { @@ -78,8 +78,8 @@ public class Pass5FixLongBranches extends Pass5AsmOptimization { } // Compile using KickAssembler - catch the output in a String - File asmFile = getTmpFile(fileName, ".asm"); - File asmPrgFile = getTmpFile(fileName, ".prg"); + File asmFile = getTmpFile(outputFileName, ".asm"); + File asmPrgFile = getTmpFile(outputFileName, ".prg"); ByteArrayOutputStream kickAssOut = new ByteArrayOutputStream(); System.setOut(new PrintStream(kickAssOut)); int asmRes = -1; diff --git a/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java b/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java index 90974c341..0833c783d 100644 --- a/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java +++ b/src/main/java/dk/camelot64/kickc/preprocessor/CPreprocessor.java @@ -270,7 +270,7 @@ public class CPreprocessor implements TokenSource { addTokenToExpandedBody(macroBodyToken, macroNameToken, expandedBody); } } - cTokenSource.addSource(new ListTokenSource(expandedBody)); + cTokenSource.addSourceFirst(new ListTokenSource(expandedBody)); return true; } return false; 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 df86bd37e..09fe93220 100644 --- a/src/test/java/dk/camelot64/kickc/parsing/macros/TestPreprocessor.java +++ b/src/test/java/dk/camelot64/kickc/parsing/macros/TestPreprocessor.java @@ -8,9 +8,6 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CodePointCharStream; import org.junit.Test; -import java.io.IOException; -import java.net.URISyntaxException; - import static junit.framework.TestCase.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -226,7 +223,7 @@ public class TestPreprocessor { private String parse(String program) { CodePointCharStream charStream = CharStreams.fromString(program); CParser cParser = new CParser(null); - cParser.addSource(charStream); + cParser.addSourceFirst(cParser.makeLexer(charStream)); KickCParser.StmtSeqContext stmtSeqContext = cParser.getParser().stmtSeq(); ProgramPrinter printVisitor = new ProgramPrinter(); printVisitor.visit(stmtSeqContext); @@ -241,7 +238,6 @@ public class TestPreprocessor { return out; } - @Override public Object visitStmtSeq(KickCParser.StmtSeqContext ctx) { for(KickCParser.StmtContext stmtContext : ctx.stmt()) { diff --git a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java index f165adefe..a57f0f496 100644 --- a/src/test/java/dk/camelot64/kickc/test/TestPrograms.java +++ b/src/test/java/dk/camelot64/kickc/test/TestPrograms.java @@ -21,6 +21,8 @@ import java.net.URISyntaxException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; import static junit.framework.TestCase.fail; import static org.junit.Assert.assertTrue; @@ -4022,7 +4024,10 @@ public class TestPrograms { if(upliftCombinations != null) { compiler.setUpliftCombinations(upliftCombinations); } - Program program = compiler.compile(fileName); + final ArrayList files = new ArrayList<>(); + final Path filePath = Paths.get(fileName); + files.add(filePath); + Program program = compiler.compile(files); compileAsm(fileName, program);