index.js (10196B)
1 import { isBindingStatement } from "../../ast/utils.js"; 2 import { defaultEnv } from "../../dist/env.js"; 3 import * as Library from "../../library/index.js"; 4 import { createContext } from "../../reducer/context.js"; 5 import { ImmutableMap } from "../../utility/immutableMap.js"; 6 import * as Result from "../../utility/result.js"; 7 import { vDict } from "../../value/index.js"; 8 import { SqOtherError } from "../SqError.js"; 9 import { SqDict } from "../SqValue/SqDict.js"; 10 import { wrapValue } from "../SqValue/index.js"; 11 import { SqValueContext } from "../SqValueContext.js"; 12 import { SqValuePath } from "../SqValuePath.js"; 13 import { ProjectItem } from "./ProjectItem.js"; 14 function getNeedToRunError() { 15 return new SqOtherError("Need to run"); 16 } 17 export class SqProject { 18 constructor(options) { 19 this.inverseGraph = new Map(); 20 this.items = new Map(); 21 this.stdLib = options?.stdLib ?? Library.getStdLib(); 22 this.environment = options?.environment ?? defaultEnv; 23 this.linker = options?.linker; 24 } 25 static create(options) { 26 return new SqProject(options); 27 } 28 getEnvironment() { 29 return this.environment; 30 } 31 setEnvironment(environment) { 32 this.environment = environment; 33 } 34 getStdLib() { 35 return this.stdLib; 36 } 37 getSourceIds() { 38 return Array.from(this.items.keys()); 39 } 40 getItem(sourceId) { 41 const item = this.items.get(sourceId); 42 if (!item) { 43 throw new Error(`Source ${sourceId} not found`); 44 } 45 return item; 46 } 47 cleanDependents(initialSourceId) { 48 const visited = new Set(); 49 const inner = (currentSourceId) => { 50 visited.add(currentSourceId); 51 if (currentSourceId !== initialSourceId) { 52 this.clean(currentSourceId); 53 } 54 for (const sourceId of this.getDependents(currentSourceId)) { 55 if (visited.has(sourceId)) { 56 continue; 57 } 58 inner(sourceId); 59 } 60 }; 61 inner(initialSourceId); 62 } 63 getDependents(sourceId) { 64 return [...(this.inverseGraph.get(sourceId)?.values() ?? [])]; 65 } 66 getDependencies(sourceId) { 67 this.parseImports(sourceId); 68 return this.getItem(sourceId).getDependencies(); 69 } 70 removeImportEdges(fromSourceId) { 71 const item = this.getItem(fromSourceId); 72 if (item.imports?.ok) { 73 for (const importData of item.imports.value) { 74 this.inverseGraph.get(importData.sourceId)?.delete(fromSourceId); 75 } 76 } 77 } 78 touchSource(sourceId) { 79 this.removeImportEdges(sourceId); 80 this.getItem(sourceId).touchSource(); 81 this.cleanDependents(sourceId); 82 } 83 setSource(sourceId, value) { 84 if (this.items.has(sourceId)) { 85 this.removeImportEdges(sourceId); 86 this.getItem(sourceId).setSource(value); 87 this.cleanDependents(sourceId); 88 } 89 else { 90 this.items.set(sourceId, new ProjectItem({ sourceId, source: value })); 91 } 92 } 93 removeSource(sourceId) { 94 if (!this.items.has(sourceId)) { 95 return; 96 } 97 this.cleanDependents(sourceId); 98 this.removeImportEdges(sourceId); 99 this.setContinues(sourceId, []); 100 this.items.delete(sourceId); 101 } 102 getSource(sourceId) { 103 return this.items.get(sourceId)?.source; 104 } 105 clean(sourceId) { 106 this.getItem(sourceId).clean(); 107 } 108 cleanAll() { 109 this.getSourceIds().forEach((id) => this.clean(id)); 110 } 111 getImportIds(sourceId) { 112 const imports = this.getImports(sourceId); 113 if (!imports) { 114 return Result.Err(getNeedToRunError()); 115 } 116 return Result.fmap(imports, (imports) => imports.map((i) => i.sourceId)); 117 } 118 getImports(sourceId) { 119 return this.getItem(sourceId).imports; 120 } 121 getContinues(sourceId) { 122 return this.getItem(sourceId).continues; 123 } 124 setContinues(sourceId, continues) { 125 for (const continueId of this.getContinues(sourceId)) { 126 this.inverseGraph.get(continueId)?.delete(sourceId); 127 } 128 for (const continueId of continues) { 129 if (!this.inverseGraph.has(continueId)) { 130 this.inverseGraph.set(continueId, new Set()); 131 } 132 this.inverseGraph.get(continueId)?.add(sourceId); 133 } 134 this.getItem(sourceId).setContinues(continues); 135 this.cleanDependents(sourceId); 136 } 137 getInternalOutput(sourceId) { 138 return this.getItem(sourceId).output ?? Result.Err(getNeedToRunError()); 139 } 140 parseImports(sourceId) { 141 const item = this.getItem(sourceId); 142 if (item.imports) { 143 return; 144 } 145 item.parseImports(this.linker); 146 for (const dependencyId of item.getDependencies()) { 147 if (!this.inverseGraph.has(dependencyId)) { 148 this.inverseGraph.set(dependencyId, new Set()); 149 } 150 this.inverseGraph.get(dependencyId)?.add(sourceId); 151 } 152 } 153 getOutput(sourceId) { 154 const internalOutputR = this.getInternalOutput(sourceId); 155 if (!internalOutputR.ok) { 156 return internalOutputR; 157 } 158 const source = this.getSource(sourceId); 159 if (source === undefined) { 160 throw new Error("Internal error: source not found"); 161 } 162 const astR = this.getItem(sourceId).ast; 163 if (!astR) { 164 throw new Error("Internal error: AST is missing when result is ok"); 165 } 166 if (!astR.ok) { 167 return astR; 168 } 169 const ast = astR.value; 170 const lastStatement = ast.statements.at(-1); 171 const hasEndExpression = !!lastStatement && !isBindingStatement(lastStatement); 172 const result = wrapValue(internalOutputR.value.result, new SqValueContext({ 173 project: this, 174 sourceId, 175 source, 176 ast, 177 valueAst: hasEndExpression ? lastStatement : ast, 178 valueAstIsPrecise: hasEndExpression, 179 path: new SqValuePath({ 180 root: "result", 181 items: [], 182 }), 183 })); 184 const [bindings, exports] = ["bindings", "exports"].map((field) => new SqDict(internalOutputR.value[field], new SqValueContext({ 185 project: this, 186 sourceId, 187 source, 188 ast: astR.value, 189 valueAst: astR.value, 190 valueAstIsPrecise: true, 191 path: new SqValuePath({ 192 root: "bindings", 193 items: [], 194 }), 195 }))); 196 return Result.Ok({ result, bindings, exports }); 197 } 198 getResult(sourceId) { 199 return Result.fmap(this.getOutput(sourceId), ({ result }) => result); 200 } 201 getBindings(sourceId) { 202 return Result.fmap(this.getOutput(sourceId), ({ bindings }) => bindings); 203 } 204 async buildExternals(sourceId, pendingIds) { 205 this.parseImports(sourceId); 206 const rImports = this.getImports(sourceId); 207 if (!rImports) { 208 throw new Error("Internal logic error"); 209 } 210 if (!rImports.ok) { 211 return rImports; 212 } 213 let externals = ImmutableMap().merge(this.getStdLib()); 214 for (const importBinding of [ 215 ...this.getItem(sourceId).getImplicitImports(), 216 ...rImports.value, 217 ]) { 218 if (!this.items.has(importBinding.sourceId)) { 219 if (!this.linker) { 220 throw new Error(`Can't load source for ${importBinding.sourceId}, linker is missing`); 221 } 222 let newSource; 223 try { 224 newSource = await this.linker.loadSource(importBinding.sourceId); 225 } 226 catch (e) { 227 return Result.Err(new SqOtherError(`Failed to load import ${importBinding.sourceId}`)); 228 } 229 this.setSource(importBinding.sourceId, newSource); 230 } 231 if (pendingIds.has(importBinding.sourceId)) { 232 return Result.Err(new SqOtherError(`Cyclic import ${importBinding.sourceId}`)); 233 } 234 await this.innerRun(importBinding.sourceId, pendingIds); 235 const outputR = this.getInternalOutput(importBinding.sourceId); 236 if (!outputR.ok) { 237 return outputR; 238 } 239 switch (importBinding.type) { 240 case "flat": 241 externals = externals.merge(outputR.value.bindings); 242 break; 243 case "named": 244 externals = externals.set(importBinding.variable, vDict(outputR.value.exports)); 245 break; 246 default: 247 throw new Error(`Internal error, ${importBinding}`); 248 } 249 } 250 return Result.Ok(externals); 251 } 252 async innerRun(sourceId, pendingIds) { 253 pendingIds.add(sourceId); 254 const cachedOutput = this.getItem(sourceId).output; 255 if (!cachedOutput) { 256 const rExternals = await this.buildExternals(sourceId, pendingIds); 257 if (!rExternals.ok) { 258 this.getItem(sourceId).failRun(rExternals.value); 259 } 260 else { 261 const context = createContext(this.getEnvironment()); 262 await this.getItem(sourceId).run(context, rExternals.value); 263 } 264 } 265 pendingIds.delete(sourceId); 266 } 267 async run(sourceId) { 268 await this.innerRun(sourceId, new Set()); 269 } 270 findValuePathByOffset(sourceId, offset) { 271 const { ast } = this.getItem(sourceId); 272 if (!ast) { 273 return Result.Err(new SqOtherError("Not parsed")); 274 } 275 if (!ast.ok) { 276 return ast; 277 } 278 const found = SqValuePath.findByOffset({ 279 ast: ast.value, 280 offset, 281 }); 282 if (!found) { 283 return Result.Err(new SqOtherError("Not found")); 284 } 285 return Result.Ok(found); 286 } 287 } 288 //# sourceMappingURL=index.js.map