Skip to content

Commit 73aa6e9

Browse files
committed
Major new language feature - defer
Added defer generator. This generator will defer outputting until a scope is exited. This adds some burden on generator writers to be aware of the need to document their scope enter/exit and explicit exits. It was implemented by putting the defer output into a splice, then having the Writer keep track of scopes and output the splices in the appropriate locations. * Speculatively fix double std::move on newStringOutput which seems to be completely invalid, but didn't seem to break anything. * Add documentation for path and field
1 parent 85401f1 commit 73aa6e9

15 files changed

+394
-65
lines changed

doc/Roadmap.org

+5-4
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,6 @@ It needs to be possible to mix strict C and C++ modules, because it's unlikely t
8383
Currently, defining which headers to include from compile-time functions is not possible. This is important to support complex compile-time code bases spread across multiple functions and headers. It's also necessary to be able to include Cakelisp headers optionally, so compile-time functions can help macros/generators/etc.
8484

8585
This shouldn't be a hard add, but it is tricky to decide where import list should go: within each function, at the module level, both of those, etc.
86-
** ~defer~ support
87-
This one is going to be a bit tricky, because scopes are going to need to be tracked. I think it's a really nice to have feature, but one of the harder, more error-prone ones.
88-
89-
I don't want to just use C++ destructors because I want a pure-C option.
9086
** ~for~ loop
9187
In GameLib, almost all loops would be fine with a number range, e.g. here are some ideas:
9288
#+BEGIN_SRC lisp
@@ -214,6 +210,11 @@ Mostly listing this because it's what I use (LSP is a bit too heavyweight for my
214210
The following are things that were on the Roadmap that are now complete.
215211

216212
These are sorted with most recently completed appearing first.
213+
214+
** ~defer~ support
215+
This one is going to be a bit tricky, because scopes are going to need to be tracked. I think it's a really nice to have feature, but one of the harder, more error-prone ones.
216+
217+
I don't want to just use C++ destructors because I want a pure-C option.
217218
** Conditionals based on build configuration
218219
It is useful to be able to define blocks of code which are e.g. operating-system specific, e.g. something like:
219220
#+BEGIN_SRC lisp

runtime/CHelpers.cake

+2-2
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@
247247
(array Keyword ";" -1)
248248
(array Expression null update)
249249
(array CloseParen null -1)
250-
(array OpenBlock null -1)
250+
(array OpenContinueBreakableScope null -1)
251251
(array Body null body)
252-
(array CloseBlock null -1)))
252+
(array CloseContinueBreakableScope null -1)))
253253
(return (c-statement-out statement)))
254254

255255
;; This only works for arrays where the size is known at compile-time

src/Evaluator.hpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,9 @@ struct EvaluatorEnvironment
337337
ArtifactCrcTable comptimeNewCommandCrcs;
338338

339339
// When a definition is replaced (e.g. by ReplaceAndEvaluateDefinition()), the original
340-
// definition's output is still used, but no longer has a definition to keep track of it. We'll
341-
// make sure the orphans get destroyed
340+
// definition's output is still used, but no longer has a definition to keep track of it. This
341+
// is also used for splices that don't have an owning object. We'll make sure the orphans get
342+
// destroyed so as to not leak memory.
342343
std::vector<GeneratorOutput*> orphanedOutputs;
343344

344345
// Create a named splice point for later output splicing. Used so the user can insert things

src/EvaluatorEnums.hpp

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ enum StringOutputModifierFlags
1717

1818
// Curly braces ({}) will be added for open and close, respectively
1919
// These are also clues to the output formatter to indent all within, etc.
20+
// If you expect statements to run within, use StringOutMod_OpenScopeBlock and
21+
// StringOutMod_CloseScopeBlock instead.
2022
StringOutMod_OpenBlock = 1 << 7,
2123
StringOutMod_CloseBlock = 1 << 8,
2224

@@ -35,6 +37,22 @@ enum StringOutputModifierFlags
3537

3638
// Signals the Writer that it needs to splice in another output list
3739
StringOutMod_Splice = 1 << 15,
40+
41+
// The Writer should paste the spliceOutput of this operation before exiting the current scope
42+
StringOutMod_SpliceOnScopeExit = 1 << 16,
43+
44+
StringOutMod_ScopeEnter = 1 << 17,
45+
StringOutMod_ScopeExit = 1 << 18,
46+
// for/while/switch all can have continue or break, which "exits" the scope or maybe just
47+
// restarts it. We need to handle these specially because they can exist within another scope,
48+
// so the writer needs to peel back all scopes
49+
StringOutMod_ScopeContinueBreakableEnter = 1 << 19,
50+
StringOutMod_ScopeContinueBreakableExit = 1 << 20,
51+
StringOutMod_ScopeContinueOrBreak = 1 << 21,
52+
StringOutMod_ScopeExitAll = 1 << 22,
53+
54+
StringOutMod_OpenScopeBlock = StringOutMod_OpenBlock | StringOutMod_ScopeEnter,
55+
StringOutMod_CloseScopeBlock = StringOutMod_CloseBlock | StringOutMod_ScopeExit,
3856
};
3957

4058
enum ImportLanguage

src/GeneratorHelpers.cpp

+50-1
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,24 @@ void addSpliceOutput(GeneratorOutput& output, GeneratorOutput* spliceOutput,
451451

452452
// Splice marker must be pushed to both source and header to preserve ordering in case
453453
// spliceOutput has both source and header outputs
454-
output.source.push_back(std::move(newStringOutput));
454+
output.source.push_back(newStringOutput);
455+
// Now we can have the header just take ownership
456+
output.header.push_back(std::move(newStringOutput));
457+
}
458+
459+
void addSpliceOutputWithModifiers(GeneratorOutput& output, GeneratorOutput* spliceOutput,
460+
const Token* startToken, StringOutputModifierFlags modifiers)
461+
{
462+
StringOutput newStringOutput = {};
463+
newStringOutput.modifiers = (StringOutputModifierFlags)((int)StringOutMod_Splice | (int)modifiers);
464+
newStringOutput.startToken = startToken;
465+
466+
newStringOutput.spliceOutput = spliceOutput;
467+
468+
// Splice marker must be pushed to both source and header to preserve ordering in case
469+
// spliceOutput has both source and header outputs
470+
output.source.push_back(newStringOutput);
471+
// Now we can have the header just take ownership
455472
output.header.push_back(std::move(newStringOutput));
456473
}
457474

@@ -1044,6 +1061,38 @@ bool CStatementOutput(EvaluatorEnvironment& environment, const EvaluatorContext&
10441061
case CloseParen:
10451062
addLangTokenOutput(output.source, StringOutMod_CloseParen, &nameToken);
10461063
break;
1064+
case OpenScope:
1065+
addLangTokenOutput(output.source,
1066+
(StringOutputModifierFlags)((int)StringOutMod_OpenBlock |
1067+
(int)StringOutMod_ScopeEnter),
1068+
&nameToken);
1069+
break;
1070+
case CloseScope:
1071+
addLangTokenOutput(output.source,
1072+
(StringOutputModifierFlags)((int)StringOutMod_CloseBlock |
1073+
(int)StringOutMod_ScopeExit),
1074+
&nameToken);
1075+
break;
1076+
case OpenContinueBreakableScope:
1077+
addLangTokenOutput(
1078+
output.source,
1079+
(StringOutputModifierFlags)((int)StringOutMod_OpenBlock |
1080+
(int)StringOutMod_ScopeContinueBreakableEnter),
1081+
&nameToken);
1082+
break;
1083+
case CloseContinueBreakableScope:
1084+
addLangTokenOutput(
1085+
output.source,
1086+
(StringOutputModifierFlags)((int)StringOutMod_CloseBlock |
1087+
(int)StringOutMod_ScopeContinueBreakableExit),
1088+
&nameToken);
1089+
break;
1090+
case ContinueOrBreakInScope:
1091+
addLangTokenOutput(output.source, StringOutMod_ScopeContinueOrBreak, &nameToken);
1092+
break;
1093+
case ExitAllScopes:
1094+
addLangTokenOutput(output.source, StringOutMod_ScopeExitAll, &nameToken);
1095+
break;
10471096
case OpenBlock:
10481097
addLangTokenOutput(output.source, StringOutMod_OpenBlock, &nameToken);
10491098
break;

src/GeneratorHelpers.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ CAKELISP_API void addLangTokenOutput(std::vector<StringOutput>& output,
101101
void addSpliceOutput(GeneratorOutput& output, GeneratorOutput* spliceOutput,
102102
const Token* startToken);
103103

104+
void addSpliceOutputWithModifiers(GeneratorOutput& output, GeneratorOutput* spliceOutput,
105+
const Token* startToken, StringOutputModifierFlags modifiers);
106+
104107
struct FunctionArgumentTokens
105108
{
106109
int startTypeIndex;

src/GeneratorHelpersEnums.hpp

+14
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,25 @@ enum CStatementOperationType
88

99
OpenParen,
1010
CloseParen,
11+
12+
// Open a scope. Always use when regular statements can be executed within
13+
OpenScope,
14+
CloseScope,
15+
// Anything which responds to continue or break should open its scope with these
16+
OpenContinueBreakableScope,
17+
CloseContinueBreakableScope,
18+
// Do not open a scope. Use only for declarations
1119
OpenBlock,
1220
CloseBlock,
21+
// Used only for e.g. array initializers
1322
OpenList,
1423
CloseList,
1524

25+
// "Unnatural" flow control, i.e., cases where the normal CloseScope will not be hit
26+
ContinueOrBreakInScope,
27+
// return keyword
28+
ExitAllScopes,
29+
1630
Keyword,
1731
KeywordNoSpace,
1832
// End the statement if it isn't an expression

0 commit comments

Comments
 (0)