Compare commits

...

2 Commits

Author SHA1 Message Date
Macoy Madson b838c01c85 New export feature 1 month ago
Macoy Madson 586e8fa8de Add error handling for token-contents-snprintf 1 month ago
  1. 12
      runtime/ComptimeHelpers.cake
  2. 86
      src/Generators.cpp
  3. 12
      src/Metadata.cpp
  4. 15
      src/ModuleManager.hpp
  5. 8
      test/Export.cake
  6. 8
      test/ExportTest.cake
  7. 1
      test/RunTests.cake

12
runtime/ComptimeHelpers.cake

@ -240,8 +240,14 @@
(tokenize-push output
(scope
(var token-contents-printf-buffer ([] 256 char) (array 0))
(snprintf token-contents-printf-buffer (sizeof token-contents-printf-buffer)
(token-splice format)
(token-splice-rest arguments tokens))
(var num-printed size_t
(snprintf token-contents-printf-buffer (sizeof token-contents-printf-buffer)
(token-splice format)
(token-splice-rest arguments tokens)))
(when (>= num-printed (sizeof token-contents-printf-buffer))
(fprintf stderr "error: token-contents-snprintf printed more characters than can fit in " \
"buffer of size %d (%d)\n"
(type-cast (sizeof token-contents-printf-buffer) int)
(type-cast num-printed int)))
(set (field (token-splice token) contents) token-contents-printf-buffer)))
(return true))

86
src/Generators.cpp

@ -14,6 +14,9 @@
#include "Tokenizer.hpp"
#include "Utilities.hpp"
// (export
const int EXPORT_SCOPE_START_EVAL_OFFSET = 2;
typedef bool (*ProcessCommandOptionFunc)(EvaluatorEnvironment& environment,
const std::vector<Token>& tokens, int startTokenIndex,
ProcessCommand* command);
@ -703,6 +706,7 @@ bool ImportGenerator(EvaluatorEnvironment& environment, const EvaluatorContext&
currentToken.contents.empty())
return false;
Module* importedModule = nullptr;
if (isCakeImport)
{
if (!environment.moduleManager)
@ -733,10 +737,9 @@ bool ImportGenerator(EvaluatorEnvironment& environment, const EvaluatorContext&
ArraySize(resolvedPathBuffer), currentToken))
return false;
// Evaluate the import!
Module* module = nullptr;
// Evaluate the import! Will only evaluate it on first import in this environment
if (!moduleManagerAddEvaluateFile(*environment.moduleManager, resolvedPathBuffer,
&module))
&importedModule))
{
ErrorAtToken(currentToken, "failed to import Cakelisp module");
return false;
@ -744,11 +747,11 @@ bool ImportGenerator(EvaluatorEnvironment& environment, const EvaluatorContext&
// Either we only want this file for its header or its macros. Don't build it into
// the runtime library/executable
if ((state == DeclarationsOnly || state == CompTimeOnly) && module)
if ((state == DeclarationsOnly || state == CompTimeOnly) && importedModule)
{
// TODO: This won't protect us from a module changing the environment, which may
// not be desired
module->skipBuild = true;
importedModule->skipBuild = true;
}
}
}
@ -793,6 +796,38 @@ bool ImportGenerator(EvaluatorEnvironment& environment, const EvaluatorContext&
addLangTokenOutput(outputDestination, StringOutMod_NewlineAfter, &currentToken);
}
// Evaluate the import's exports in the current context (the importer module)
if (importedModule)
{
// We have imported the file. Prevent new exports being created to simplify things
importedModule->exportScopesLocked = true;
for (ModuleExportScope& exportScope : importedModule->exportScopes)
{
// Already processed this export. Is this even necessary?
if (exportScope.modulesEvaluatedExport.find(context.module->filename) !=
exportScope.modulesEvaluatedExport.end())
continue;
int startEvalateTokenIndex =
exportScope.startTokenIndex + EXPORT_SCOPE_START_EVAL_OFFSET;
EvaluatorContext exportModuleContext = context;
int numErrors = EvaluateGenerateAll_Recursive(environment, exportModuleContext,
*exportScope.tokens,
startEvalateTokenIndex, output);
if (numErrors)
{
NoteAtToken((*exportScope.tokens)[exportScope.startTokenIndex],
"while evaluating export");
NoteAtToken(tokens[startTokenIndex], "export came from this import");
return false;
}
exportScope.modulesEvaluatedExport[context.module->filename] = 1;
}
}
output.imports.push_back({currentToken.contents,
isCakeImport ? ImportLanguage_Cakelisp : ImportLanguage_C,
&currentToken});
@ -2644,6 +2679,46 @@ bool ComptimeDefineSymbolGenerator(EvaluatorEnvironment& environment,
return true;
}
// Export works like a delayed evaluate where it is evaluated within each importing module
bool ExportScopeGenerator(EvaluatorEnvironment& environment, const EvaluatorContext& context,
const std::vector<Token>& tokens, int startTokenIndex,
GeneratorOutput& output)
{
// Don't let the user think this function can be called during comptime/runtime
if (!ExpectEvaluatorScope("export", tokens[startTokenIndex], context, EvaluatorScope_Module))
return false;
if (!context.module)
{
ErrorAtToken(tokens[startTokenIndex], "modules not supported (internal code error?)");
return false;
}
// See this member's comment to understand why this is here
if (context.module->exportScopesLocked)
{
ErrorAtToken(
tokens[startTokenIndex],
"export has been added, but other modules have already evaluated past exports of this "
"module. Try to move this export out of comptime blocks or do not macro-generate it");
return false;
}
const Token& startEvalToken = tokens[startTokenIndex + EXPORT_SCOPE_START_EVAL_OFFSET];
if (startEvalToken.type == TokenType_CloseParen)
{
ErrorAtToken(startEvalToken, "expected statements to export");
return false;
}
ModuleExportScope newExport;
newExport.tokens = &tokens;
newExport.startTokenIndex = startTokenIndex;
context.module->exportScopes.push_back(newExport);
return true;
}
// Give the user a replacement suggestion
typedef std::unordered_map<std::string, const char*> DeprecatedHelpStringMap;
DeprecatedHelpStringMap s_deprecatedHelpStrings;
@ -3071,6 +3146,7 @@ void importFundamentalGenerators(EvaluatorEnvironment& environment)
environment.generators["comptime-error"] = ComptimeErrorGenerator;
environment.generators["comptime-cond"] = ComptimeCondGenerator;
environment.generators["comptime-define-symbol"] = ComptimeDefineSymbolGenerator;
environment.generators["export"] = ExportScopeGenerator;
// Dispatches based on invocation name
const char* cStatementKeywords[] = {

12
src/Metadata.cpp

@ -22,6 +22,7 @@ enum EvaluationTime
EvaluationTime_EvaluatedImmediately = 1 << 0,
EvaluationTime_CompileTime = 1 << 1,
EvaluationTime_Runtime = 1 << 2,
EvaluationTime_EvaluatedOnImport = 1 << 3,
};
enum GeneratorCategory
@ -126,6 +127,11 @@ GeneratorMetadata g_generatorMetadata[] = {
{
"ignore",
},
{"export", GeneratorCategory_Uncategorized, LanguageRequirement_Evaluated,
EvaluationTime_EvaluatedOnImport, 1, 99,
"When any other module imports the current module, evaluate the statements within this export "
"scope in the context of the other module. This allows modules to e.g. 'infect' other modules "
"with settings necessary for the importer to build"},
//
// Math
@ -340,7 +346,7 @@ GeneratorMetadata g_generatorMetadata[] = {
{
"defun-nodecl",
},
{
{
"defun-comptime",
},
{
@ -356,7 +362,7 @@ GeneratorMetadata g_generatorMetadata[] = {
//
// Memory
//
{
{
"set",
},
{
@ -368,7 +374,7 @@ GeneratorMetadata g_generatorMetadata[] = {
{
"addr",
},
{
{
"deref",
},
{

15
src/ModuleManager.hpp

@ -21,6 +21,15 @@ struct ModuleDependency
extern const char* g_modulePreBuildHookSignature;
typedef bool (*ModulePreBuildHook)(ModuleManager& manager, Module* module);
struct ModuleExportScope
{
const std::vector<Token>* tokens;
int startTokenIndex; // Start of (export) invocation, not eval statements (for easier errors)
// Prevent double-evaluation
std::unordered_map<std::string, int> modulesEvaluatedExport;
};
// A module is typically associated with a single file. Keywords like local mean in-module only
struct Module
{
@ -30,6 +39,12 @@ struct Module
std::string sourceOutputName;
std::string headerOutputName;
std::vector<ModuleExportScope> exportScopes;
// As soon as the first importer evaluates any exports from this module, we can no longer add
// new exports, because then we would have to go back to the importing modules and re-evaluate.
// We could make it smart enough to do this, but it doesn't seem worth the effort now
bool exportScopesLocked;
// Build system
std::vector<ModuleDependency> dependencies;

8
test/Export.cake

@ -0,0 +1,8 @@
(skip-build)
(export
(add-cakelisp-search-directory "runtime")
(import &comptime-only "CHelpers.cake"))
(export
(declare-extern-function test ())
(add-cpp-build-dependency "dir/Test.cpp"))

8
test/ExportTest.cake

@ -0,0 +1,8 @@
(import &comptime-only "Export.cake")
(c-import "<stdio.h>")
(defun main (&return int)
(fprintf stderr "Hello, export!\n")
(test)
(return 0))

1
test/RunTests.cake

@ -18,6 +18,7 @@
(array "Code modification" "test/CodeModification.cake")
;; (array "Build options" "test/BuildOptions.cake")
(array "Execute" "test/Execute.cake")
(array "Export" "test/ExportTest.cake")
(array "Defines" "test/Defines.cake")
(array "Multi-line strings" "test/MultiLineStrings.cake")
(array "Build helpers" "test/BuildHelpers.cake")

Loading…
Cancel
Save