1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-12-26 03:32:23 +00:00

Added support for compiling multiple C-files using a single command line. Closes #382

This commit is contained in:
jespergravgaard 2020-04-11 13:03:36 +02:00
parent f42a921d2b
commit 1c59ad61fd
12 changed files with 144 additions and 79 deletions

View File

@ -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<Path> 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;
}

View File

@ -37,8 +37,8 @@ import java.util.stream.Collectors;
)
public class KickC implements Callable<Void> {
@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<Path> 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<Path> libDir = null;
@ -46,8 +46,8 @@ public class KickC implements Callable<Void> {
@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<Void> {
}
}
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<Void> {
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<Void> {
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<Void> {
}
// 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<Void> {
// 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<Void> {
// 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);

View File

@ -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<String> 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;
}
/**

View File

@ -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<String> 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);
}
}

View File

@ -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<Token> tokens = new ArrayList<>();
tokens.add(token);
addSource(new ListTokenSource(tokens));
addSourceFirst(new ListTokenSource(tokens));
return token;
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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;

View File

@ -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;

View File

@ -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()) {

View File

@ -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<Path> files = new ArrayList<>();
final Path filePath = Paths.get(fileName);
files.add(filePath);
Program program = compiler.compile(files);
compileAsm(fileName, program);