frameStack.ts (2085B)
1 // This is called "frameStack" and not "callStack", because the last frame in errors is often not a function call. 2 // A "frame" is a pair of a scope (function or top-level scope, currently stored as a string) and a location inside it. 3 // See this comment to deconfuse about what a frame is: https://github.com/quantified-uncertainty/squiggle/pull/1172#issuecomment-1264115038 4 5 import { LocationRange } from "peggy"; 6 7 export const topFrameName = "<top>"; 8 9 export class Frame { 10 // Weird hack: without this, Frame class won't be a separate type from the plain JS Object type, since it doesn't have any meaningful methods. 11 // This could lead to bugs. 12 isFrame() { 13 return 1; 14 } 15 16 constructor( 17 public name: string, 18 public location?: LocationRange // can be empty for calls from builtin functions, or for the top frame 19 ) {} 20 21 toString() { 22 return ( 23 this.name + 24 (this.location 25 ? ` at line ${this.location.start.line}, column ${this.location.start.column}, file ${this.location.source}` 26 : "") 27 ); 28 } 29 } 30 31 export class FrameStack { 32 constructor( 33 public frame: Frame, 34 public parent?: FrameStack 35 ) {} 36 37 static make(): FrameStack { 38 return new FrameStack(new Frame("<root>")); // this top frame is always invisible 39 } 40 41 extend(name: string, location?: LocationRange): FrameStack { 42 return new FrameStack(new Frame(name, location), this); 43 } 44 45 // this includes the left offset because it's mostly used in SqError.toStringWithStackTrace 46 toString(): string { 47 return this.toFrameArray() 48 .map((f) => " " + f.toString()) 49 .join("\n"); 50 } 51 52 toFrameArray(): Frame[] { 53 const result: Frame[] = []; 54 let t: FrameStack = this; 55 while (t && t.frame) { 56 if (!t.parent) break; // top level frame is fake and should be skipped 57 result.push(t.frame); 58 t = t.parent; 59 } 60 return result; 61 } 62 63 getTopFrame(): Frame | undefined { 64 return this.parent ? this.frame : undefined; // top level frame is always invisible 65 } 66 67 isEmpty(): boolean { 68 return this.parent === undefined; 69 } 70 }