Browse Source

Many writer changes, other output fixes

* Improved Converter errors
* Added defgenerator
* Added SpaceBefore to improve Splice formatting
* Wrap boolean operators with parentheses to make them unambiguous
* Write process output to separate pipes instead of
stdin. Additionally, read process output separately, one by one. This
ensures output comes through sequentially, which is important for
multi-line errors (often output by clang)
* Writer now only writes files which have meaningful output (leaving
out header files if the module is all local, etc.)
* Writer now properly handles splices which contain output for the
header file
HotReloadingState
Macoy Madson 9 months ago
parent
commit
a630659df4
10 changed files with 447 additions and 229 deletions
  1. +36
    -30
      src/Converters.cpp
  2. +3
    -1
      src/Converters.hpp
  3. +13
    -4
      src/Evaluator.cpp
  4. +14
    -13
      src/EvaluatorEnums.hpp
  5. +119
    -7
      src/Generators.cpp
  6. +10
    -0
      src/OutputPreambles.cpp
  7. +2
    -0
      src/OutputPreambles.hpp
  8. +21
    -15
      src/RunProcess.cpp
  9. +210
    -147
      src/Writer.cpp
  10. +19
    -12
      test/Dependencies.cake

+ 36
- 30
src/Converters.cpp View File

@ -1,5 +1,6 @@
#include "Converters.hpp"
#include "Tokenizer.hpp"
#include "Utilities.hpp"
// TODO: safe version of strcat
@ -7,16 +8,17 @@
#include <stdio.h>
#include <string.h>
static bool writeCharToBuffer(char c, char** at, char* bufferStart, int bufferSize)
static bool writeCharToBuffer(char c, char** at, char* bufferStart, int bufferSize,
const Token& token)
{
**at = c;
++(*at);
char* endOfBuffer = bufferStart + bufferSize - 1;
if (*at >= endOfBuffer)
{
printf(
"\nError: lispNameStyleToCNameStyle(): buffer of size %d was too small. String will be "
"cut off\n",
ErrorAtTokenf(
token,
"lispNameStyleToCNameStyle(): buffer of size %d was too small. String will be cut off",
bufferSize);
*endOfBuffer = '\0';
return false;
@ -25,7 +27,8 @@ static bool writeCharToBuffer(char c, char** at, char* bufferStart, int bufferSi
return true;
}
static bool writeStringToBuffer(const char* str, char** at, char* bufferStart, int bufferSize)
static bool writeStringToBuffer(const char* str, char** at, char* bufferStart, int bufferSize,
const Token& token)
{
for (const char* c = str; *c != '\0'; ++c)
{
@ -34,10 +37,10 @@ static bool writeStringToBuffer(const char* str, char** at, char* bufferStart, i
char* endOfBuffer = bufferStart + bufferSize - 1;
if (*at >= endOfBuffer)
{
printf(
"\nError: lispNameStyleToCNameStyle(): buffer of size %d was too small. String "
"will be cut off\n",
bufferSize);
ErrorAtTokenf(token,
"lispNameStyleToCNameStyle(): buffer of size %d was too small. String "
"will be cut off",
bufferSize);
*(endOfBuffer) = '\0';
return false;
}
@ -47,7 +50,7 @@ static bool writeStringToBuffer(const char* str, char** at, char* bufferStart, i
}
void lispNameStyleToCNameStyle(NameStyleMode mode, const char* name, char* bufferOut,
int bufferOutSize)
int bufferOutSize, const Token& token)
{
bool upcaseNextCharacter = false;
bool isPlural = false;
@ -62,7 +65,7 @@ void lispNameStyleToCNameStyle(NameStyleMode mode, const char* name, char* buffe
switch (mode)
{
case NameStyleMode_Underscores:
if (!writeCharToBuffer('_', &bufferWrite, bufferOut, bufferOutSize))
if (!writeCharToBuffer('_', &bufferWrite, bufferOut, bufferOutSize, token))
return;
break;
case NameStyleMode_CamelCase:
@ -76,9 +79,9 @@ void lispNameStyleToCNameStyle(NameStyleMode mode, const char* name, char* buffe
upcaseNextCharacter = true;
break;
default:
printf(
"Error: lispNameStyleToCNameStyle() encountered unrecognized separator "
"mode\n");
ErrorAtToken(token,
"lispNameStyleToCNameStyle() encountered unrecognized separator "
"mode\n");
break;
}
}
@ -87,19 +90,20 @@ void lispNameStyleToCNameStyle(NameStyleMode mode, const char* name, char* buffe
// Special case for C++ namespaces: valid only if there are two in a row
if ((c == name && *c + 1 == ':') || (*(c + 1) == ':' || *(c - 1) == ':'))
{
if (!writeCharToBuffer(*c, &bufferWrite, bufferOut, bufferOutSize))
if (!writeCharToBuffer(*c, &bufferWrite, bufferOut, bufferOutSize, token))
return;
}
else
{
if (c == name)
printf(
"Warning: lispNameStyleToCNameStyle() received name starting with : which "
ErrorAtToken(
token,
"lispNameStyleToCNameStyle() received name starting with : which "
"wasn't a C++-style :: scope resolution operator; is a generator wrongly "
"interpreting a special symbol?\n");
requiredSymbolConversion = true;
if (!writeStringToBuffer("Colon", &bufferWrite, bufferOut, bufferOutSize))
if (!writeStringToBuffer("Colon", &bufferWrite, bufferOut, bufferOutSize, token))
return;
}
}
@ -107,12 +111,12 @@ void lispNameStyleToCNameStyle(NameStyleMode mode, const char* name, char* buffe
{
if (upcaseNextCharacter)
{
if (!writeCharToBuffer(toupper(*c), &bufferWrite, bufferOut, bufferOutSize))
if (!writeCharToBuffer(toupper(*c), &bufferWrite, bufferOut, bufferOutSize, token))
return;
}
else
{
if (!writeCharToBuffer(*c, &bufferWrite, bufferOut, bufferOutSize))
if (!writeCharToBuffer(*c, &bufferWrite, bufferOut, bufferOutSize, token))
return;
}
@ -126,37 +130,39 @@ void lispNameStyleToCNameStyle(NameStyleMode mode, const char* name, char* buffe
switch (*c)
{
case '+':
if (!writeStringToBuffer("Add", &bufferWrite, bufferOut, bufferOutSize))
if (!writeStringToBuffer("Add", &bufferWrite, bufferOut, bufferOutSize, token))
return;
break;
case '-':
if (!writeStringToBuffer("Sub", &bufferWrite, bufferOut, bufferOutSize))
if (!writeStringToBuffer("Sub", &bufferWrite, bufferOut, bufferOutSize, token))
return;
break;
case '*':
if (!writeStringToBuffer("Mul", &bufferWrite, bufferOut, bufferOutSize))
if (!writeStringToBuffer("Mul", &bufferWrite, bufferOut, bufferOutSize, token))
return;
break;
case '/':
if (!writeStringToBuffer("Div", &bufferWrite, bufferOut, bufferOutSize))
if (!writeStringToBuffer("Div", &bufferWrite, bufferOut, bufferOutSize, token))
return;
break;
case '%':
if (!writeStringToBuffer("Mod", &bufferWrite, bufferOut, bufferOutSize))
if (!writeStringToBuffer("Mod", &bufferWrite, bufferOut, bufferOutSize, token))
return;
break;
case '.':
// TODO: Decide how to handle object pathing
if (!writeStringToBuffer(".", &bufferWrite, bufferOut, bufferOutSize))
if (!writeStringToBuffer(".", &bufferWrite, bufferOut, bufferOutSize, token))
return;
break;
default:
printf(
"Error: While converting lisp-style %s to C-style, encountered character "
ErrorAtTokenf(
token,
"while converting lisp-style '%s' to C-style, encountered character "
"'%c' which has no conversion equivalent. It will be replaced with "
"'BadChar'\n",
"'BadChar'",
name, *c);
if (!writeStringToBuffer("BadChar", &bufferWrite, bufferOut, bufferOutSize))
if (!writeStringToBuffer("BadChar", &bufferWrite, bufferOut, bufferOutSize,
token))
return;
break;
}


+ 3
- 1
src/Converters.hpp View File

@ -2,6 +2,8 @@
#include "ConverterEnums.hpp"
struct Token;
struct NameStyleSettings
{
// In general, you should write C/C++ types as they appear in C/C++, because then ETAGS etc. can
@ -23,4 +25,4 @@ struct NameStyleSettings
// them. This also means you can use whatever style you want in Cakelisp, and you'll get valid C/C++
// generated (so long as your non-'-' strings match the other C/C++ names)
void lispNameStyleToCNameStyle(NameStyleMode mode, const char* name, char* bufferOut,
int bufferOutSize);
int bufferOutSize, const Token& token);

+ 13
- 4
src/Evaluator.cpp View File

@ -615,8 +615,16 @@ int BuildEvaluateReferences(EvaluatorEnvironment& environment, int& numErrorsOut
NameStyleSettings nameSettings;
WriterFormatSettings formatSettings;
WriterOutputSettings outputSettings = {};
outputSettings.sourceHeading = macroSourceHeading;
outputSettings.sourceFooter = macroSourceFooter;
if (definition->type == ObjectType_CompileTimeGenerator)
{
outputSettings.sourceHeading = generatorSourceHeading;
outputSettings.sourceFooter = generatorSourceFooter;
}
else
{
outputSettings.sourceHeading = macroSourceHeading;
outputSettings.sourceFooter = macroSourceFooter;
}
outputSettings.sourceCakelispFilename = sourceOutputName;
// Use the separate output prepared specifically for this compile-time object
if (!writeGeneratorOutput(*definition->output, nameSettings, formatSettings,
@ -671,7 +679,8 @@ int BuildEvaluateReferences(EvaluatorEnvironment& environment, int& numErrorsOut
if (buildObject.status != 0)
{
ErrorAtTokenf(*buildObject.definition->name,
"Failed to compile definition with status %d", buildObject.status);
"Failed to compile definition '%s' with status %d",
buildObject.definition->name->contents.c_str(), buildObject.status);
continue;
}
@ -734,7 +743,7 @@ int BuildEvaluateReferences(EvaluatorEnvironment& environment, int& numErrorsOut
char symbolNameBuffer[MAX_NAME_LENGTH] = {0};
lispNameStyleToCNameStyle(nameSettings.functionNameMode,
buildObject.definition->name->contents.c_str(), symbolNameBuffer,
sizeof(symbolNameBuffer));
sizeof(symbolNameBuffer), *buildObject.definition->name);
void* compileTimeFunction = getSymbolFromDynamicLibrary(builtLib, symbolNameBuffer);
if (!compileTimeFunction)
{


+ 14
- 13
src/EvaluatorEnums.hpp View File

@ -5,35 +5,36 @@ enum StringOutputModifierFlags
StringOutMod_None = 0,
StringOutMod_NewlineAfter = 1 << 0,
StringOutMod_SpaceAfter = 1 << 1,
StringOutMod_SpaceBefore = 1 << 2,
// There needs to be enough information to know what a thing is to pick the format, which is why
// this list is rather short
StringOutMod_ConvertTypeName = 1 << 2,
StringOutMod_ConvertFunctionName = 1 << 3,
StringOutMod_ConvertVariableName = 1 << 4,
StringOutMod_ConvertTypeName = 1 << 3,
StringOutMod_ConvertFunctionName = 1 << 4,
StringOutMod_ConvertVariableName = 1 << 5,
StringOutMod_SurroundWithQuotes = 1 << 5,
StringOutMod_SurroundWithQuotes = 1 << 6,
// Curly braces ({}) will be added for open and close, respectively
// These are also clues to the output formatter to indent all within, etc.
StringOutMod_OpenBlock = 1 << 6,
StringOutMod_CloseBlock = 1 << 7,
StringOutMod_OpenBlock = 1 << 7,
StringOutMod_CloseBlock = 1 << 8,
StringOutMod_OpenParen = 1 << 8,
StringOutMod_CloseParen = 1 << 9,
StringOutMod_OpenParen = 1 << 9,
StringOutMod_CloseParen = 1 << 10,
// In C, ';' with a new line
StringOutMod_EndStatement = 1 << 10,
StringOutMod_EndStatement = 1 << 11,
// ',', for lists of arguments, expressions (e.g. initializer lists), etc.
StringOutMod_ListSeparator = 1 << 11,
StringOutMod_ListSeparator = 1 << 12,
// Uses {} for initializer lists etc.
StringOutMod_OpenList = 1 << 12,
StringOutMod_CloseList = 1 << 13,
StringOutMod_OpenList = 1 << 13,
StringOutMod_CloseList = 1 << 14,
// Signals the Writer that it needs to splice in another output list
StringOutMod_Splice = 1 << 14,
StringOutMod_Splice = 1 << 15,
};
enum ImportLanguage


+ 119
- 7
src/Generators.cpp View File

@ -148,6 +148,8 @@ bool CIncludeGenerator(EvaluatorEnvironment& environment, const EvaluatorContext
.compare(cakeSuffix) == 0)
{
isCakeImport = true;
// TODO Only include the cakelisp file if it is actually needed. For example, macro-only
// .cake files will not have a header output at all
// TODO Make .hpp optional for C-only support
PrintfBuffer(includeBuffer, "#include \"%s.hpp\"", pathToken.contents.c_str());
@ -661,7 +663,7 @@ bool FunctionInvocationGenerator(EvaluatorEnvironment& environment, const Evalua
return true;
}
// Handles both uninitized and initilized variables as well as global and static
// Handles both uninitialized and initialized variables as well as global and static
// Module-local variables are automatically marked as static
bool VariableDeclarationGenerator(EvaluatorEnvironment& environment,
const EvaluatorContext& context, const std::vector<Token>& tokens,
@ -827,6 +829,7 @@ bool ArrayAccessGenerator(EvaluatorEnvironment& environment, const EvaluatorCont
return true;
}
// TODO Consider merging with defgenerator?
bool DefMacroGenerator(EvaluatorEnvironment& environment, const EvaluatorContext& context,
const std::vector<Token>& tokens, int startTokenIndex,
GeneratorOutput& output)
@ -913,6 +916,95 @@ bool DefMacroGenerator(EvaluatorEnvironment& environment, const EvaluatorContext
return true;
}
// Essentially the same as DefMacro, though I could see them diverging or merging
bool DefGeneratorGenerator(EvaluatorEnvironment& environment, const EvaluatorContext& context,
const std::vector<Token>& tokens, int startTokenIndex,
GeneratorOutput& output)
{
if (!ExpectEvaluatorScope("defgenerator", tokens[startTokenIndex], context,
EvaluatorScope_Module))
return false;
int endDefunTokenIndex = FindCloseParenTokenIndex(tokens, startTokenIndex);
int endTokenIndex = endDefunTokenIndex;
int startNameTokenIndex = startTokenIndex;
StripInvocation(startNameTokenIndex, endTokenIndex);
int nameIndex = startNameTokenIndex;
const Token& nameToken = tokens[nameIndex];
if (!ExpectTokenType("defgenerator", nameToken, TokenType_Symbol))
return false;
int argsIndex = nameIndex + 1;
if (!ExpectInInvocation("defgenerator expected arguments", tokens, argsIndex,
endDefunTokenIndex))
return false;
const Token& argsStart = tokens[argsIndex];
if (!ExpectTokenType("defgenerator", argsStart, TokenType_OpenParen))
return false;
// Will be cleaned up when the environment is destroyed
GeneratorOutput* compTimeOutput = new GeneratorOutput;
ObjectDefinition newGeneratorDef = {};
newGeneratorDef.name = &nameToken;
newGeneratorDef.type = ObjectType_CompileTimeGenerator;
// Let the reference required propagation step handle this
newGeneratorDef.isRequired = false;
newGeneratorDef.output = compTimeOutput;
if (!addObjectDefinition(environment, newGeneratorDef))
return false;
// TODO: It would be nice to support global vs. local generators
// This only really needs to be an environment distinction, not a code output distinction
// Generators will be found without headers thanks to dynamic linking
// bool isModuleLocal = tokens[startTokenIndex + 1].contents.compare("defgenerator-local") == 0;
// Generators must return success or failure
addStringOutput(compTimeOutput->source, "bool", StringOutMod_SpaceAfter,
&tokens[startTokenIndex]);
addStringOutput(compTimeOutput->source, nameToken.contents, StringOutMod_ConvertFunctionName,
&nameToken);
addLangTokenOutput(compTimeOutput->source, StringOutMod_OpenParen, &argsStart);
// Generators always receive the same arguments
// TODO: Output generator arguments with proper output calls
addStringOutput(compTimeOutput->source,
"EvaluatorEnvironment& environment, const EvaluatorContext& context, const "
"std::vector<Token>& tokens, int startTokenIndex, GeneratorOutput& output",
StringOutMod_None, &argsStart);
int endArgsIndex = FindCloseParenTokenIndex(tokens, argsIndex);
addLangTokenOutput(compTimeOutput->source, StringOutMod_CloseParen, &tokens[endArgsIndex]);
int startBodyIndex = endArgsIndex + 1;
addLangTokenOutput(compTimeOutput->source, StringOutMod_OpenBlock, &tokens[startBodyIndex]);
// Evaluate our body!
EvaluatorContext generatorBodyContext = context;
generatorBodyContext.scope = EvaluatorScope_Body;
generatorBodyContext.isMacroOrGeneratorDefinition = true;
// Let the reference required propagation step handle this
generatorBodyContext.isRequired = false;
generatorBodyContext.definitionName = &nameToken;
// TODO Remove this, we don't need it any more
StringOutput bodyDelimiterTemplate = {};
int numErrors =
EvaluateGenerateAll_Recursive(environment, generatorBodyContext, tokens, startBodyIndex,
bodyDelimiterTemplate, *compTimeOutput);
if (numErrors)
{
delete compTimeOutput;
return false;
}
addLangTokenOutput(compTimeOutput->source, StringOutMod_CloseBlock, &tokens[endTokenIndex]);
return true;
}
//
// C Statement generation
//
@ -930,6 +1022,7 @@ enum CStatementOperationType
CloseList,
Keyword,
KeywordNoSpace,
EndStatement,
// Evaluate argument(s)
@ -965,6 +1058,10 @@ bool cStatementOutput(EvaluatorEnvironment& environment, const EvaluatorContext&
addStringOutput(output.source, operation[i].keywordOrSymbol,
StringOutMod_SpaceAfter, &nameToken);
break;
case KeywordNoSpace:
addStringOutput(output.source, operation[i].keywordOrSymbol, StringOutMod_None,
&nameToken);
break;
case Splice:
{
if (operation[i].argumentIndex < 0)
@ -981,7 +1078,8 @@ bool cStatementOutput(EvaluatorEnvironment& environment, const EvaluatorContext&
bodyContext.scope = EvaluatorScope_ExpressionsOnly;
StringOutput spliceDelimiterTemplate = {};
spliceDelimiterTemplate.output = operation[i].keywordOrSymbol;
spliceDelimiterTemplate.modifiers = StringOutMod_SpaceAfter;
addModifierToStringOutput(spliceDelimiterTemplate, StringOutMod_SpaceBefore);
addModifierToStringOutput(spliceDelimiterTemplate, StringOutMod_SpaceAfter);
int numErrors = EvaluateGenerateAll_Recursive(environment, bodyContext, tokens,
startSpliceListIndex,
spliceDelimiterTemplate, output);
@ -1117,8 +1215,8 @@ bool CStatementGenerator(EvaluatorEnvironment& environment, const EvaluatorConte
{Expression, nullptr, 2},
{EndStatement, nullptr, -1}};
const CStatementOperation dereference[] = {{Keyword, "*", -1}, {Expression, nullptr, 1}};
const CStatementOperation addressOf[] = {{Keyword, "&", -1}, {Expression, nullptr, 1}};
const CStatementOperation dereference[] = {{KeywordNoSpace, "*", -1}, {Expression, nullptr, 1}};
const CStatementOperation addressOf[] = {{KeywordNoSpace, "&", -1}, {Expression, nullptr, 1}};
// Similar to progn, but doesn't necessarily mean things run in order (this doesn't add
// barriers or anything). It's useful both for making arbitrary scopes and for making if
@ -1127,9 +1225,22 @@ bool CStatementGenerator(EvaluatorEnvironment& environment, const EvaluatorConte
{OpenBlock, nullptr, -1}, {Body, nullptr, 1}, {CloseBlock, nullptr, -1}};
// https://www.tutorialspoint.com/cprogramming/c_operators.htm proved useful
const CStatementOperation booleanOr[] = {{Splice, "||", 1}};
const CStatementOperation booleanAnd[] = {{Splice, "&&", 1}};
const CStatementOperation booleanNot[] = {{Keyword, "!", -1}, {Expression, nullptr, 1}};
// These could probably be made smarter to not need all the redundant parentheses. For now I'll
// make it absolutely unambiguous
const CStatementOperation booleanOr[] = {
{OpenParen, nullptr, -1},
{Splice, "||", 1},
{CloseParen, nullptr, -1},
};
const CStatementOperation booleanAnd[] = {
{OpenParen, nullptr, -1},
{Splice, "&&", 1},
{CloseParen, nullptr, -1},
};
const CStatementOperation booleanNot[] = {{KeywordNoSpace, "!", -1},
{OpenParen, nullptr, -1},
{Expression, nullptr, 1},
{CloseParen, nullptr, -1}};
const CStatementOperation bitwiseOr[] = {{Splice, "|", 1}};
const CStatementOperation bitwiseAnd[] = {{Splice, "&", 1}};
@ -1287,6 +1398,7 @@ void importFundamentalGenerators(EvaluatorEnvironment& environment)
environment.generators["defun-local"] = DefunGenerator;
environment.generators["defmacro"] = DefMacroGenerator;
environment.generators["defgenerator"] = DefGeneratorGenerator;
environment.generators["var"] = VariableDeclarationGenerator;
environment.generators["global-var"] = VariableDeclarationGenerator;


+ 10
- 0
src/OutputPreambles.cpp View File

@ -9,6 +9,16 @@ const char* macroSourceHeading =
// Close extern "C" block
const char* macroSourceFooter = "}\n";
// Must use extern "C" for dynamic symbols, because otherwise name mangling makes things hard
const char* generatorSourceHeading =
"#include \"Evaluator.hpp\""
"\n#include \"EvaluatorEnums.hpp\""
"\n#include \"Tokenizer.hpp\""
"\n#include \"GeneratorHelpers.hpp\""
"\nextern \"C\"\n{\n";
// Close extern "C" block
const char* generatorSourceFooter = "}\n";
const char* generatedSourceHeading = nullptr;
const char* generatedSourceFooter = nullptr;
const char* generatedHeaderHeading = "#pragma once\n";


+ 2
- 0
src/OutputPreambles.hpp View File

@ -3,6 +3,8 @@
// All generated macro source files automatically have this
extern const char* macroSourceHeading;
extern const char* macroSourceFooter;
extern const char* generatorSourceHeading;
extern const char* generatorSourceFooter;
extern const char* generatedSourceHeading;
extern const char* generatedSourceFooter;


+ 21
- 15
src/RunProcess.cpp View File

@ -24,6 +24,7 @@ struct Subprocess
{
int* statusOut;
ProcessId processId;
int pipeReadFileDescriptor;
};
static std::vector<Subprocess> s_subprocesses;
@ -93,15 +94,15 @@ int runProcess(const RunProcessArguments& arguments, int* statusOut)
// Parent
else
{
if (dup2(pipeFileDescriptors[PipeRead], STDIN_FILENO) == -1)
{
perror("RunProcess: ");
return 1;
}
// if (dup2(pipeFileDescriptors[PipeRead], STDIN_FILENO) == -1)
// {
// perror("RunProcess: ");
// return 1;
// }
// Only read
close(pipeFileDescriptors[PipeWrite]);
// printf("Created child process %d\n", pid);
s_subprocesses.push_back({statusOut, pid});
s_subprocesses.push_back({statusOut, pid, pipeFileDescriptors[PipeRead]});
}
return 1;
@ -114,18 +115,23 @@ void waitForAllProcessesClosed(SubprocessOnOutputFunc onOutput)
if (s_subprocesses.empty())
return;
// TODO: Don't merge all subprocesses to stdin, keep them separate to prevent multi-line errors
// from being split
char processOutputBuffer[1024];
while (fgets(processOutputBuffer, sizeof(processOutputBuffer), stdin))
{
subprocessReceiveStdOut(processOutputBuffer);
onOutput(processOutputBuffer);
}
for (Subprocess& process : s_subprocesses)
{
#ifdef UNIX
char processOutputBuffer[1024] = {0};
int numBytesRead =
read(process.pipeReadFileDescriptor, processOutputBuffer, sizeof(processOutputBuffer));
while (numBytesRead > 0)
{
processOutputBuffer[numBytesRead] = '\0';
subprocessReceiveStdOut(processOutputBuffer);
onOutput(processOutputBuffer);
numBytesRead = read(process.pipeReadFileDescriptor, processOutputBuffer,
sizeof(processOutputBuffer));
}
close(process.pipeReadFileDescriptor);
waitpid(process.processId, process.statusOut, 0);
#endif
}


+ 210
- 147
src/Writer.cpp View File

@ -10,6 +10,101 @@
#include <stdio.h>
#include <string.h>
bool moveFile(const char* srcFilename, const char* destFilename)
{
FILE* srcFile = fopen(srcFilename, "r");
FILE* destFile = fopen(destFilename, "w");
if (!srcFile || !destFile)
return false;
char buffer[1024];
while (fgets(buffer, sizeof(buffer), srcFile))
fputs(buffer, destFile);
fclose(srcFile);
fclose(destFile);
printf("Wrote %s\n", destFilename);
if (remove(srcFilename) != 0)
{
printf("Failed to remove %s\n", srcFilename);
return false;
}
return true;
}
bool writeIfContentsNewer(const char* tempFilename, const char* outputFilename)
{
bool verbose = false;
// Read temporary file and destination file and compare
FILE* newFile = fopen(tempFilename, "r");
if (!newFile)
{
printf("Error: Could not open %s\n", tempFilename);
return false;
}
FILE* oldFile = fopen(outputFilename, "r");
if (!oldFile)
{
// Write new and remove temp
if (verbose)
printf("Destination file didn't exist. Writing\n");
return moveFile(tempFilename, outputFilename);
}
else
{
if (verbose)
printf("Destination file exists. Comparing\n");
char newBuffer[1024] = {0};
char oldBuffer[1024] = {0};
bool identical = true;
while (1)
{
bool newBufferRead = fgets(newBuffer, sizeof(newBuffer), newFile) != nullptr;
bool oldBufferRead = fgets(oldBuffer, sizeof(oldBuffer), oldFile) != nullptr;
if (!newBufferRead || !oldBufferRead)
{
if (newBufferRead != oldBufferRead)
identical = false;
break;
}
if (memcmp(newBuffer, oldBuffer, sizeof(newBuffer)) != 0)
{
identical = false;
break;
}
}
if (identical)
{
if (verbose)
printf("Files are identical. Skipping\n");
fclose(newFile);
fclose(oldFile);
if (remove(tempFilename) != 0)
{
printf("Failed to remove %s\n", tempFilename);
return false;
}
return true;
}
if (verbose)
printf("File changed. writing\n");
fclose(newFile);
fclose(oldFile);
return moveFile(tempFilename, outputFilename);
}
}
const char* importLanguageToString(ImportLanguage type)
{
switch (type)
@ -90,6 +185,8 @@ static void Writer_Writef(StringOutputState& state, const char* format, ...)
static void printIndentation(const WriterFormatSettings& formatSettings, StringOutputState& state)
{
// TODO: Good indentation
return;
if (formatSettings.indentStyle == WriterFormatIndentType_Tabs)
{
for (int i = 0; i < state.blockDepth; ++i)
@ -127,13 +224,16 @@ static void writeStringOutput(const NameStyleSettings& nameSettings,
}
}
if (outputOperation.modifiers & StringOutMod_SpaceBefore)
Writer_Writef(state, " ");
// TODO Validate flags for e.g. OpenParen | CloseParen, which shouldn't be allowed
NameStyleMode mode = getNameStyleModeForFlags(nameSettings, outputOperation.modifiers);
if (mode)
{
char convertedName[MAX_NAME_LENGTH] = {0};
lispNameStyleToCNameStyle(mode, outputOperation.output.c_str(), convertedName,
sizeof(convertedName));
sizeof(convertedName), *outputOperation.startToken);
Writer_Writef(state, "%s", convertedName);
}
else if (outputOperation.modifiers & StringOutMod_SurroundWithQuotes)
@ -199,35 +299,13 @@ static void writeStringOutput(const NameStyleSettings& nameSettings,
}
}
bool moveFile(const char* srcFilename, const char* destFilename)
{
FILE* srcFile = fopen(srcFilename, "r");
FILE* destFile = fopen(destFilename, "w");
if (!srcFile || !destFile)
return false;
char buffer[1024];
while (fgets(buffer, sizeof(buffer), srcFile))
fputs(buffer, destFile);
fclose(srcFile);
fclose(destFile);
printf("Wrote %s\n", destFilename);
if (remove(srcFilename) != 0)
{
printf("Failed to remove %s\n", srcFilename);
return false;
}
return true;
}
// This is annoyingly complex because splices allow things in source to write to header
void writeOutputFollowSplices_Recursive(const NameStyleSettings& nameSettings,
const WriterFormatSettings& formatSettings,
StringOutputState& outputState,
const std::vector<StringOutput>& outputOperations)
StringOutputState& otherOutputState,
const std::vector<StringOutput>& outputOperations,
bool isHeader)
{
for (const StringOutput& operation : outputOperations)
{
@ -242,11 +320,24 @@ void writeOutputFollowSplices_Recursive(const NameStyleSettings& nameSettings,
{
if (operation.spliceOutput)
{
writeOutputFollowSplices_Recursive(nameSettings, formatSettings, outputState,
operation.spliceOutput->source);
if (!operation.spliceOutput->header.empty())
ErrorAtToken(*operation.startToken,
"splice output contained header output, which is not supported");
if (isHeader)
{
writeOutputFollowSplices_Recursive(
nameSettings, formatSettings, otherOutputState, outputState,
operation.spliceOutput->source, /*isHeader=*/false);
writeOutputFollowSplices_Recursive(
nameSettings, formatSettings, outputState, otherOutputState,
operation.spliceOutput->header, /*isHeader=*/true);
}
else
{
writeOutputFollowSplices_Recursive(
nameSettings, formatSettings, outputState, otherOutputState,
operation.spliceOutput->source, /*isHeader=*/false);
writeOutputFollowSplices_Recursive(
nameSettings, formatSettings, otherOutputState, outputState,
operation.spliceOutput->header, /*isHeader=*/true);
}
}
else
ErrorAtToken(*operation.startToken, "expected splice output");
@ -256,118 +347,116 @@ void writeOutputFollowSplices_Recursive(const NameStyleSettings& nameSettings,
}
}
// TODO This sucks
// TODO Lots of params
bool writeIfContentsNewer(const NameStyleSettings& nameSettings,
const WriterFormatSettings& formatSettings,
const WriterOutputSettings& outputSettings, const char* heading,
const char* footer, const std::vector<StringOutput>& outputOperations,
const char* outputFilename)
bool writeOutputs(const NameStyleSettings& nameSettings, const WriterFormatSettings& formatSettings,
const WriterOutputSettings& outputSettings, const GeneratorOutput& outputToWrite)
{
bool verbose = false;
// Write to a temporary file
StringOutputState outputState = {};
char tempFilename[MAX_PATH_LENGTH] = {0};
PrintfBuffer(tempFilename, "%s.temp", outputFilename);
// TODO: If this fails to open, Writer_Writef just won't write to the file, it'll print
outputState.fileOut = fileOpen(tempFilename, "w");
char sourceOutputName[MAX_PATH_LENGTH] = {0};
PrintfBuffer(sourceOutputName, "%s.cpp", outputSettings.sourceCakelispFilename);
char headerOutputName[MAX_PATH_LENGTH] = {0};
PrintfBuffer(headerOutputName, "%s.hpp", outputSettings.sourceCakelispFilename);
if (heading)
struct
{
Writer_Writef(outputState, heading);
// TODO: Put this in Writer_Writef
for (const char* c = heading; *c != '\0'; ++c)
{
if (*c == '\n')
++outputState.currentLine;
}
}
writeOutputFollowSplices_Recursive(nameSettings, formatSettings, outputState, outputOperations);
if (footer)
bool isHeader;
const char* outputFilename;
const char* heading;
const char* footer;
StringOutputState outputState;
// To determine if anything was actually written
StringOutputState stateBeforeOutputWrite;
char tempFilename[MAX_PATH_LENGTH];
} outputs[] = {{/*isHeader=*/false,
sourceOutputName,
outputSettings.sourceHeading,
outputSettings.sourceFooter,
{},
{},
{0}},
{
/*isHeader=*/true,
headerOutputName,
outputSettings.headerHeading,
outputSettings.headerFooter,
{},
{},
{0},
}};
for (int i = 0; i < static_cast<int>(ArraySize(outputs)); ++i)
{
Writer_Writef(outputState, footer);
// TODO: Put this in Writer_Writef
for (const char* c = footer; *c != '\0'; ++c)
// Write to a temporary file
PrintfBuffer(outputs[i].tempFilename, "%s.temp", outputs[i].outputFilename);
// TODO: If this fails to open, Writer_Writef just won't write to the file, it'll print
outputs[i].outputState.fileOut = fileOpen(outputs[i].tempFilename, "w");
if (outputs[i].heading)
{
if (*c == '\n')
++outputState.currentLine;
Writer_Writef(outputs[i].outputState, outputs[i].heading);
// TODO: Put this in Writer_Writef
for (const char* c = outputs[i].heading; *c != '\0'; ++c)
{
if (*c == '\n')
++outputs[i].outputState.currentLine;
}
}
}
if (outputState.fileOut)
{
fclose(outputState.fileOut);
outputState.fileOut = nullptr;
outputs[i].stateBeforeOutputWrite = outputs[i].outputState;
}
// Read temporary file and destination file and compare
FILE* newFile = fopen(tempFilename, "r");
if (!newFile)
{
printf("Error: Could not open %s\n", tempFilename);
return false;
}
FILE* oldFile = fopen(outputFilename, "r");
if (!oldFile)
{
// Write new and remove temp
if (verbose)
printf("Destination file didn't exist. Writing\n");
// Source
writeOutputFollowSplices_Recursive(nameSettings, formatSettings, outputs[0].outputState,
outputs[1].outputState, outputToWrite.source,
outputs[0].isHeader);
// Header
writeOutputFollowSplices_Recursive(nameSettings, formatSettings, outputs[1].outputState,
outputs[0].outputState, outputToWrite.header,
outputs[1].isHeader);
return moveFile(tempFilename, outputFilename);
}
else
for (int i = 0; i < static_cast<int>(ArraySize(outputs)); ++i)
{
if (verbose)
printf("Destination file exists. Comparing\n");
char newBuffer[1024] = {0};
char oldBuffer[1024] = {0};
bool identical = true;
while (1)
// No output to this file. Don't write anything
if (outputs[i].outputState.numCharsOutput ==
outputs[i].stateBeforeOutputWrite.numCharsOutput)
{
bool newBufferRead = fgets(newBuffer, sizeof(newBuffer), newFile) != nullptr;
bool oldBufferRead = fgets(oldBuffer, sizeof(oldBuffer), oldFile) != nullptr;
if (!newBufferRead || !oldBufferRead)
{
if (newBufferRead != oldBufferRead)
identical = false;
break;
}
if (verbose)
printf("%s had no meaningful output\n", outputs[i].tempFilename);
if (memcmp(newBuffer, oldBuffer, sizeof(newBuffer)) != 0)
fclose(outputs[i].outputState.fileOut);
outputs[i].outputState.fileOut = nullptr;
if (remove(outputs[i].tempFilename) != 0)
{
identical = false;
break;
printf("Error: Failed to remove %s\n", outputs[i].tempFilename);
return false;
}
continue;
}
if (identical)
if (outputs[i].footer)
{
if (verbose)
printf("Files are identical. Skipping\n");
fclose(newFile);
fclose(oldFile);
if (remove(tempFilename) != 0)
Writer_Writef(outputs[i].outputState, outputs[i].footer);
// TODO: Put this in Writer_Writef
for (const char* c = outputs[i].footer; *c != '\0'; ++c)
{
printf("Failed to remove %s\n", tempFilename);
return false;
if (*c == '\n')
++outputs[i].outputState.currentLine;
}
return true;
}
if (verbose)
printf("File changed. writing\n");
fclose(newFile);
fclose(oldFile);
if (outputs[i].outputState.fileOut)
{
fclose(outputs[i].outputState.fileOut);
outputs[i].outputState.fileOut = nullptr;
}
return moveFile(tempFilename, outputFilename);
if (!writeIfContentsNewer(outputs[i].tempFilename, outputs[i].outputFilename))
return false;
}
return true;
}
bool writeGeneratorOutput(const GeneratorOutput& generatedOutput,
@ -376,38 +465,12 @@ bool writeGeneratorOutput(const GeneratorOutput& generatedOutput,
const WriterOutputSettings& outputSettings)
{
bool verbose = false;
if (!generatedOutput.source.empty())
{
if (verbose)
printf("\tTo source file:\n");
{
char sourceOutputName[MAX_PATH_LENGTH] = {0};
PrintfBuffer(sourceOutputName, "%s.cpp", outputSettings.sourceCakelispFilename);
if (!writeIfContentsNewer(nameSettings, formatSettings, outputSettings,
outputSettings.sourceHeading, outputSettings.sourceFooter,
generatedOutput.source, sourceOutputName))
return false;
}
}
if (!generatedOutput.header.empty())
{
if (verbose)
printf("\n\tTo header file:\n");
{
char headerOutputName[MAX_PATH_LENGTH] = {0};
PrintfBuffer(headerOutputName, "%s.hpp", outputSettings.sourceCakelispFilename);
if (!writeIfContentsNewer(nameSettings, formatSettings, outputSettings,
outputSettings.headerHeading, outputSettings.headerFooter,
generatedOutput.header, headerOutputName))
return false;
}
}
if (!writeOutputs(nameSettings, formatSettings, outputSettings, generatedOutput))
return false;
// TODO: Write mapping and metadata
if (false)
if (verbose)
{
// Metadata
printf("\n\tImports:\n");


+ 19
- 12
test/Dependencies.cake View File

@ -5,13 +5,14 @@
(defun main (&return int)
(printf "%s\n" (hello-from-comptime))
(empty-macro)
(test-generator)
(return 0))
(defmacro hello-from-comptime ()
(var (& (const Token)) startToken (at startTokenIndex tokens))
(output.push_back (array TokenType_String "Hello, world!"
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(return true))
(defmacro amazing-print-create ()
@ -19,35 +20,41 @@
;; I need quoting soon lol
(output.push_back (array TokenType_OpenParen ""
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_Symbol "defun" ;; "defun-local" to get rid of error
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_Symbol "defun"
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_Symbol "amazing-print"
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_OpenParen ""
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_CloseParen ""
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_OpenParen ""
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_Symbol "printf"
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_String "It worked"
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_CloseParen ""
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
(output.push_back (array TokenType_CloseParen ""
startToken.source startToken.lineNumber
startToken.columnStart startToken.columnEnd))
startToken.columnStart startToken.columnEnd))
;; (bad-function)
(return true))
(amazing-print-create)
(defgenerator test-generator ()
(var (& (const Token)) startToken (at startTokenIndex tokens))
(addStringOutput output.source "printf(\"This is a test\");"
StringOutMod_NewlineAfter (addr startToken))
(return true))

Loading…
Cancel
Save