23
23
#include < exception>
24
24
#include < functional>
25
25
#include < iterator>
26
+ #include < list>
26
27
#include < ostream>
27
28
#include < set>
28
29
#include < string>
@@ -199,12 +200,22 @@ class source_stack {
199
200
* each partial application's source location.
200
201
*/
201
202
struct frame {
203
+ /* *
204
+ * The frame from which the current frame was jumped to. This is
205
+ * nullptr for the root frame.
206
+ */
207
+ frame* prev;
202
208
/* *
203
209
* The evaluation context of the source frame. When a new frame is pushed:
204
210
* - macros — retain the context from the previous frame.
205
211
* - partials — derive a new context from the previous frame.
206
212
*/
207
213
eval_context context;
214
+ /* *
215
+ * The currently active partial application, or nullptr if the frame
216
+ * originated from the root, or is a macro.
217
+ */
218
+ partial_definition::ptr partial;
208
219
/* *
209
220
* For all elements except the top of the stack, this is the location of the
210
221
* partial application within that source that led to the current stack.
@@ -215,44 +226,59 @@ class source_stack {
215
226
* saved before the new source is pushed to the stack. After the partial
216
227
* application completes, the saved location is dropped.
217
228
*/
218
- source_location apply_location;
229
+ source_location jumped_from;
230
+ /* *
231
+ * For `{{#pragma ignore-newlines}}`.
232
+ */
219
233
bool ignore_newlines = false ;
220
234
221
- explicit frame (eval_context ctx) : context(std::move(ctx)) {}
235
+ std::optional<std::string> name () const {
236
+ return partial == nullptr ? std::nullopt : std::optional{partial->name };
237
+ }
238
+
239
+ explicit frame (
240
+ frame* prev, eval_context ctx, partial_definition::ptr partial)
241
+ : prev(prev), context(std::move(ctx)), partial(std::move(partial)) {}
222
242
};
223
243
224
244
/* *
225
245
* Returns the current source stack frame, or nullptr if the stack is empty.
226
246
*/
227
- frame* top () {
228
- if (frames_.empty ()) {
229
- return nullptr ;
230
- }
231
- return &frames_.back ();
247
+ frame* top () { return frames_.empty () ? nullptr : &frames_.back (); }
248
+ const frame* top () const {
249
+ return frames_.empty () ? nullptr : &frames_.back ();
232
250
}
233
251
234
252
/* *
235
253
* An RAII guard that pushes and pops sources from the stack of partial
236
254
* applications.
237
255
*/
238
- auto make_frame_guard (eval_context eval_ctx, source_location apply_location) {
256
+ auto make_frame_guard (
257
+ eval_context eval_ctx,
258
+ partial_definition::ptr partial,
259
+ source_location jumped_from) {
239
260
class frame_guard {
240
261
public:
241
262
explicit frame_guard (
242
263
source_stack& stack,
243
264
eval_context eval_ctx,
244
- source_location apply_location)
265
+ partial_definition::ptr&& partial,
266
+ source_location jumped_from)
245
267
: stack_(stack) {
246
268
if (auto * frame = stack_.top ()) {
247
- frame->apply_location = std::move (apply_location);
269
+ // Save the jump location since we're jumping to a new frame. This
270
+ // allows collecting a backtrace.
271
+ frame->jumped_from = jumped_from;
248
272
}
249
- stack.frames_ .emplace_back (std::move (eval_ctx));
273
+ stack.frames_ .emplace_back (
274
+ stack_.top (), std::move (eval_ctx), std::move (partial));
250
275
}
251
276
~frame_guard () noexcept {
252
277
assert (!stack_.frames_ .empty ());
253
278
stack_.frames_ .pop_back ();
254
279
if (auto * source = stack_.top ()) {
255
- source->apply_location = source_location ();
280
+ // Reset the jump location since we've return to its origin.
281
+ source->jumped_from = source_location ();
256
282
}
257
283
}
258
284
frame_guard (frame_guard&& other) = delete ;
@@ -263,10 +289,23 @@ class source_stack {
263
289
private:
264
290
source_stack& stack_;
265
291
};
266
- return frame_guard{*this , std::move (eval_ctx), std::move (apply_location)};
292
+ return frame_guard{
293
+ *this , std::move (eval_ctx), std::move (partial), jumped_from};
267
294
}
268
295
269
- using backtrace = std::vector<resolved_location>;
296
+ struct backtrace_frame {
297
+ /* *
298
+ * The resolved source location where a jump happened, or the origin of
299
+ * the backtrace for the top-most frame.
300
+ */
301
+ resolved_location location;
302
+ /* *
303
+ * The name of the partial application (if present) from which the jump
304
+ * happened.
305
+ */
306
+ std::optional<std::string> name;
307
+ };
308
+ using backtrace = std::vector<backtrace_frame>;
270
309
/* *
271
310
* Creates a back trace for debugging that contains the chain of partial
272
311
* applications within the source.
@@ -275,25 +314,31 @@ class source_stack {
275
314
* caller.
276
315
*/
277
316
backtrace make_backtrace_at (const source_location& current) const {
278
- assert (!frames_.empty ());
279
317
assert (current != source_location ());
318
+ const frame* frame = top ();
319
+ assert (frame != nullptr );
280
320
281
- std::vector<resolved_location> result;
282
- result.emplace_back (resolved_location (current, diags_.source_mgr ()));
283
- for (auto frame = std::next (frames_.rbegin ()); frame != frames_.rend ();
284
- ++frame) {
285
- assert (frame->apply_location != source_location ());
286
- result.emplace_back (
287
- resolved_location (frame->apply_location , diags_.source_mgr ()));
321
+ std::vector<backtrace_frame> result;
322
+ result.emplace_back (backtrace_frame{
323
+ resolved_location (current, diags_.source_mgr ()),
324
+ frame->name (),
325
+ });
326
+ frame = frame->prev ;
327
+ for (; frame != nullptr ; frame = frame->prev ) {
328
+ assert (frame->jumped_from != source_location ());
329
+ result.emplace_back (backtrace_frame{
330
+ resolved_location (frame->jumped_from , diags_.source_mgr ()),
331
+ frame->name (),
332
+ });
288
333
}
289
334
return result;
290
335
}
291
336
292
337
explicit source_stack (diagnostics_engine& diags) : diags_(diags) {}
293
338
294
339
private:
295
- // Using std::vector as a stack so we can iterate over it
296
- std::vector <frame> frames_;
340
+ // Doubly linked list provides stable iterators / pointers for backtraces.
341
+ std::list <frame> frames_;
297
342
diagnostics_engine& diags_;
298
343
};
299
344
@@ -371,7 +416,9 @@ class render_engine {
371
416
auto eval_ctx = eval_context::with_root_scope (
372
417
std::move (root_context_), std::exchange (opts_.globals , {}));
373
418
auto source_frame_guard = source_stack_.make_frame_guard (
374
- std::move (eval_ctx), source_location ());
419
+ std::move (eval_ctx),
420
+ nullptr /* the root node is not a partial */ ,
421
+ source_location ());
375
422
visit (root.body_elements );
376
423
return true ;
377
424
} catch (const render_error& err) {
@@ -385,14 +432,17 @@ class render_engine {
385
432
auto source_trace = [&]() -> std::string {
386
433
std::string result;
387
434
for (std::size_t i = 0 ; i < backtrace.size (); ++i) {
388
- const auto & frame = backtrace[i];
435
+ const source_stack::backtrace_frame& frame = backtrace[i];
436
+ const std::string location = frame.name .has_value ()
437
+ ? fmt::format (" {} @ {}" , *frame.name , frame.location .file_name ())
438
+ : fmt::format (" {}" , frame.location .file_name ());
389
439
fmt::format_to (
390
440
std::back_inserter (result),
391
441
" #{} {} <line:{}, col:{}>\n " ,
392
442
i,
393
- frame. file_name () ,
394
- frame.line (),
395
- frame.column ());
443
+ location ,
444
+ frame.location . line (),
445
+ frame.location . column ());
396
446
}
397
447
return result;
398
448
}();
@@ -1071,7 +1121,7 @@ class render_engine {
1071
1121
}
1072
1122
1073
1123
auto source_frame_guard = source_stack_.make_frame_guard (
1074
- std::move (derived_ctx), partial_statement.loc .begin );
1124
+ std::move (derived_ctx), partial, partial_statement.loc .begin );
1075
1125
auto indent_guard =
1076
1126
out_.make_indent_guard (partial_statement.standalone_offset_within_line );
1077
1127
visit (partial->bodies );
@@ -1104,7 +1154,9 @@ class render_engine {
1104
1154
// Macros are "inlined" into their invocation site. In other words, they
1105
1155
// execute within the scope where they are invoked.
1106
1156
auto source_frame_guard = source_stack_.make_frame_guard (
1107
- current_frame ().context , macro.loc .begin );
1157
+ current_frame ().context ,
1158
+ nullptr /* no partial definition because macros are at root scope */ ,
1159
+ macro.loc .begin );
1108
1160
auto indent_guard =
1109
1161
out_.make_indent_guard (macro.standalone_offset_within_line );
1110
1162
visit (resolved_macro->body_elements );
0 commit comments