forked from eriksvedang/cakelisp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEvaluator.cpp
2349 lines (2058 loc) · 83.8 KB
/
Evaluator.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "Evaluator.hpp"
#include <stdio.h>
#include <string.h>
#include "Build.hpp"
#include "Converters.hpp"
#include "DynamicLoader.hpp"
#include "FileUtilities.hpp"
#include "GeneratorHelpers.hpp"
#include "Generators.hpp"
#include "Logging.hpp"
#include "OutputPreambles.hpp"
#include "RunProcess.hpp"
#include "Tokenizer.hpp"
#include "Utilities.hpp"
#include "Writer.hpp"
//
// Environment
//
const char* globalDefinitionName = "<global>";
const char* cakelispWorkingDir = "cakelisp_cache";
const char* g_environmentPreLinkHookSignature =
"('manager (& ModuleManager) 'link-command (& ProcessCommand) 'link-time-inputs (* "
"ProcessCommandInput) 'num-link-time-inputs int &return bool)";
const char* g_environmentPostReferencesResolvedHookSignature =
"('environment (& EvaluatorEnvironment) &return bool)";
static const char* g_environmentCompileTimeVariableDestroySignature = "('data (* void))";
GeneratorFunc findGenerator(EvaluatorEnvironment& environment, const char* functionName)
{
GeneratorIterator findIt = environment.generators.find(std::string(functionName));
if (findIt != environment.generators.end())
return findIt->second;
return nullptr;
}
static MacroFunc findMacro(EvaluatorEnvironment& environment, const char* functionName)
{
MacroIterator findIt = environment.macros.find(std::string(functionName));
if (findIt != environment.macros.end())
return findIt->second;
return nullptr;
}
void* findCompileTimeFunction(EvaluatorEnvironment& environment, const char* functionName)
{
CompileTimeFunctionTableIterator findIt =
environment.compileTimeFunctions.find(std::string(functionName));
if (findIt != environment.compileTimeFunctions.end())
return findIt->second;
return nullptr;
}
bool findCompileTimeSymbol(EvaluatorEnvironment& environment, const char* symbolName)
{
return environment.compileTimeSymbols.find(symbolName) != environment.compileTimeSymbols.end();
}
static bool isCompileTimeCodeLoaded(EvaluatorEnvironment& environment,
const ObjectDefinition& definition)
{
switch (definition.type)
{
case ObjectType_CompileTimeMacro:
return findMacro(environment, definition.name.c_str()) != nullptr;
case ObjectType_CompileTimeGenerator:
return findGenerator(environment, definition.name.c_str()) != nullptr;
case ObjectType_CompileTimeFunction:
return findCompileTimeFunction(environment, definition.name.c_str()) != nullptr;
default:
return false;
}
}
bool addObjectDefinition(EvaluatorEnvironment& environment, ObjectDefinition& definition)
{
ObjectDefinitionMap::iterator findIt = environment.definitions.find(definition.name);
if (findIt == environment.definitions.end())
{
if (isCompileTimeCodeLoaded(environment, definition))
{
ErrorAtTokenf(*definition.definitionInvocation,
"multiple definitions of %s. Name may be conflicting with built-in macro "
"or generator",
definition.name.c_str());
return false;
}
environment.definitions[definition.name] = definition;
return true;
}
else
{
ErrorAtTokenf(*definition.definitionInvocation, "multiple definitions of %s",
definition.name.c_str());
NoteAtToken(*findIt->second.definitionInvocation, "first defined here");
return false;
}
}
ObjectDefinition* findObjectDefinition(EvaluatorEnvironment& environment, const char* name)
{
ObjectDefinitionMap::iterator findIt = environment.definitions.find(name);
if (findIt != environment.definitions.end())
return &findIt->second;
return nullptr;
}
const ObjectReferenceStatus* addObjectReference(EvaluatorEnvironment& environment,
const Token& referenceNameToken,
ObjectReference& reference)
{
// Default to the module requiring the reference, for top-level references
std::string definitionName = globalDefinitionName;
if (!reference.context.definitionName && reference.context.scope != EvaluatorScope_Module)
Log("error: addObjectReference() expects a definitionName\n");
if (reference.context.definitionName)
{
definitionName = reference.context.definitionName->contents;
}
const char* defName = definitionName.c_str();
if (logging.references)
Logf("Adding reference %s to %s\n", referenceNameToken.contents.c_str(), defName);
// Add the reference requirement to the definition it occurred in
ObjectReferenceStatus* refStatus = nullptr;
ObjectDefinitionMap::iterator findDefinition = environment.definitions.find(definitionName);
if (findDefinition == environment.definitions.end())
{
if (definitionName.compare(globalDefinitionName) != 0)
{
Logf("error: expected definition %s to already exist. Things will break\n",
definitionName.c_str());
}
else
{
ErrorAtTokenf(referenceNameToken,
"error: expected %s definition to exist as a top-level catch-all",
globalDefinitionName);
}
}
else
{
// The reference is copied here somewhat unnecessarily. It would be too much of a hassle to
// make a good link to the reference in the reference pool, because it can easily be moved
// by hash realloc or vector resize
ObjectReferenceStatusMap::iterator findRefIt =
findDefinition->second.references.find(referenceNameToken.contents.c_str());
if (findRefIt == findDefinition->second.references.end())
{
ObjectReferenceStatus newStatus;
newStatus.name = &referenceNameToken;
newStatus.guessState = reference.type == ObjectReferenceResolutionType_AlreadyLoaded ?
GuessState_Resolved :
GuessState_None;
newStatus.references.push_back(reference);
std::pair<ObjectReferenceStatusMap::iterator, bool> newRefStatusResult =
findDefinition->second.references.emplace(
std::make_pair(referenceNameToken.contents, std::move(newStatus)));
refStatus = &newRefStatusResult.first->second;
}
else
{
findRefIt->second.references.push_back(reference);
refStatus = &findRefIt->second;
}
}
// Add the reference to the reference pool. This makes it easier to find all places where it is
// referenced during resolve time
ObjectReferencePoolMap::iterator findIt =
environment.referencePools.find(referenceNameToken.contents);
if (findIt == environment.referencePools.end())
{
ObjectReferencePool newPool = {};
newPool.references.push_back(reference);
environment.referencePools[referenceNameToken.contents] = std::move(newPool);
}
else
{
findIt->second.references.push_back(reference);
}
return refStatus;
}
int getNextFreeBuildId(EvaluatorEnvironment& environment)
{
return ++environment.nextFreeBuildId;
}
bool isCompileTimeObject(ObjectType type)
{
return type == ObjectType_CompileTimeMacro || type == ObjectType_CompileTimeGenerator ||
type == ObjectType_CompileTimeFunction;
}
bool CreateCompileTimeVariable(EvaluatorEnvironment& environment, const char* name,
const char* typeExpression, void* data,
const char* destroyCompileTimeFuncName)
{
CompileTimeVariableTableIterator findIt = environment.compileTimeVariables.find(name);
if (findIt != environment.compileTimeVariables.end())
{
Logf("error: CreateCompileTimeVariable(): variable %s already defined\n", name);
return false;
}
CompileTimeVariable newCompileTimeVariable = {};
newCompileTimeVariable.type = typeExpression;
newCompileTimeVariable.data = data;
if (destroyCompileTimeFuncName)
newCompileTimeVariable.destroyCompileTimeFuncName = destroyCompileTimeFuncName;
environment.compileTimeVariables[name] = newCompileTimeVariable;
// Make sure it gets built and loaded once it is defined
if (destroyCompileTimeFuncName)
environment.requiredCompileTimeFunctions[destroyCompileTimeFuncName] =
"compile time variable destructor";
return true;
}
bool GetCompileTimeVariable(EvaluatorEnvironment& environment, const char* name,
const char* typeExpression, void** dataOut)
{
*dataOut = nullptr;
CompileTimeVariableTableIterator findIt = environment.compileTimeVariables.find(name);
if (findIt == environment.compileTimeVariables.end())
return false;
if (findIt->second.type.compare(typeExpression) != 0)
{
Logf(
"error: GetCompileTimeVariable(): type does not match existing variable %s. Types must "
"match exactly. Expected %s, got %s\n",
name, findIt->second.type.c_str(), typeExpression);
return false;
}
*dataOut = findIt->second.data;
return true;
}
//
// Evaluator
//
// Dispatch to a generator or expand a macro and evaluate its output recursively. If the reference
// is unknown, add it to a list so EvaluateResolveReferences() can come back and decide what to do
// with it. Only EvaluateResolveReferences() decides whether to create a C/C++ invocation
bool HandleInvocation_Recursive(EvaluatorEnvironment& environment, const EvaluatorContext& context,
const std::vector<Token>& tokens, int invocationStartIndex,
GeneratorOutput& output)
{
const Token& invocationStart = tokens[invocationStartIndex];
const Token& invocationName = tokens[invocationStartIndex + 1];
if (!ExpectTokenType("evaluator", invocationName, TokenType_Symbol))
return false;
MacroFunc invokedMacro = findMacro(environment, invocationName.contents.c_str());
if (invokedMacro)
{
// We must use a separate vector for each macro because Token lists must be immutable. If
// they weren't, pointers to tokens would be invalidated
const std::vector<Token>* macroOutputTokens = nullptr;
bool macroSucceeded;
{
// Do NOT modify token lists after they are created. You can change the token contents
std::vector<Token>* macroOutputTokensNoConst_CREATIONONLY = new std::vector<Token>();
// Have the macro generate some code for us!
macroSucceeded = invokedMacro(environment, context, tokens, invocationStartIndex,
*macroOutputTokensNoConst_CREATIONONLY);
// Make it const to save any temptation of modifying the list and breaking everything
macroOutputTokens = macroOutputTokensNoConst_CREATIONONLY;
}
// Don't even try to validate the code if the macro wasn't satisfied
if (!macroSucceeded)
{
ErrorAtTokenf(invocationName, "macro '%s' returned failure",
invocationName.contents.c_str());
// Deleting these tokens is only safe at this point because we know we have not
// evaluated them. As soon as they are evaluated, they must be kept around
delete macroOutputTokens;
return false;
}
// The macro had no output, but we won't let that bother us
if (macroOutputTokens->empty())
{
delete macroOutputTokens;
return true;
}
// TODO: Pretty print to macro expand file and change output token source to
// point there
// Macro must generate valid parentheses pairs!
bool validateResult = validateTokens(*macroOutputTokens);
if (!validateResult)
{
NoteAtToken(invocationStart,
"code was generated from macro. See erroneous macro "
"expansion below:");
prettyPrintTokens(*macroOutputTokens);
Log("\n");
// Deleting these tokens is only safe at this point because we know we have not
// evaluated them. As soon as they are evaluated, they must be kept around
delete macroOutputTokens;
return false;
}
// Macro succeeded and output valid tokens. Keep its tokens for later referencing and
// destruction. Note that macroOutputTokens cannot be destroyed safely until all pointers to
// its Tokens are cleared. This means even if we fail while evaluating the tokens, we will
// keep the array around because the environment might still hold references to the tokens.
// It's also necessary for error reporting
environment.comptimeTokens.push_back(macroOutputTokens);
// Let the definition know about the expansion so it is easy to construct an expanded list
// of all tokens in the definition
if (context.definitionName)
{
ObjectDefinitionMap::iterator findIt =
environment.definitions.find(context.definitionName->contents);
if (findIt != environment.definitions.end())
{
ObjectDefinition& definition = findIt->second;
definition.macroExpansions.push_back({&invocationStart, macroOutputTokens});
}
else
ErrorAtTokenf(invocationStart,
"could not find definition '%s' to associate macro expansion "
"(internal code error)",
context.definitionName->contents.c_str());
}
// Note that macros always inherit the current context, whereas bodies change it
int result = EvaluateGenerateAll_Recursive(environment, context, *macroOutputTokens,
/*startTokenIndex=*/0, output);
if (result != 0)
{
NoteAtToken(invocationStart,
"code was generated from macro. See macro expansion below:");
prettyPrintTokens(*macroOutputTokens);
Log("\n");
return false;
}
return true;
}
GeneratorFunc invokedGenerator = findGenerator(environment, invocationName.contents.c_str());
if (invokedGenerator)
{
environment.lastGeneratorReferences[invocationName.contents.c_str()] =
&tokens[invocationStartIndex];
return invokedGenerator(environment, context, tokens, invocationStartIndex, output);
}
// Check for known Cakelisp functions
ObjectDefinitionMap::iterator findIt = environment.definitions.find(invocationName.contents);
if (findIt != environment.definitions.end())
{
// Plain-old Cakelisp runtime function
if (!isCompileTimeObject(findIt->second.type))
return FunctionInvocationGenerator(environment, context, tokens, invocationStartIndex,
output);
if (findIt->second.type == ObjectType_CompileTimeFunction &&
findCompileTimeFunction(environment, invocationName.contents.c_str()))
{
if (context.resolvingReference != &invocationName)
{
ObjectReference newReference = {};
newReference.type = ObjectReferenceResolutionType_AlreadyLoaded;
newReference.tokens = &tokens;
newReference.startIndex = invocationStartIndex;
newReference.context = context;
const ObjectReferenceStatus* referenceStatus =
addObjectReference(environment, invocationName, newReference);
if (!referenceStatus)
{
ErrorAtToken(tokens[invocationStartIndex],
"failed to create reference status (internal error)");
return false;
}
}
return FunctionInvocationGenerator(environment, context, tokens, invocationStartIndex,
output);
}
}
// Unknown reference
{
// We don't know what this is. We cannot guess it is a C/C++ function yet, because it
// could be a generator or macro invocation that hasn't been defined yet. Leave a note
// for the evaluator to come back to this token once a satisfying answer is found
ObjectReference newReference = {};
newReference.type = ObjectReferenceResolutionType_Splice;
newReference.tokens = &tokens;
newReference.startIndex = invocationStartIndex;
newReference.context = context;
// Make room for whatever gets output once this reference is resolved
newReference.spliceOutput = new GeneratorOutput;
if (logging.splices)
NoteAtTokenf(invocationName, "unknown reference to %s, creating splice %p",
invocationName.contents.c_str(), newReference.spliceOutput);
// We push in a StringOutMod_Splice as a sentinel that the splice list needs to be
// checked. Otherwise, it will be a no-op to Writer. It's useful to have this sentinel
// so that multiple splices take up space and will then maintain sequential order
addSpliceOutput(output, newReference.spliceOutput, &invocationStart);
const ObjectReferenceStatus* referenceStatus =
addObjectReference(environment, invocationName, newReference);
if (!referenceStatus)
{
ErrorAtToken(tokens[invocationStartIndex],
"failed to create reference status (internal error)");
return false;
}
// If some action has already happened on this reference, duplicate it here
// This code wouldn't be necessary if BuildEvaluateReferences() checked all of its reference
// instances, and stored a status on each one. I don't like the duplication here, but it
// does match the other HandleInvocation_Recursive() invocation types, which are handled as
// soon as the environment has enough information to resolve the invocation
if (referenceStatus->guessState == GuessState_Guessed ||
(findIt != environment.definitions.end() &&
findIt->second.type == ObjectType_CompileTimeFunction))
{
// Guess now, because BuildEvaluateReferences thinks it has already guessed all refs
bool result =
FunctionInvocationGenerator(environment, newReference.context, *newReference.tokens,
newReference.startIndex, *newReference.spliceOutput);
// Our guess didn't even evaluate
if (!result)
return false;
}
// We're going to return true even though evaluation isn't yet done.
// BuildEvaluateReferences() will handle the evaluation after it knows what the
// references are
}
return true;
}
int EvaluateGenerate_Recursive(EvaluatorEnvironment& environment, const EvaluatorContext& context,
const std::vector<Token>& tokens, int startTokenIndex,
GeneratorOutput& output)
{
// Note that in most cases, we will continue evaluation in order to turn up more errors
int numErrors = 0;
if (!tokens.empty())
environment.wasCodeEvaluatedThisPhase = true;
const Token& token = tokens[startTokenIndex];
if (token.type == TokenType_OpenParen)
{
// Invocation of a macro, generator, or function (either foreign or Cakelisp function)
bool invocationSucceeded =
HandleInvocation_Recursive(environment, context, tokens, startTokenIndex, output);
if (!invocationSucceeded)
++numErrors;
}
else if (token.type == TokenType_CloseParen)
{
// This is totally normal. We've reached the end of the body or file. If that isn't the
// case, the code isn't being validated with validateTokens(); code which hasn't
// been validated should NOT be run - this function trusts its inputs blindly!
// This will also be hit if eval itself has been broken: it is expected to skip tokens
// within invocations, including the final close paren
return numErrors;
}
else
{
// The remaining token types evaluate to themselves. Output them directly.
if (ExpectEvaluatorScope("evaluated constant", token, context,
EvaluatorScope_ExpressionsOnly))
{
switch (token.type)
{
case TokenType_Symbol:
{
// Special case: C requires NULL, C++ encourages nullptr. Let's handle them both
// automatically with null
if (token.contents.compare("null") == 0)
{
// TODO: C vs. C++
addStringOutput(output.source, "nullptr", StringOutMod_None, &token);
break;
}
// We need to convert what look like names in case they are lispy, but not
// integer, character, or floating point constants
char firstChar = token.contents[0];
char secondChar = token.contents.size() > 1 ? token.contents[1] : 0;
if (firstChar == '\'' || isdigit(firstChar) ||
(firstChar == '-' && (secondChar == '.' || isdigit(secondChar))))
addStringOutput(output.source, token.contents, StringOutMod_None, &token);
else
{
// Potential lisp name. Convert
addStringOutput(output.source, token.contents,
StringOutMod_ConvertVariableName, &token);
}
break;
}
case TokenType_String:
addStringOutput(output.source, token.contents, StringOutMod_SurroundWithQuotes,
&token);
break;
default:
ErrorAtTokenf(token,
"Unhandled token type %s; has a new token type been added, or "
"evaluator has been changed?",
tokenTypeToString(token.type));
return 1;
}
}
else
numErrors++;
}
return numErrors;
}
// Delimiter template will be inserted between the outputs
int EvaluateGenerateAll_Recursive(EvaluatorEnvironment& environment,
const EvaluatorContext& context, const std::vector<Token>& tokens,
int startTokenIndex, GeneratorOutput& output)
{
// Note that in most cases, we will continue evaluation in order to turn up more errors
int numErrors = 0;
bool isDelimiterUsed = !context.delimiterTemplate.output.empty() ||
context.delimiterTemplate.modifiers != StringOutMod_None;
bool isDelimiterSyntactic = !context.delimiterTemplate.output.empty() ||
context.delimiterTemplate.modifiers != StringOutMod_NewlineAfter;
// Used to detect when something was actually output during evaluation
int lastOutputTotalSize = output.source.size() + output.header.size();
int numTokens = tokens.size();
for (int currentTokenIndex = startTokenIndex; currentTokenIndex < numTokens;
++currentTokenIndex)
{
if (tokens[currentTokenIndex].type == TokenType_CloseParen)
{
// Reached the end of an argument list or body. Only modules will hit numTokens
break;
}
// Starting a new argument to evaluate
if (isDelimiterUsed && currentTokenIndex != startTokenIndex)
{
bool outputChanged =
output.source.size() + output.header.size() != (unsigned long)lastOutputTotalSize;
// If the delimiter is a newline only, it is probably for humans only, and can be
// ignored if evaluation results in no output
if (isDelimiterSyntactic || outputChanged)
{
StringOutput delimiter = context.delimiterTemplate;
delimiter.startToken = &tokens[currentTokenIndex];
// TODO: Controlling source vs. header output?
output.source.push_back(std::move(delimiter));
}
}
lastOutputTotalSize = output.source.size() + output.header.size();
numErrors +=
EvaluateGenerate_Recursive(environment, context, tokens, currentTokenIndex, output);
if (tokens[currentTokenIndex].type == TokenType_OpenParen)
{
// Skip invocation body. for()'s increment will skip us past the final ')'
currentTokenIndex = FindCloseParenTokenIndex(tokens, currentTokenIndex);
}
}
return numErrors;
}
bool ReplaceAndEvaluateDefinition(EvaluatorEnvironment& environment,
const char* definitionToReplaceName,
const std::vector<Token>& newDefinitionTokens)
{
ObjectDefinitionMap::iterator findIt = environment.definitions.find(definitionToReplaceName);
if (findIt == environment.definitions.end())
{
Logf("error: ReplaceAndEvaluateDefinition() could not find definition '%s'\n",
definitionToReplaceName);
return false;
}
if (!validateTokens(newDefinitionTokens))
{
Log("note: encountered error while validating the following replacement definition:\n");
prettyPrintTokens(newDefinitionTokens);
return false;
}
EvaluatorContext definitionContext = findIt->second.context;
GeneratorOutput* definitionOutput = findIt->second.output;
// This output is still referred to by the module (etc.) output's splice. When a replacement
// definition is added, it will actually add a splice to the old definiton's output and create
// its own output. Have the environment hold on to it for later destruction
environment.orphanedOutputs.push_back(definitionOutput);
// This makes me nervous because the user could have a reference to this when calling this
// function. I can't think of a safer way to get rid of the reference without deleting it
environment.definitions.erase(findIt);
findIt = environment.definitions.end();
definitionOutput->source.clear();
definitionOutput->header.clear();
if (definitionContext.scope == EvaluatorScope_Module)
{
StringOutput moduleDelimiterTemplate = {};
moduleDelimiterTemplate.modifiers = StringOutMod_NewlineAfter;
definitionContext.delimiterTemplate = moduleDelimiterTemplate;
}
bool result = EvaluateGenerateAll_Recursive(environment, definitionContext, newDefinitionTokens,
/*startTokenIndex=*/0, *definitionOutput) == 0;
if (!result)
{
Log("note: encountered error while evaluating the following replacement definition:\n");
prettyPrintTokens(newDefinitionTokens);
}
return result;
}
bool ClearAndEvaluateAtSplicePoint(EvaluatorEnvironment& environment, const char* splicePointName,
const std::vector<Token>* newSpliceTokens)
{
SplicePointTableIterator findIt = environment.splicePoints.find(splicePointName);
if (findIt == environment.splicePoints.end())
{
Logf("error: splice point %s not found\n", splicePointName);
return false;
}
SplicePoint* splicePoint = &findIt->second;
bool result = EvaluateGenerateAll_Recursive(environment, splicePoint->context, *newSpliceTokens,
/*startTokenIndex=*/0, *splicePoint->output) == 0;
if (!result)
{
Log("note: encountered error while evaluating the following splice:\n");
prettyPrintTokens(*newSpliceTokens);
}
return result;
}
// Determine what needs to be built, iteratively
// TODO This can be made faster. I did the most naive version first, for now
static void PropagateRequiredToReferences(EvaluatorEnvironment& environment)
{
// Figure out what is required
// This needs to loop so long as it doesn't recurse to references
int numRequiresStatusChanged = 0;
do
{
numRequiresStatusChanged = 0;
for (ObjectDefinitionPair& definitionPair : environment.definitions)
{
ObjectDefinition& definition = definitionPair.second;
// Automatically require a compile-time function if the environment needs it (typically
// because some other function was called that added the requirement before the
// definition was available)
if (definition.type == ObjectType_CompileTimeFunction && !definition.isRequired)
{
RequiredCompileTimeFunctionReasonsTableIterator findIt =
environment.requiredCompileTimeFunctions.find(definition.name.c_str());
if (findIt != environment.requiredCompileTimeFunctions.end())
{
if (logging.dependencyPropagation)
Logf("Define %s promoted to required because %s\n", definition.name.c_str(),
findIt->second);
definition.isRequired = true;
definition.environmentRequired = true;
}
}
if (logging.dependencyPropagation)
{
const char* status = definition.isRequired ? "(required)" : "(not required)";
Logf("Define %s %s\n", definition.name.c_str(), status);
}
for (ObjectReferenceStatusPair& reference : definition.references)
{
ObjectReferenceStatus& referenceStatus = reference.second;
if (logging.dependencyPropagation)
Logf("\tRefers to %s\n", referenceStatus.name->contents.c_str());
if (definition.isRequired)
{
ObjectDefinitionMap::iterator findIt =
environment.definitions.find(referenceStatus.name->contents);
if (findIt != environment.definitions.end() && !findIt->second.isRequired)
{
if (logging.dependencyPropagation)
Logf("\t Infecting %s with required due to %s\n",
referenceStatus.name->contents.c_str(), definition.name.c_str());
++numRequiresStatusChanged;
findIt->second.isRequired = true;
}
}
}
}
} while (numRequiresStatusChanged);
}
static void OnCompileProcessOutput(const char* output)
{
// TODO C/C++ error to Cakelisp token mapper
}
enum BuildStage
{
BuildStage_None,
BuildStage_Compiling,
BuildStage_Linking,
BuildStage_Loading,
BuildStage_ResolvingReferences,
BuildStage_Finished
};
// Note: environment.definitions can be resized/rehashed during evaluation, which invalidates
// iterators. For now, I will rely on the fact that std::unordered_map does not invalidate
// references on resize. This will need to change if the data structure changes
struct ComptimeBuildObject
{
int buildId = -1;
int status = -1;
BuildStage stage = BuildStage_None;
bool hasAnyRefs = false;
std::string artifactsName;
std::string dynamicLibraryPath;
std::string sourceOutputName;
std::string buildObjectName;
std::vector<std::string> importLibraries;
ObjectDefinition* definition = nullptr;
};
static std::vector<ObjectReference>* GetReferenceListFromReference(EvaluatorEnvironment& environment,
const char* referenceToResolve)
{
ObjectReferencePoolMap::iterator referencePoolIt =
environment.referencePools.find(referenceToResolve);
if (referencePoolIt == environment.referencePools.end())
return nullptr;
return &referencePoolIt->second.references;
}
// Once a definition is known, references to it can be re-evaluated to splice in the appropriate
// code. Returns the number of references resolved
static int ReevaluateResolveReferences(EvaluatorEnvironment& environment,
const char* referenceToResolve, bool warnIfNoReferences,
int& numErrorsOut)
{
int numReferencesResolved = 0;
// Resolve references
std::vector<ObjectReference>* references =
GetReferenceListFromReference(environment, referenceToResolve);
if (!references)
{
if (warnIfNoReferences)
Logf(
"warning: built an object %s which had no references. It should not have been "
"built. There may be a problem with Cakelisp internally\n",
referenceToResolve);
return 0;
}
bool hasErrors = false;
// The old-style loop must be used here because EvaluateGenerate_Recursive can add to this
// list, which invalidates iterators
for (int i = 0; i < (int)references->size(); ++i)
{
const int maxNumReferences = 1 << 13;
if (i >= maxNumReferences)
{
Logf(
"error: definition %s exceeded max number of references (%d). Is it in an infinite "
"loop?",
referenceToResolve, maxNumReferences);
for (int n = 0; n < 10; ++n)
{
ErrorAtToken((*(*references)[n].tokens)[(*references)[n].startIndex], "Reference here");
}
hasErrors = true;
++numErrorsOut;
break;
}
if ((*references)[i].isResolved)
continue;
if ((*references)[i].type == ObjectReferenceResolutionType_Splice &&
(*references)[i].spliceOutput)
{
ObjectReference* referenceValidPreEval = &((*references)[i]);
// In case a compile-time function has already guessed the invocation was a C/C++
// function, clear that invocation output
resetGeneratorOutput(*referenceValidPreEval->spliceOutput);
if (logging.buildProcess)
NoteAtTokenf((*referenceValidPreEval->tokens)[referenceValidPreEval->startIndex],
"resolving reference to %s. output is %p", referenceToResolve,
referenceValidPreEval->spliceOutput);
// Make sure we don't create additional references for this same reference, as we are
// resolving it
referenceValidPreEval->context.resolvingReference =
&(*referenceValidPreEval->tokens)[referenceValidPreEval->startIndex + 1];
size_t referencePoolSizePreEval = environment.referencePools.size();
int result = EvaluateGenerate_Recursive(
environment, referenceValidPreEval->context, *referenceValidPreEval->tokens,
referenceValidPreEval->startIndex, *referenceValidPreEval->spliceOutput);
referenceValidPreEval = nullptr;
// We must now re-look up the reference pool and reference list if there's a chance the
// memory has been moved due to evaluation adding new references.
// As an optimization, only do this if the pool size changed, which should be the only
// case where the memory could have been moved.
if (environment.referencePools.size() != referencePoolSizePreEval)
references = GetReferenceListFromReference(environment, referenceToResolve);
hasErrors |= result > 0;
numErrorsOut += result;
}
else if ((*references)[i].type == ObjectReferenceResolutionType_AlreadyLoaded)
{
// Nothing to do
}
else
{
// Do not resolve, we don't know how to resolve this type of reference
ErrorAtToken((*((*references)[i]).tokens)[(*references)[i].startIndex],
"do not know how to resolve this reference (internal code error?)");
hasErrors = true;
++numErrorsOut;
continue;
}
if (hasErrors)
continue;
// Regardless of what evaluate turned up, we resolved this as far as we care. Trying
// again isn't going to change the number of errors
// Note that if new references emerge to this definition, they will automatically be
// recognized as the definition and handled then and there, so we don't need to make
// more than one pass
(*references)[i].isResolved = true;
++numReferencesResolved;
}
return numReferencesResolved;
}
bool ComptimePrepareHeaders(EvaluatorEnvironment& environment)
{
const char* outputDir = cakelispWorkingDir;
const char* combinedHeaderName = "CakelispComptime.hpp";
std::vector<std::string> headerSearchDirectories;
{
// Need working dir to find cached file itself
headerSearchDirectories.push_back(".");
// Need Cakelisp src dir to find cakelisp headers. If these aren't checked for
// modification, comptime code can end up calling stale functions/initializing
// incorrect types
headerSearchDirectories.push_back(
environment.cakelispSrcDir.empty() ? "src" : environment.cakelispSrcDir);
}
char combinedHeaderRelativePath[MAX_PATH_LENGTH] = {0};
PrintfBuffer(combinedHeaderRelativePath, "%s/%s", outputDir, combinedHeaderName);
std::vector<const char*> headersToCombine(ArraySize(g_comptimeDefaultHeaders));
for (size_t i = 0; i < ArraySize(g_comptimeDefaultHeaders); ++i)
headersToCombine[i] = g_comptimeDefaultHeaders[i];
if (!writeCombinedHeader(combinedHeaderRelativePath, headersToCombine))
return false;
const char* buildExecutable =
environment.compileTimeHeaderPrecompilerCommand.fileToExecute.c_str();
char precompiledHeaderFilename[MAX_PATH_LENGTH] = {0};
if (!outputFilenameFromSourceFilename(outputDir, combinedHeaderRelativePath,
precompiledHeaderExtension, precompiledHeaderFilename,
sizeof(precompiledHeaderFilename)))
{
Log("error: failed to prepare precompiled header output filename");
return false;
}
char precompiledHeaderOutputArgument[MAX_PATH_LENGTH + 5] = {0};
makePrecompiledHeaderOutputArgument(precompiledHeaderOutputArgument,
sizeof(precompiledHeaderOutputArgument),
precompiledHeaderFilename, buildExecutable);
char headerInclude[MAX_PATH_LENGTH] = {0};
if (environment.cakelispSrcDir.empty())
makeIncludeArgument(headerInclude, sizeof(headerInclude), "src/");
else
makeIncludeArgument(headerInclude, sizeof(headerInclude),
environment.cakelispSrcDir.c_str());
char precompileHeaderExecutable[MAX_PATH_LENGTH] = {0};
if (!resolveExecutablePath(buildExecutable, precompileHeaderExecutable,
sizeof(precompileHeaderExecutable)))
return false;
ProcessCommandInput precompileHeaderInputs[] = {
{ProcessCommandArgumentType_SourceInput, {combinedHeaderRelativePath}},
{ProcessCommandArgumentType_PrecompiledHeaderOutput, {precompiledHeaderOutputArgument}},
{ProcessCommandArgumentType_CakelispHeadersInclude, {headerInclude}}};
const char** buildArguments = MakeProcessArgumentsFromCommand(
precompileHeaderExecutable, environment.compileTimeHeaderPrecompilerCommand.arguments,
precompileHeaderInputs, ArraySize(precompileHeaderInputs));
if (!buildArguments)
{
return false;
}
// Can we use the cached version?
if (!cppFileNeedsBuild(environment, combinedHeaderRelativePath, precompiledHeaderFilename,
buildArguments, environment.comptimeCachedCommandCrcs,
environment.comptimeNewCommandCrcs,
environment.comptimeHeaderModifiedCache, headerSearchDirectories))
{
if (logging.buildProcess)
Logf("No need to update precompiled header %s\n", precompiledHeaderFilename);
environment.comptimeHeadersPrepared = true;
environment.comptimeCombinedHeaderFilename = combinedHeaderName;
free(buildArguments);
return true;
}
if (logging.buildProcess)
Logf("Updating precompiled header %s\n", precompiledHeaderFilename);
RunProcessArguments arguments = {};
arguments.fileToExecute = buildExecutable;
arguments.arguments = buildArguments;
int status = -1;
if (runProcess(arguments, &status) != 0)
{
free(buildArguments);
environment.comptimeNewCommandCrcs.erase(precompiledHeaderFilename);
return false;
}
free(buildArguments);
waitForAllProcessesClosed(OnCompileProcessOutput);
if (status == 0)
{
environment.comptimeHeadersPrepared = true;
environment.comptimeCombinedHeaderFilename = combinedHeaderName;