@@ -19,7 +19,7 @@ import {
1919 type Internable , type Primitive , intern ,
2020} from '@/core/types/internable' ;
2121import {
22- SyntaxNodeIdGenerator ,
22+ SyntaxNode , SyntaxNodeIdGenerator ,
2323} from '@/core/types/nodes' ;
2424import {
2525 NodeSymbolIdGenerator , SymbolFactory ,
@@ -94,14 +94,6 @@ const COMPUTING = Symbol('COMPUTING');
9494type QuerySymbol = symbol ;
9595
9696export default class Compiler {
97- // A global cache mapping from:
98- // (QuerySymbol, interned argument string) -> Result
99- private globalCache = new Map < QuerySymbol , Map < string , any > > ( ) ;
100-
101- // A local cache mapping from:
102- // (Filepath, QuerySymbol, interned argument string) -> Result
103- private localCache = new Map < FilepathId , Map < QuerySymbol , Map < string , any > > > ( ) ;
104-
10597 // Interners
10698 nodeIdGenerator = new SyntaxNodeIdGenerator ( ) ;
10799
@@ -132,30 +124,45 @@ export default class Compiler {
132124 this . globalCache . clear ( ) ;
133125 }
134126
127+ // A global cache mapping from:
128+ // (QuerySymbol, interned argument string) -> Result
129+ private globalCache = new Map < QuerySymbol , Map < string , any > > ( ) ;
130+
131+ // Turn a normal function into a Compiler's global query
132+ // Input: A function that only accepts internable types | primitive types
133+ // Output: A global query wrapping the function
135134 private globalQuery < Args extends ( Primitive | Primitive [ ] | Internable < Primitive > ) [ ] , Return > (
136135 fn : ( this : Compiler , ...args : Args ) => Return ,
137136 ) : ( ...args : Args ) => Return {
137+ // 1. Generates a unique query symbol for the query
138138 const queryKey = Symbol ( ) ;
139+
139140 return ( ( ...args : Args ) : Return => {
141+ // Ensure there's a cache entry for this Query
142+ if ( ! this . globalCache . has ( queryKey ) ) {
143+ this . globalCache . set ( queryKey , new Map ( ) ) ;
144+ }
145+
146+ // 2. Intern all arguments and join into a single string
140147 const argKey = args . map ( ( a ) => intern ( a ) ) . join ( '\0' ) ;
141- let subCache = this . globalCache . get ( queryKey ) ;
142- if ( subCache instanceof Map ) {
143- if ( subCache . has ( argKey ) ) {
144- const cached = subCache . get ( argKey ) ;
145- if ( cached === COMPUTING ) {
146- throw new Error ( `Cycle detected in query: ${ fn . name } ( ${ argKey } )` ) ;
147- }
148- return cached ;
148+
149+ // 3. Check the cache using (QuerySymbol, interned argument string)
150+ const subCache = this . globalCache . get ( queryKey ) ! ;
151+ if ( subCache . has ( argKey ) ) {
152+ const cached = subCache . get ( argKey ) ;
153+ // 4. If the cache entry is being computed -> Throw query cycle error
154+ if ( cached === COMPUTING ) {
155+ throw new Error ( `Cycle detected in query: ${ fn . name } ( ${ argKey } )` ) ;
149156 }
157+ // 5. Cache hit, return
158+ return cached ;
150159 }
151160
152- if ( ! ( subCache instanceof Map ) ) {
153- subCache = new Map ( ) ;
154- this . globalCache . set ( queryKey , subCache ) ;
155- }
161+ // 6. Flag the current entry as computing
156162 subCache . set ( argKey , COMPUTING ) ;
157163
158164 try {
165+ // 7. Cache miss, recompute & cache
159166 const result = fn . apply ( this , args ) ;
160167 subCache . set ( argKey , result ) ;
161168 return result ;
@@ -166,24 +173,45 @@ export default class Compiler {
166173 } ) as ( ...args : Args ) => Return ;
167174 }
168175
169- private localQuery < Args extends [ Filepath , ...( Primitive | Primitive [ ] | Internable < Primitive > ) [ ] ] , Return > (
176+ // A local cache mapping from:
177+ // (Filepath, QuerySymbol, interned argument string) -> Result
178+ private localCache = new Map < FilepathId , Map < QuerySymbol , Map < string , any > > > ( ) ;
179+
180+ // Turn a normal function into a Compiler's local query
181+ // Input: A function whose first arg is a Filepath or SyntaxNode (from which we extract .filepath)
182+ // & accepts internable types | primitive types for the rest
183+ // Output: A local query wrapping the function, cached per-file
184+ private localQuery < Args extends [
185+ Filepath | SyntaxNode , // filepath or node as first arg
186+ ...( Primitive | Primitive [ ] | Internable < Primitive > ) [ ] ,
187+ ] , Return > (
170188 fn : ( this : Compiler , ...args : Args ) => Return ,
171189 ) : ( ...args : Args ) => Return {
190+ // 1. Generates a unique query symbol for the query
172191 const queryKey = Symbol ( ) ;
192+
173193 return ( ( ...args : Args ) : Return => {
194+ // 2. Retrieve the filepath (from Filepath directly or from SyntaxNode.filepath)
174195 const [
175- filepath ,
196+ firstArg ,
176197 ...rest
177198 ] = args ;
199+ const filepath = firstArg instanceof SyntaxNode ? firstArg . filepath : firstArg ;
178200 const filepathId = filepath . intern ( ) ;
179- const argKey = rest . map ( ( a ) => intern ( a ) ) . join ( '\0' ) ;
180201
202+ // 3. Intern all arguments (including first) and join into a single string
203+ // The first arg is interned too so different nodes from the same file
204+ // don't collide in the cache.
205+ const argKey = args . map ( ( a ) => intern ( a ) ) . join ( '\0' ) ;
206+
207+ // Ensure that the local cache for the filepath exists
181208 let filepathCache = this . localCache . get ( filepathId ) ;
182209 if ( ! filepathCache ) {
183210 filepathCache = new Map ( ) ;
184211 this . localCache . set ( filepathId , filepathCache ) ;
185212 }
186213
214+ // Ensure that the local subcache for the (filepath, QuerySymbol) exists
187215 let subCache = filepathCache . get ( queryKey ) ;
188216 if ( ! subCache ) {
189217 subCache = new Map ( ) ;
@@ -192,14 +220,18 @@ export default class Compiler {
192220
193221 if ( subCache . has ( argKey ) ) {
194222 const cached = subCache . get ( argKey ) ;
223+ // 4. Query cycle, throw
195224 if ( cached === COMPUTING ) {
196225 throw new Error ( `Cycle detected in query: ${ fn . name } (${ filepathId } , ${ argKey } )` ) ;
197226 }
227+ // 5. Cache hit, return
198228 return cached ;
199229 }
200230
231+ // 6. Flag the current query as being computed
201232 subCache . set ( argKey , COMPUTING ) ;
202233 try {
234+ // 7. Recompute the query result & cache
203235 const result = fn . apply ( this , args ) ;
204236 subCache . set ( argKey , result ) ;
205237 return result ;
@@ -210,102 +242,105 @@ export default class Compiler {
210242 } ) as ( ...args : Args ) => Return ;
211243 }
212244
213- // A local query.
214- // Lex + parse a single file into an AST. Related: parseProject.
215- // (filepath: Filepath) => Report<FileParseIndex>
245+ // A local query
246+ // Lex + parse a single file into an AST
247+ // Signature: (filepath: Filepath) => Report<FileParseIndex>
216248 parseFile = this . localQuery ( parseFile ) ;
217- // A global query.
218- // Lex + parse all entry-point files. Related: parseFile.
219- // () => Map<string, Report<FileParseIndex>>
249+ // A global query
250+ // Lex + parse all entry-point files
251+ // Returns a map from the file's absolute path to FileParseIndex
252+ // Signature: () => Map<string, Report<FileParseIndex>>
220253 parseProject = this . globalQuery ( parseProject ) ;
221254
222- // A global query.
223- // Run the binder on a single AST node (dispatches to global modules). Related: bindFile, bindProject.
224- // (node: SyntaxNode) => Report<void> | Report<Unhandled>
255+ // A local query
256+ // Validate an AST node and return any compile errors
257+ // Signature: (node: SyntaxNode) => Report<void> | Report<Unhandled>
258+ validateNode = this . localQuery ( validateNode ) ;
259+ // A local query
260+ // Return the fully-qualified name segments of an AST node (e.g. ['public', 'users'])
261+ // Signature: (node: SyntaxNode) => Report<string[] | undefined> | Report<Unhandled>
262+ nodeFullname = this . localQuery ( fullname ) ;
263+ // A local query
264+ // Return the alias string of an AST node if one is declared
265+ // Signature: (node: SyntaxNode) => Report<string | undefined> | Report<Unhandled>
266+ nodeAlias = this . localQuery ( nodeAlias ) ;
267+ // A local query
268+ // Return the settings/options map of an AST node
269+ // Signature: (node: SyntaxNode) => Report<Settings> | Report<Unhandled>
270+ nodeSettings = this . localQuery ( nodeSettings ) ;
271+
272+ // A global query
273+ // Run the binder on a single AST node
274+ // Signature: (node: SyntaxNode) => Report<void> | Report<Unhandled>
225275 bindNode = this . globalQuery ( bindNode ) ;
226- // A global query.
227- // Bind a single file's AST (calls bindNode on the root). Related: bindNode, bindProject.
228- // (filepath: Filepath) => Report<void>
276+ // A global query
277+ // Bind a single file's AST (calls bindNode on the root)
278+ // Signature: (filepath: Filepath) => Report<void>
229279 bindFile = this . globalQuery ( bindFile ) ;
230- // A global query.
231- // Bind all entry-point files in dependency order. Related: bindFile.
232- // () => Map<string, Report<void>>
280+ // A global query
281+ // Bind all entry-point files in dependency order
282+ // Returns a map from the file's absolute path to bind errors
283+ // Signature: () => Map<string, Report<void>>
233284 bindProject = this . globalQuery ( bindProject ) ;
234285
235- // A global query.
236- // Interpret a single AST node into a SchemaElement (dispatches to global modules). Related: interpretFile, interpretProject.
237- // (node: SyntaxNode) => Report<SchemaElement | SchemaElement[] | undefined> | Report<Unhandled>
286+ // A global query
287+ // Interpret a single AST node into a SchemaElement
288+ // Signature: (node: SyntaxNode) => Report<SchemaElement | SchemaElement[] | undefined> | Report<Unhandled>
238289 interpretNode = this . globalQuery ( interpretNode ) ;
239- // A global query.
240- // Interpret a single file's AST into a raw Database. Related: interpretNode, interpretProject.
241- // (filepath: Filepath) => Report<Readonly<Database> | undefined>
290+ // A global query
291+ // Interpret a single file's AST into a raw Database
292+ // Signature: (filepath: Filepath) => Report<Readonly<Database> | undefined>
242293 interpretFile = this . globalQuery ( interpretFile ) ;
243- // A global query.
244- // Interpret all reachable files and merge into a MasterDatabase. Related: interpretFile.
245- // () => Report<MasterDatabase>
294+ // A global query
295+ // Interpret all reachable files and merge into a MasterDatabase (A database but for multifile)
296+ // Signature: () => Report<MasterDatabase>
246297 interpretProject = this . globalQuery ( interpretProject ) ;
247- // A global query.
248- // Export a reconciled, alias-resolved Database for a single file (calls interpretProject internally). Related: interpretProject.
249- // (filepath: Filepath) => Report<Readonly<Database> | undefined>
298+ // A global query
299+ // Start from an entrypoint file, collects all imports and merges into the entrypoint
300+ // Export a Database representing this merged view of the MasterDatabase
301+ // Signature: (filepath: Filepath) => Report<Readonly<Database> | undefined>
250302 exportSchemaJson = this . globalQuery ( exportSchemaJson ) ;
251303
252- // A global query.
253- // Resolve the NodeSymbol declared by a given AST node. Related: symbolMembers, nodeReferee.
254- // (node: SyntaxNode) => Report<NodeSymbol> | Report<Unhandled>
304+ // A global query
305+ // Return the NodeSymbol for a single SyntaxNode
306+ // Signature: (node: SyntaxNode) => Report<NodeSymbol> | Report<Unhandled>
255307 nodeSymbol = this . globalQuery ( nodeSymbol ) ;
256- // A global query.
257- // Return all direct child symbols of a symbol. Related: nodeSymbol, lookupMembers.
258- // (symbol: NodeSymbol) => Report<NodeSymbol[]> | Report<Unhandled>
308+ // A global query
309+ // Return all direct child symbols of a symbol
310+ // Signature: (symbol: NodeSymbol) => Report<NodeSymbol[]> | Report<Unhandled>
259311 symbolMembers = this . globalQuery ( symbolMembers ) ;
260- // A global query.
261- // Look up a member by kind and name inside a symbol or node scope. Related: symbolMembers.
262- // (symbolOrNode: NodeSymbol | SyntaxNode, targetKind: SymbolKind, targetName: string) => Report<NodeSymbol | undefined>
312+ // A global query
313+ // Look up a member by kind and name inside a symbol or node scope
314+ // Signature: (symbolOrNode: NodeSymbol | SyntaxNode, targetKind: SymbolKind, targetName: string) => Report<NodeSymbol | undefined>
263315 lookupMembers = this . globalQuery ( lookupMembers ) ;
264- // A global query.
316+ // A global query
265317 // Resolve what symbol a reference node points to. Related: nodeSymbol, symbolReferences.
266- // (node: SyntaxNode) => Report<NodeSymbol | undefined> | Report<Unhandled>
318+ // Signature: (node: SyntaxNode) => Report<NodeSymbol | undefined> | Report<Unhandled>
267319 nodeReferee = this . globalQuery ( nodeReferee ) ;
268- // A global query.
320+ // A global query
269321 // Find all reference nodes that point to a given symbol. Related: nodeReferee.
270- // (symbol: NodeSymbol) => Report<SyntaxNode[]>
322+ // Signature: (symbol: NodeSymbol) => Report<SyntaxNode[]>
271323 symbolReferences = this . globalQuery ( symbolReferences ) ;
272- // A global query.
324+ // A global query
273325 // Return all AliasSymbols across the project whose originalSymbol is the given symbol (transitive).
274- // (symbol: NodeSymbol) => Report<AliasSymbol[]>
326+ // Signature: (symbol: NodeSymbol) => Report<AliasSymbol[]>
275327 symbolAliases = this . globalQuery ( symbolAliases ) ;
276- // A global query.
328+ // A global query
277329 // Return all UseSymbols across the project whose originalSymbol is the given symbol (transitive).
278- // (symbol: NodeSymbol) => Report<UseSymbol[]>
330+ // Signature: (symbol: NodeSymbol) => Report<UseSymbol[]>
279331 symbolUses = this . globalQuery ( symbolUses ) ;
280332
281- // A global query.
282- // Validate an AST node and return any compile errors.
283- // (node: SyntaxNode) => Report<void> | Report<Unhandled>
284- validateNode = this . globalQuery ( validateNode ) ;
285- // A global query.
286- // Return the fully-qualified name segments of an AST node (e.g. ['public', 'users']).
287- // (node: SyntaxNode) => Report<string[] | undefined> | Report<Unhandled>
288- nodeFullname = this . globalQuery ( fullname ) ;
289- // A global query.
290- // Return the alias string of an AST node if one is declared. Related: nodeFullname.
291- // (node: SyntaxNode) => Report<string | undefined> | Report<Unhandled>
292- nodeAlias = this . globalQuery ( nodeAlias ) ;
293- // A global query.
294- // Return the settings/options map of an AST node.
295- // (node: SyntaxNode) => Report<Settings> | Report<Unhandled>
296- nodeSettings = this . globalQuery ( nodeSettings ) ;
297-
298- // A local query.
333+ // A local query
299334 // Return the direct import filepath IDs declared by use statements in a file. Related: reachableFiles.
300- // (filepath: Filepath) => Set< Filepath>
335+ // Signature: (filepath: Filepath) => Filepath[]
301336 fileDependencies = this . localQuery ( fileDependencies ) ;
302- // A local query.
337+ // A local query
303338 // BFS-traverse imports from an entry filepath and return all reachable files. Related: fileDependencies.
304- // (entry: Filepath) => Set< Filepath>
339+ // Signature: (entry: Filepath) => Filepath[]
305340 reachableFiles = this . localQuery ( reachableFiles ) ;
306- // A global query.
341+ // A global query
307342 // Return the importable members (non-schema, schema, reuses, uses) of a schema symbol or file. Related: topLevelSchemaMembers.
308- // (symbolOrFilepath: SchemaSymbol | Filepath) => Report<{ nonSchemaMembers, schemaMembers, reuses, uses }>
343+ // Signature: (symbolOrFilepath: SchemaSymbol | Filepath) => Report<{ nonSchemaMembers, schemaMembers, reuses, uses }>
309344 usableMembers = this . globalQuery ( usableMembers ) ;
310345
311346 // transform queries
@@ -343,6 +378,7 @@ export default class Compiler {
343378 return {
344379 definitionProvider : new DBMLDefinitionProvider ( this ) ,
345380 referenceProvider : new DBMLReferencesProvider ( this ) ,
381+ // Trigger completion on these characters
346382 autocompletionProvider : new DBMLCompletionItemProvider ( this , [
347383 '.' ,
348384 ',' ,
0 commit comments