@invinite-org/chartlang-compiler 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +310 -0
- package/dist/analysis/extractDependencyGraph.d.ts.map +1 -1
- package/dist/analysis/extractDependencyGraph.js +9 -1
- package/dist/analysis/extractDependencyGraph.js.map +1 -1
- package/dist/analysis/extractInputs.d.ts.map +1 -1
- package/dist/analysis/extractInputs.js +2 -0
- package/dist/analysis/extractInputs.js.map +1 -1
- package/dist/analysis/extractMaxLookback.d.ts +2 -1
- package/dist/analysis/extractMaxLookback.d.ts.map +1 -1
- package/dist/analysis/extractMaxLookback.js +90 -6
- package/dist/analysis/extractMaxLookback.js.map +1 -1
- package/dist/analysis/extractRequestedIntervals.d.ts +63 -1
- package/dist/analysis/extractRequestedIntervals.d.ts.map +1 -1
- package/dist/analysis/extractRequestedIntervals.js +245 -29
- package/dist/analysis/extractRequestedIntervals.js.map +1 -1
- package/dist/analysis/forbiddenConstructs.d.ts.map +1 -1
- package/dist/analysis/forbiddenConstructs.js +2 -41
- package/dist/analysis/forbiddenConstructs.js.map +1 -1
- package/dist/analysis/index.d.ts +4 -1
- package/dist/analysis/index.d.ts.map +1 -1
- package/dist/analysis/index.js +3 -1
- package/dist/analysis/index.js.map +1 -1
- package/dist/analysis/loopBounds.d.ts +91 -0
- package/dist/analysis/loopBounds.d.ts.map +1 -0
- package/dist/analysis/loopBounds.js +132 -0
- package/dist/analysis/loopBounds.js.map +1 -0
- package/dist/analysis/resolveIndexBound.d.ts +73 -0
- package/dist/analysis/resolveIndexBound.d.ts.map +1 -0
- package/dist/analysis/resolveIndexBound.js +336 -0
- package/dist/analysis/resolveIndexBound.js.map +1 -0
- package/dist/analysis/stateArrayCapacity.d.ts +58 -0
- package/dist/analysis/stateArrayCapacity.d.ts.map +1 -0
- package/dist/analysis/stateArrayCapacity.js +108 -0
- package/dist/analysis/stateArrayCapacity.js.map +1 -0
- package/dist/analysis/validateSecurityExpr.d.ts +25 -0
- package/dist/analysis/validateSecurityExpr.d.ts.map +1 -0
- package/dist/analysis/validateSecurityExpr.js +154 -0
- package/dist/analysis/validateSecurityExpr.js.map +1 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +22 -3
- package/dist/api.js.map +1 -1
- package/dist/diagnostics.d.ts +8 -2
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/manifest.d.ts +3 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +11 -0
- package/dist/manifest.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +148 -15
- package/dist/program.js.map +1 -1
- package/dist/transformers/callsiteIdInjection.d.ts +21 -0
- package/dist/transformers/callsiteIdInjection.d.ts.map +1 -1
- package/dist/transformers/callsiteIdInjection.js +34 -4
- package/dist/transformers/callsiteIdInjection.js.map +1 -1
- package/dist/transformers/plotKindFromCallsite.d.ts +3 -0
- package/dist/transformers/plotKindFromCallsite.d.ts.map +1 -1
- package/dist/transformers/plotKindFromCallsite.js +7 -0
- package/dist/transformers/plotKindFromCallsite.js.map +1 -1
- package/dist/transformers/resolveCallee.d.ts +21 -0
- package/dist/transformers/resolveCallee.d.ts.map +1 -1
- package/dist/transformers/resolveCallee.js +14 -1
- package/dist/transformers/resolveCallee.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forbiddenConstructs.js","sourceRoot":"","sources":["../../src/analysis/forbiddenConstructs.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport ts from \"typescript\";\n\nimport { type CompileDiagnostic, createDiagnostic } from \"../diagnostics.js\";\n\nconst HOSTILE_GLOBAL_NAMES = new Set([\n \"fetch\",\n \"setTimeout\",\n \"setInterval\",\n \"queueMicrotask\",\n \"Promise\",\n \"requestAnimationFrame\",\n \"Date\",\n \"eval\",\n \"require\",\n // Phase 7 — the indicator-composition rewriter synthesises calls to\n // this helper; user scripts must not name-collide with the slot.\n \"__chartlang_depOutput\",\n]);\n\nconst COMPARISON_OPS = new Set<ts.SyntaxKind>([\n ts.SyntaxKind.LessThanToken,\n ts.SyntaxKind.LessThanEqualsToken,\n ts.SyntaxKind.GreaterThanToken,\n ts.SyntaxKind.GreaterThanEqualsToken,\n]);\n\n/**\n * Walk the source file and emit a diagnostic for every forbidden construct:\n *\n * - `while` / `do-while` / `for-of` / `for-in` / unbounded `for` →\n * `unbounded-loop`.\n * - Self-recursive function declaration → `recursion-not-allowed`.\n * - References to hostile globals (`Math.random`, `Date.*`, `fetch`,\n * `setTimeout`, `setInterval`, `queueMicrotask`, `Promise`,\n * `requestAnimationFrame`), plus `require(...)`, dynamic `import(...)`,\n * `eval(...)`, `new Function(...)` → `hostile-global`.\n *\n * @since 0.1\n * @example\n * // const diagnostics = runForbiddenConstructs(sourceFile, \"demo.chart.ts\");\n * const fn: typeof runForbiddenConstructs = runForbiddenConstructs;\n * void fn;\n */\nexport function runForbiddenConstructs(\n sourceFile: ts.SourceFile,\n sourcePath: string,\n): ReadonlyArray<CompileDiagnostic> {\n const diagnostics: CompileDiagnostic[] = [];\n\n function emit(\n node: ts.Node,\n code: \"unbounded-loop\" | \"recursion-not-allowed\" | \"hostile-global\",\n message: string,\n ): void {\n diagnostics.push(\n createDiagnostic({\n severity: \"error\",\n code,\n message,\n file: sourcePath,\n node,\n sourceFile,\n }),\n );\n }\n\n function checkForStatement(node: ts.ForStatement): boolean {\n const init = node.initializer;\n const condition = node.condition;\n const incrementor = node.incrementor;\n if (!init || !condition || !incrementor) return false;\n if (!ts.isVariableDeclarationList(init)) return false;\n if (init.declarations.length !== 1) return false;\n const declaration = init.declarations[0];\n if (!declaration || !ts.isIdentifier(declaration.name)) return false;\n const initializer = declaration.initializer;\n if (!initializer || !ts.isNumericLiteral(initializer)) return false;\n if (!ts.isBinaryExpression(condition)) return false;\n if (!COMPARISON_OPS.has(condition.operatorToken.kind)) return false;\n if (!ts.isNumericLiteral(condition.right)) return false;\n if (!ts.isIdentifier(condition.left)) return false;\n if (condition.left.text !== declaration.name.text) return false;\n if (!ts.isPostfixUnaryExpression(incrementor)) return false;\n if (!ts.isIdentifier(incrementor.operand)) return false;\n if (incrementor.operand.text !== declaration.name.text) return false;\n return true;\n }\n\n function isInsideAncestor(node: ts.Node, predicate: (parent: ts.Node) => boolean): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (predicate(current)) return true;\n current = current.parent;\n }\n return false;\n }\n\n function isDeclarationName(parent: ts.Node, node: ts.Identifier): boolean {\n const named = parent as { readonly name?: ts.Node };\n if (named.name !== node) return false;\n return (\n ts.isFunctionDeclaration(parent) ||\n ts.isFunctionExpression(parent) ||\n ts.isClassDeclaration(parent) ||\n ts.isClassExpression(parent) ||\n ts.isVariableDeclaration(parent) ||\n ts.isParameter(parent) ||\n ts.isInterfaceDeclaration(parent) ||\n ts.isTypeAliasDeclaration(parent) ||\n ts.isEnumDeclaration(parent) ||\n ts.isModuleDeclaration(parent)\n );\n }\n\n function enclosingFunctionName(node: ts.Node): string | null {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isFunctionDeclaration(current) && current.name) {\n return current.name.text;\n }\n if (ts.isVariableDeclaration(current) && ts.isIdentifier(current.name)) {\n const initializer = current.initializer;\n if (\n initializer &&\n (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer))\n ) {\n return current.name.text;\n }\n }\n current = current.parent;\n }\n return null;\n }\n\n const visit = (node: ts.Node): void => {\n if (ts.isWhileStatement(node)) {\n emit(node, \"unbounded-loop\", \"`while` loops are not allowed.\");\n } else if (ts.isDoStatement(node)) {\n emit(node, \"unbounded-loop\", \"`do…while` loops are not allowed.\");\n } else if (ts.isForOfStatement(node)) {\n emit(node, \"unbounded-loop\", \"`for…of` loops are not allowed in Phase 1.\");\n } else if (ts.isForInStatement(node)) {\n emit(node, \"unbounded-loop\", \"`for…in` loops are not allowed.\");\n } else if (ts.isForStatement(node)) {\n if (!checkForStatement(node)) {\n emit(\n node,\n \"unbounded-loop\",\n \"`for` loops must use literal numeric bounds: for (let i = <num>; i </<= <num>; i++).\",\n );\n }\n } else if (ts.isCallExpression(node)) {\n const expression = node.expression;\n if (ts.isIdentifier(expression)) {\n if (expression.text === \"eval\") {\n emit(node, \"hostile-global\", \"`eval` is not allowed.\");\n } else if (expression.text === \"require\") {\n emit(node, \"hostile-global\", \"`require` is not allowed.\");\n }\n } else if (expression.kind === ts.SyntaxKind.ImportKeyword) {\n emit(node, \"hostile-global\", \"Dynamic `import()` is not allowed.\");\n }\n const functionName = enclosingFunctionName(node);\n if (\n functionName !== null &&\n ts.isIdentifier(expression) &&\n expression.text === functionName\n ) {\n emit(\n node,\n \"recursion-not-allowed\",\n `Self-recursive call to \\`${functionName}\\` is not allowed.`,\n );\n }\n } else if (ts.isNewExpression(node)) {\n const expression = node.expression;\n if (ts.isIdentifier(expression) && expression.text === \"Function\") {\n emit(node, \"hostile-global\", \"`new Function(...)` is not allowed.\");\n }\n } else if (ts.isPropertyAccessExpression(node)) {\n const objectName = node.expression;\n if (ts.isIdentifier(objectName) && objectName.text === \"Math\") {\n if (node.name.text === \"random\") {\n emit(node, \"hostile-global\", \"`Math.random` is not allowed.\");\n }\n } else if (ts.isIdentifier(objectName) && objectName.text === \"Date\") {\n emit(node, \"hostile-global\", \"`Date.*` is not allowed.\");\n }\n } else if (ts.isIdentifier(node)) {\n if (!HOSTILE_GLOBAL_NAMES.has(node.text)) {\n ts.forEachChild(node, visit);\n return;\n }\n const parent = node.parent;\n if (parent && isDeclarationName(parent, node)) {\n ts.forEachChild(node, visit);\n return;\n }\n if (\n parent &&\n (ts.isPropertyAccessExpression(parent) ||\n ts.isPropertyAssignment(parent) ||\n ts.isPropertySignature(parent) ||\n ts.isBindingElement(parent) ||\n ts.isImportSpecifier(parent) ||\n ts.isExportSpecifier(parent))\n ) {\n ts.forEachChild(node, visit);\n return;\n }\n const isInsideTypeContext = isInsideAncestor(\n node,\n (ancestor) => ts.isTypeNode(ancestor) || ts.isTypeReferenceNode(ancestor),\n );\n if (isInsideTypeContext) {\n ts.forEachChild(node, visit);\n return;\n }\n emit(node, \"hostile-global\", `\\`${node.text}\\` is not allowed.`);\n }\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return Object.freeze(diagnostics.slice());\n}\n"]}
|
|
1
|
+
{"version":3,"file":"forbiddenConstructs.js","sourceRoot":"","sources":["../../src/analysis/forbiddenConstructs.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport ts from \"typescript\";\n\nimport { type CompileDiagnostic, createDiagnostic } from \"../diagnostics.js\";\nimport { parseBoundedForLoop } from \"./loopBounds.js\";\n\nconst HOSTILE_GLOBAL_NAMES = new Set([\n \"fetch\",\n \"setTimeout\",\n \"setInterval\",\n \"queueMicrotask\",\n \"Promise\",\n \"requestAnimationFrame\",\n \"Date\",\n \"eval\",\n \"require\",\n // Phase 7 — the indicator-composition rewriter synthesises calls to\n // this helper; user scripts must not name-collide with the slot.\n \"__chartlang_depOutput\",\n]);\n\n/**\n * Walk the source file and emit a diagnostic for every forbidden construct:\n *\n * - `while` / `do-while` / `for-of` / `for-in` / unbounded `for` →\n * `unbounded-loop`.\n * - Self-recursive function declaration → `recursion-not-allowed`.\n * - References to hostile globals (`Math.random`, `Date.*`, `fetch`,\n * `setTimeout`, `setInterval`, `queueMicrotask`, `Promise`,\n * `requestAnimationFrame`), plus `require(...)`, dynamic `import(...)`,\n * `eval(...)`, `new Function(...)` → `hostile-global`.\n *\n * @since 0.1\n * @example\n * // const diagnostics = runForbiddenConstructs(sourceFile, \"demo.chart.ts\");\n * const fn: typeof runForbiddenConstructs = runForbiddenConstructs;\n * void fn;\n */\nexport function runForbiddenConstructs(\n sourceFile: ts.SourceFile,\n sourcePath: string,\n): ReadonlyArray<CompileDiagnostic> {\n const diagnostics: CompileDiagnostic[] = [];\n\n function emit(\n node: ts.Node,\n code: \"unbounded-loop\" | \"recursion-not-allowed\" | \"hostile-global\",\n message: string,\n ): void {\n diagnostics.push(\n createDiagnostic({\n severity: \"error\",\n code,\n message,\n file: sourcePath,\n node,\n sourceFile,\n }),\n );\n }\n\n function isInsideAncestor(node: ts.Node, predicate: (parent: ts.Node) => boolean): boolean {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (predicate(current)) return true;\n current = current.parent;\n }\n return false;\n }\n\n function isDeclarationName(parent: ts.Node, node: ts.Identifier): boolean {\n const named = parent as { readonly name?: ts.Node };\n if (named.name !== node) return false;\n return (\n ts.isFunctionDeclaration(parent) ||\n ts.isFunctionExpression(parent) ||\n ts.isClassDeclaration(parent) ||\n ts.isClassExpression(parent) ||\n ts.isVariableDeclaration(parent) ||\n ts.isParameter(parent) ||\n ts.isInterfaceDeclaration(parent) ||\n ts.isTypeAliasDeclaration(parent) ||\n ts.isEnumDeclaration(parent) ||\n ts.isModuleDeclaration(parent)\n );\n }\n\n function enclosingFunctionName(node: ts.Node): string | null {\n let current: ts.Node | undefined = node.parent;\n while (current) {\n if (ts.isFunctionDeclaration(current) && current.name) {\n return current.name.text;\n }\n if (ts.isVariableDeclaration(current) && ts.isIdentifier(current.name)) {\n const initializer = current.initializer;\n if (\n initializer &&\n (ts.isFunctionExpression(initializer) || ts.isArrowFunction(initializer))\n ) {\n return current.name.text;\n }\n }\n current = current.parent;\n }\n return null;\n }\n\n const visit = (node: ts.Node): void => {\n if (ts.isWhileStatement(node)) {\n emit(node, \"unbounded-loop\", \"`while` loops are not allowed.\");\n } else if (ts.isDoStatement(node)) {\n emit(node, \"unbounded-loop\", \"`do…while` loops are not allowed.\");\n } else if (ts.isForOfStatement(node)) {\n emit(node, \"unbounded-loop\", \"`for…of` loops are not allowed in Phase 1.\");\n } else if (ts.isForInStatement(node)) {\n emit(node, \"unbounded-loop\", \"`for…in` loops are not allowed.\");\n } else if (ts.isForStatement(node)) {\n if (parseBoundedForLoop(node) === null) {\n emit(\n node,\n \"unbounded-loop\",\n \"`for` loops must use literal numeric bounds: for (let i = <num>; i </<= <num>; i++).\",\n );\n }\n } else if (ts.isCallExpression(node)) {\n const expression = node.expression;\n if (ts.isIdentifier(expression)) {\n if (expression.text === \"eval\") {\n emit(node, \"hostile-global\", \"`eval` is not allowed.\");\n } else if (expression.text === \"require\") {\n emit(node, \"hostile-global\", \"`require` is not allowed.\");\n }\n } else if (expression.kind === ts.SyntaxKind.ImportKeyword) {\n emit(node, \"hostile-global\", \"Dynamic `import()` is not allowed.\");\n }\n const functionName = enclosingFunctionName(node);\n if (\n functionName !== null &&\n ts.isIdentifier(expression) &&\n expression.text === functionName\n ) {\n emit(\n node,\n \"recursion-not-allowed\",\n `Self-recursive call to \\`${functionName}\\` is not allowed.`,\n );\n }\n } else if (ts.isNewExpression(node)) {\n const expression = node.expression;\n if (ts.isIdentifier(expression) && expression.text === \"Function\") {\n emit(node, \"hostile-global\", \"`new Function(...)` is not allowed.\");\n }\n } else if (ts.isPropertyAccessExpression(node)) {\n const objectName = node.expression;\n if (ts.isIdentifier(objectName) && objectName.text === \"Math\") {\n if (node.name.text === \"random\") {\n emit(node, \"hostile-global\", \"`Math.random` is not allowed.\");\n }\n } else if (ts.isIdentifier(objectName) && objectName.text === \"Date\") {\n emit(node, \"hostile-global\", \"`Date.*` is not allowed.\");\n }\n } else if (ts.isIdentifier(node)) {\n if (!HOSTILE_GLOBAL_NAMES.has(node.text)) {\n ts.forEachChild(node, visit);\n return;\n }\n const parent = node.parent;\n if (parent && isDeclarationName(parent, node)) {\n ts.forEachChild(node, visit);\n return;\n }\n if (\n parent &&\n (ts.isPropertyAccessExpression(parent) ||\n ts.isPropertyAssignment(parent) ||\n ts.isPropertySignature(parent) ||\n ts.isBindingElement(parent) ||\n ts.isImportSpecifier(parent) ||\n ts.isExportSpecifier(parent))\n ) {\n ts.forEachChild(node, visit);\n return;\n }\n const isInsideTypeContext = isInsideAncestor(\n node,\n (ancestor) => ts.isTypeNode(ancestor) || ts.isTypeReferenceNode(ancestor),\n );\n if (isInsideTypeContext) {\n ts.forEachChild(node, visit);\n return;\n }\n emit(node, \"hostile-global\", `\\`${node.text}\\` is not allowed.`);\n }\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n\n return Object.freeze(diagnostics.slice());\n}\n"]}
|
package/dist/analysis/index.d.ts
CHANGED
|
@@ -2,12 +2,15 @@ export { runStructuralChecks } from "./structuralChecks.js";
|
|
|
2
2
|
export type { StructuralBindingInfo, StructuralCheckResult } from "./structuralChecks.js";
|
|
3
3
|
export { runForbiddenConstructs } from "./forbiddenConstructs.js";
|
|
4
4
|
export { runStatefulCallInLoop } from "./statefulCallInLoop.js";
|
|
5
|
+
export { MAX_STATE_ARRAY_CAPACITY, runStateArrayCapacity } from "./stateArrayCapacity.js";
|
|
5
6
|
export { extractCapabilities } from "./extractCapabilities.js";
|
|
6
7
|
export { extractMaxLookback } from "./extractMaxLookback.js";
|
|
7
8
|
export type { ExtractMaxLookbackResult } from "./extractMaxLookback.js";
|
|
8
9
|
export { extractInputs } from "./extractInputs.js";
|
|
9
10
|
export type { ExtractedDescriptor, ExtractInputsResult } from "./extractInputs.js";
|
|
10
|
-
export { extractRequestedIntervals } from "./extractRequestedIntervals.js";
|
|
11
|
+
export { extractRequestAnalysis, extractRequestedIntervals } from "./extractRequestedIntervals.js";
|
|
12
|
+
export type { RequestAnalysis } from "./extractRequestedIntervals.js";
|
|
13
|
+
export { validateSecurityExpr } from "./validateSecurityExpr.js";
|
|
11
14
|
export { validateLowerTfIntervals } from "./validateLowerTfIntervals.js";
|
|
12
15
|
export { extractRequiresIntervals } from "./extractRequiresIntervals.js";
|
|
13
16
|
export { extractAlertConditions } from "./extractAlertConditions.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACnF,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AACnG,YAAY,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,YAAY,EACR,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,eAAe,GAClB,MAAM,6BAA6B,CAAC"}
|
package/dist/analysis/index.js
CHANGED
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
export { runStructuralChecks } from "./structuralChecks.js";
|
|
4
4
|
export { runForbiddenConstructs } from "./forbiddenConstructs.js";
|
|
5
5
|
export { runStatefulCallInLoop } from "./statefulCallInLoop.js";
|
|
6
|
+
export { MAX_STATE_ARRAY_CAPACITY, runStateArrayCapacity } from "./stateArrayCapacity.js";
|
|
6
7
|
export { extractCapabilities } from "./extractCapabilities.js";
|
|
7
8
|
export { extractMaxLookback } from "./extractMaxLookback.js";
|
|
8
9
|
export { extractInputs } from "./extractInputs.js";
|
|
9
|
-
export { extractRequestedIntervals } from "./extractRequestedIntervals.js";
|
|
10
|
+
export { extractRequestAnalysis, extractRequestedIntervals } from "./extractRequestedIntervals.js";
|
|
11
|
+
export { validateSecurityExpr } from "./validateSecurityExpr.js";
|
|
10
12
|
export { validateLowerTfIntervals } from "./validateLowerTfIntervals.js";
|
|
11
13
|
export { extractRequiresIntervals } from "./extractRequiresIntervals.js";
|
|
12
14
|
export { extractAlertConditions } from "./extractAlertConditions.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,+DAA+D;AAE/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAEnG,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nexport { runStructuralChecks } from \"./structuralChecks.js\";\nexport type { StructuralBindingInfo, StructuralCheckResult } from \"./structuralChecks.js\";\nexport { runForbiddenConstructs } from \"./forbiddenConstructs.js\";\nexport { runStatefulCallInLoop } from \"./statefulCallInLoop.js\";\nexport { MAX_STATE_ARRAY_CAPACITY, runStateArrayCapacity } from \"./stateArrayCapacity.js\";\nexport { extractCapabilities } from \"./extractCapabilities.js\";\nexport { extractMaxLookback } from \"./extractMaxLookback.js\";\nexport type { ExtractMaxLookbackResult } from \"./extractMaxLookback.js\";\nexport { extractInputs } from \"./extractInputs.js\";\nexport type { ExtractedDescriptor, ExtractInputsResult } from \"./extractInputs.js\";\nexport { extractRequestAnalysis, extractRequestedIntervals } from \"./extractRequestedIntervals.js\";\nexport type { RequestAnalysis } from \"./extractRequestedIntervals.js\";\nexport { validateSecurityExpr } from \"./validateSecurityExpr.js\";\nexport { validateLowerTfIntervals } from \"./validateLowerTfIntervals.js\";\nexport { extractRequiresIntervals } from \"./extractRequiresIntervals.js\";\nexport { extractAlertConditions } from \"./extractAlertConditions.js\";\nexport type { ExtractAlertConditionsResult } from \"./extractAlertConditions.js\";\nexport { extractDependencyGraph } from \"./extractDependencyGraph.js\";\nexport type {\n DepConsumesEntry,\n DepGraph,\n DrawnScript,\n PrivateDep,\n ProducerRef,\n ProducerSnapshot,\n ResolveProducer,\n} from \"./extractDependencyGraph.js\";\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* The comparison operators a legal chartlang `for` condition may use.
|
|
4
|
+
* Shared by `parseBoundedForLoop` (which captures the operator so a sizer
|
|
5
|
+
* can derive the loop's max index) and `forbiddenConstructs` (which rejects
|
|
6
|
+
* any other condition shape) so the two passes recognise the same set.
|
|
7
|
+
*
|
|
8
|
+
* @since 0.1
|
|
9
|
+
* @stable
|
|
10
|
+
* @example
|
|
11
|
+
* COMPARISON_OPS.has(ts.SyntaxKind.LessThanToken); // → true
|
|
12
|
+
*/
|
|
13
|
+
export declare const COMPARISON_OPS: ReadonlySet<ts.SyntaxKind>;
|
|
14
|
+
/**
|
|
15
|
+
* The parsed shape of a legal chartlang `for` loop.
|
|
16
|
+
*
|
|
17
|
+
* @since 0.1
|
|
18
|
+
* @stable
|
|
19
|
+
* @example
|
|
20
|
+
* const loop: BoundedForLoop = {
|
|
21
|
+
* varName: "i",
|
|
22
|
+
* start: 0,
|
|
23
|
+
* op: ts.SyntaxKind.LessThanToken,
|
|
24
|
+
* limit: 5,
|
|
25
|
+
* };
|
|
26
|
+
* void loop;
|
|
27
|
+
*/
|
|
28
|
+
export type BoundedForLoop = Readonly<{
|
|
29
|
+
/** The induction variable name (the `i` in `for (let i = …)`). */
|
|
30
|
+
varName: string;
|
|
31
|
+
/** The literal initial value (`for (let i = <start>; …)`). */
|
|
32
|
+
start: number;
|
|
33
|
+
/** The comparison operator token used in the condition. */
|
|
34
|
+
op: ts.SyntaxKind;
|
|
35
|
+
/** The literal right-hand bound (`… i <op> <limit>; …`). */
|
|
36
|
+
limit: number;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Parse a `ts.ForStatement` into its `BoundedForLoop` shape, or `null`
|
|
40
|
+
* when it is not the one legal chartlang loop form
|
|
41
|
+
* (`for (let i = <numLit>; i <comparison> <numLit>; i++)` — single
|
|
42
|
+
* `let` init, id-on-left/literal-on-right condition, postfix `i++`).
|
|
43
|
+
* The single source of truth for "what is a bounded loop"; both
|
|
44
|
+
* `forbiddenConstructs` (reject everything else) and
|
|
45
|
+
* `resolveIndexUpperBound` (size the index range) call it so the two
|
|
46
|
+
* passes can never disagree.
|
|
47
|
+
*
|
|
48
|
+
* @since 0.1
|
|
49
|
+
* @stable
|
|
50
|
+
* @example
|
|
51
|
+
* // for (let i = 0; i < 5; i++) → { varName: "i", start: 0,
|
|
52
|
+
* // op: LessThanToken, limit: 5 }
|
|
53
|
+
* const fn: typeof parseBoundedForLoop = parseBoundedForLoop;
|
|
54
|
+
* void fn;
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseBoundedForLoop(node: ts.ForStatement): BoundedForLoop | null;
|
|
57
|
+
/**
|
|
58
|
+
* The induction variable's **declaration** identifier of the single legal
|
|
59
|
+
* chartlang loop *initializer* shape, or `null` otherwise. A sizer calls
|
|
60
|
+
* this directly when it needs the declaration node (not just the `varName`
|
|
61
|
+
* text) to ask the type checker whether an index use resolves to this
|
|
62
|
+
* loop's own binding rather than a nested shadow of the same name. Shares
|
|
63
|
+
* `parseBoundedForLoop`'s initializer acceptance via `parseLoopInit`.
|
|
64
|
+
*
|
|
65
|
+
* @since 0.1
|
|
66
|
+
* @stable
|
|
67
|
+
* @example
|
|
68
|
+
* // for (let i = 0; i < 5; i++) → the `i` declaration identifier
|
|
69
|
+
* const fn: typeof boundedLoopVarId = boundedLoopVarId;
|
|
70
|
+
* void fn;
|
|
71
|
+
*/
|
|
72
|
+
export declare function boundedLoopVarId(node: ts.ForStatement): ts.Identifier | null;
|
|
73
|
+
/**
|
|
74
|
+
* Unwrap any number of nested parentheses around an expression. The Pine
|
|
75
|
+
* converter emits a historical bar offset as the parenthesised form
|
|
76
|
+
* `bar.point(-(N), …)` (see the converter's `anchorToWorldPoint`), so the
|
|
77
|
+
* lookback recogniser must peel the parens before matching the literal;
|
|
78
|
+
* the index-bound resolver does the same before matching a numeric leaf.
|
|
79
|
+
* Housed here — a leaf module with no analysis-package imports — so both
|
|
80
|
+
* `extractMaxLookback` and `resolveIndexBound` can share it without a
|
|
81
|
+
* circular import.
|
|
82
|
+
*
|
|
83
|
+
* @since 0.1
|
|
84
|
+
* @stable
|
|
85
|
+
* @example
|
|
86
|
+
* // unwrapParens of `((7))` → the `7` numeric-literal node
|
|
87
|
+
* const fn: typeof unwrapParens = unwrapParens;
|
|
88
|
+
* void fn;
|
|
89
|
+
*/
|
|
90
|
+
export declare function unwrapParens(node: ts.Expression): ts.Expression;
|
|
91
|
+
//# sourceMappingURL=loopBounds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopBounds.d.ts","sourceRoot":"","sources":["../../src/analysis/loopBounds.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,EAAE,WAAW,CAAC,EAAE,CAAC,UAAU,CAKpD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IAClC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC;IAClB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,GAAG,cAAc,GAAG,IAAI,CAoBhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,GAAG,EAAE,CAAC,UAAU,GAAG,IAAI,CAE5E;AAsBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAI/D"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Copyright (c) 2026 Invinite. Licensed under the MIT License.
|
|
2
|
+
// See the LICENSE file in the repo root for full license text.
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
/**
|
|
5
|
+
* The comparison operators a legal chartlang `for` condition may use.
|
|
6
|
+
* Shared by `parseBoundedForLoop` (which captures the operator so a sizer
|
|
7
|
+
* can derive the loop's max index) and `forbiddenConstructs` (which rejects
|
|
8
|
+
* any other condition shape) so the two passes recognise the same set.
|
|
9
|
+
*
|
|
10
|
+
* @since 0.1
|
|
11
|
+
* @stable
|
|
12
|
+
* @example
|
|
13
|
+
* COMPARISON_OPS.has(ts.SyntaxKind.LessThanToken); // → true
|
|
14
|
+
*/
|
|
15
|
+
export const COMPARISON_OPS = new Set([
|
|
16
|
+
ts.SyntaxKind.LessThanToken,
|
|
17
|
+
ts.SyntaxKind.LessThanEqualsToken,
|
|
18
|
+
ts.SyntaxKind.GreaterThanToken,
|
|
19
|
+
ts.SyntaxKind.GreaterThanEqualsToken,
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* Parse a `ts.ForStatement` into its `BoundedForLoop` shape, or `null`
|
|
23
|
+
* when it is not the one legal chartlang loop form
|
|
24
|
+
* (`for (let i = <numLit>; i <comparison> <numLit>; i++)` — single
|
|
25
|
+
* `let` init, id-on-left/literal-on-right condition, postfix `i++`).
|
|
26
|
+
* The single source of truth for "what is a bounded loop"; both
|
|
27
|
+
* `forbiddenConstructs` (reject everything else) and
|
|
28
|
+
* `resolveIndexUpperBound` (size the index range) call it so the two
|
|
29
|
+
* passes can never disagree.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.1
|
|
32
|
+
* @stable
|
|
33
|
+
* @example
|
|
34
|
+
* // for (let i = 0; i < 5; i++) → { varName: "i", start: 0,
|
|
35
|
+
* // op: LessThanToken, limit: 5 }
|
|
36
|
+
* const fn: typeof parseBoundedForLoop = parseBoundedForLoop;
|
|
37
|
+
* void fn;
|
|
38
|
+
*/
|
|
39
|
+
export function parseBoundedForLoop(node) {
|
|
40
|
+
const init = parseLoopInit(node);
|
|
41
|
+
if (init === null)
|
|
42
|
+
return null;
|
|
43
|
+
const condition = node.condition;
|
|
44
|
+
const incrementor = node.incrementor;
|
|
45
|
+
if (!condition || !incrementor)
|
|
46
|
+
return null;
|
|
47
|
+
if (!ts.isBinaryExpression(condition))
|
|
48
|
+
return null;
|
|
49
|
+
if (!COMPARISON_OPS.has(condition.operatorToken.kind))
|
|
50
|
+
return null;
|
|
51
|
+
if (!ts.isNumericLiteral(condition.right))
|
|
52
|
+
return null;
|
|
53
|
+
if (!ts.isIdentifier(condition.left))
|
|
54
|
+
return null;
|
|
55
|
+
if (condition.left.text !== init.varId.text)
|
|
56
|
+
return null;
|
|
57
|
+
if (!ts.isPostfixUnaryExpression(incrementor))
|
|
58
|
+
return null;
|
|
59
|
+
if (!ts.isIdentifier(incrementor.operand))
|
|
60
|
+
return null;
|
|
61
|
+
if (incrementor.operand.text !== init.varId.text)
|
|
62
|
+
return null;
|
|
63
|
+
return {
|
|
64
|
+
varName: init.varId.text,
|
|
65
|
+
start: Number(init.start.text),
|
|
66
|
+
op: condition.operatorToken.kind,
|
|
67
|
+
limit: Number(condition.right.text),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The induction variable's **declaration** identifier of the single legal
|
|
72
|
+
* chartlang loop *initializer* shape, or `null` otherwise. A sizer calls
|
|
73
|
+
* this directly when it needs the declaration node (not just the `varName`
|
|
74
|
+
* text) to ask the type checker whether an index use resolves to this
|
|
75
|
+
* loop's own binding rather than a nested shadow of the same name. Shares
|
|
76
|
+
* `parseBoundedForLoop`'s initializer acceptance via `parseLoopInit`.
|
|
77
|
+
*
|
|
78
|
+
* @since 0.1
|
|
79
|
+
* @stable
|
|
80
|
+
* @example
|
|
81
|
+
* // for (let i = 0; i < 5; i++) → the `i` declaration identifier
|
|
82
|
+
* const fn: typeof boundedLoopVarId = boundedLoopVarId;
|
|
83
|
+
* void fn;
|
|
84
|
+
*/
|
|
85
|
+
export function boundedLoopVarId(node) {
|
|
86
|
+
return parseLoopInit(node)?.varId ?? null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The accepted `for (let i = <numLit>; …)` initializer — a single-
|
|
90
|
+
* declaration `let`/`const` list whose name is an identifier with a
|
|
91
|
+
* numeric-literal start value — captured as both nodes, or `null`. The one
|
|
92
|
+
* place the initializer shape is recognised; `parseBoundedForLoop` and
|
|
93
|
+
* `boundedLoopVarId` both build on it (no narrowing casts in either).
|
|
94
|
+
*/
|
|
95
|
+
function parseLoopInit(node) {
|
|
96
|
+
const init = node.initializer;
|
|
97
|
+
if (!init || !ts.isVariableDeclarationList(init))
|
|
98
|
+
return null;
|
|
99
|
+
if (init.declarations.length !== 1)
|
|
100
|
+
return null;
|
|
101
|
+
const declaration = init.declarations[0];
|
|
102
|
+
if (!declaration || !ts.isIdentifier(declaration.name))
|
|
103
|
+
return null;
|
|
104
|
+
const start = declaration.initializer;
|
|
105
|
+
if (!start || !ts.isNumericLiteral(start))
|
|
106
|
+
return null;
|
|
107
|
+
return { varId: declaration.name, start };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Unwrap any number of nested parentheses around an expression. The Pine
|
|
111
|
+
* converter emits a historical bar offset as the parenthesised form
|
|
112
|
+
* `bar.point(-(N), …)` (see the converter's `anchorToWorldPoint`), so the
|
|
113
|
+
* lookback recogniser must peel the parens before matching the literal;
|
|
114
|
+
* the index-bound resolver does the same before matching a numeric leaf.
|
|
115
|
+
* Housed here — a leaf module with no analysis-package imports — so both
|
|
116
|
+
* `extractMaxLookback` and `resolveIndexBound` can share it without a
|
|
117
|
+
* circular import.
|
|
118
|
+
*
|
|
119
|
+
* @since 0.1
|
|
120
|
+
* @stable
|
|
121
|
+
* @example
|
|
122
|
+
* // unwrapParens of `((7))` → the `7` numeric-literal node
|
|
123
|
+
* const fn: typeof unwrapParens = unwrapParens;
|
|
124
|
+
* void fn;
|
|
125
|
+
*/
|
|
126
|
+
export function unwrapParens(node) {
|
|
127
|
+
let current = node;
|
|
128
|
+
while (ts.isParenthesizedExpression(current))
|
|
129
|
+
current = current.expression;
|
|
130
|
+
return current;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=loopBounds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopBounds.js","sourceRoot":"","sources":["../../src/analysis/loopBounds.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Invinite. Licensed under the MIT License.\n// See the LICENSE file in the repo root for full license text.\n\nimport ts from \"typescript\";\n\n/**\n * The comparison operators a legal chartlang `for` condition may use.\n * Shared by `parseBoundedForLoop` (which captures the operator so a sizer\n * can derive the loop's max index) and `forbiddenConstructs` (which rejects\n * any other condition shape) so the two passes recognise the same set.\n *\n * @since 0.1\n * @stable\n * @example\n * COMPARISON_OPS.has(ts.SyntaxKind.LessThanToken); // → true\n */\nexport const COMPARISON_OPS: ReadonlySet<ts.SyntaxKind> = new Set<ts.SyntaxKind>([\n ts.SyntaxKind.LessThanToken,\n ts.SyntaxKind.LessThanEqualsToken,\n ts.SyntaxKind.GreaterThanToken,\n ts.SyntaxKind.GreaterThanEqualsToken,\n]);\n\n/**\n * The parsed shape of a legal chartlang `for` loop.\n *\n * @since 0.1\n * @stable\n * @example\n * const loop: BoundedForLoop = {\n * varName: \"i\",\n * start: 0,\n * op: ts.SyntaxKind.LessThanToken,\n * limit: 5,\n * };\n * void loop;\n */\nexport type BoundedForLoop = Readonly<{\n /** The induction variable name (the `i` in `for (let i = …)`). */\n varName: string;\n /** The literal initial value (`for (let i = <start>; …)`). */\n start: number;\n /** The comparison operator token used in the condition. */\n op: ts.SyntaxKind;\n /** The literal right-hand bound (`… i <op> <limit>; …`). */\n limit: number;\n}>;\n\n/**\n * Parse a `ts.ForStatement` into its `BoundedForLoop` shape, or `null`\n * when it is not the one legal chartlang loop form\n * (`for (let i = <numLit>; i <comparison> <numLit>; i++)` — single\n * `let` init, id-on-left/literal-on-right condition, postfix `i++`).\n * The single source of truth for \"what is a bounded loop\"; both\n * `forbiddenConstructs` (reject everything else) and\n * `resolveIndexUpperBound` (size the index range) call it so the two\n * passes can never disagree.\n *\n * @since 0.1\n * @stable\n * @example\n * // for (let i = 0; i < 5; i++) → { varName: \"i\", start: 0,\n * // op: LessThanToken, limit: 5 }\n * const fn: typeof parseBoundedForLoop = parseBoundedForLoop;\n * void fn;\n */\nexport function parseBoundedForLoop(node: ts.ForStatement): BoundedForLoop | null {\n const init = parseLoopInit(node);\n if (init === null) return null;\n const condition = node.condition;\n const incrementor = node.incrementor;\n if (!condition || !incrementor) return null;\n if (!ts.isBinaryExpression(condition)) return null;\n if (!COMPARISON_OPS.has(condition.operatorToken.kind)) return null;\n if (!ts.isNumericLiteral(condition.right)) return null;\n if (!ts.isIdentifier(condition.left)) return null;\n if (condition.left.text !== init.varId.text) return null;\n if (!ts.isPostfixUnaryExpression(incrementor)) return null;\n if (!ts.isIdentifier(incrementor.operand)) return null;\n if (incrementor.operand.text !== init.varId.text) return null;\n return {\n varName: init.varId.text,\n start: Number(init.start.text),\n op: condition.operatorToken.kind,\n limit: Number(condition.right.text),\n };\n}\n\n/**\n * The induction variable's **declaration** identifier of the single legal\n * chartlang loop *initializer* shape, or `null` otherwise. A sizer calls\n * this directly when it needs the declaration node (not just the `varName`\n * text) to ask the type checker whether an index use resolves to this\n * loop's own binding rather than a nested shadow of the same name. Shares\n * `parseBoundedForLoop`'s initializer acceptance via `parseLoopInit`.\n *\n * @since 0.1\n * @stable\n * @example\n * // for (let i = 0; i < 5; i++) → the `i` declaration identifier\n * const fn: typeof boundedLoopVarId = boundedLoopVarId;\n * void fn;\n */\nexport function boundedLoopVarId(node: ts.ForStatement): ts.Identifier | null {\n return parseLoopInit(node)?.varId ?? null;\n}\n\n/**\n * The accepted `for (let i = <numLit>; …)` initializer — a single-\n * declaration `let`/`const` list whose name is an identifier with a\n * numeric-literal start value — captured as both nodes, or `null`. The one\n * place the initializer shape is recognised; `parseBoundedForLoop` and\n * `boundedLoopVarId` both build on it (no narrowing casts in either).\n */\nfunction parseLoopInit(\n node: ts.ForStatement,\n): Readonly<{ varId: ts.Identifier; start: ts.NumericLiteral }> | null {\n const init = node.initializer;\n if (!init || !ts.isVariableDeclarationList(init)) return null;\n if (init.declarations.length !== 1) return null;\n const declaration = init.declarations[0];\n if (!declaration || !ts.isIdentifier(declaration.name)) return null;\n const start = declaration.initializer;\n if (!start || !ts.isNumericLiteral(start)) return null;\n return { varId: declaration.name, start };\n}\n\n/**\n * Unwrap any number of nested parentheses around an expression. The Pine\n * converter emits a historical bar offset as the parenthesised form\n * `bar.point(-(N), …)` (see the converter's `anchorToWorldPoint`), so the\n * lookback recogniser must peel the parens before matching the literal;\n * the index-bound resolver does the same before matching a numeric leaf.\n * Housed here — a leaf module with no analysis-package imports — so both\n * `extractMaxLookback` and `resolveIndexBound` can share it without a\n * circular import.\n *\n * @since 0.1\n * @stable\n * @example\n * // unwrapParens of `((7))` → the `7` numeric-literal node\n * const fn: typeof unwrapParens = unwrapParens;\n * void fn;\n */\nexport function unwrapParens(node: ts.Expression): ts.Expression {\n let current = node;\n while (ts.isParenthesizedExpression(current)) current = current.expression;\n return current;\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* Compile-time context for resolving a series index's upper bound.
|
|
4
|
+
*
|
|
5
|
+
* @since 0.1
|
|
6
|
+
* @stable
|
|
7
|
+
* @example
|
|
8
|
+
* const ctx: IndexBoundContext = {
|
|
9
|
+
* constEnv: new Map([["k", 3]]),
|
|
10
|
+
* checker, // ts.TypeChecker
|
|
11
|
+
* };
|
|
12
|
+
* void ctx;
|
|
13
|
+
*/
|
|
14
|
+
export type IndexBoundContext = Readonly<{
|
|
15
|
+
/** `const <id> = <numeric literal>` bindings visible at the index use site. */
|
|
16
|
+
constEnv: ReadonlyMap<string, number>;
|
|
17
|
+
/** Checker used to avoid resolving loop variables through a shadowed name. */
|
|
18
|
+
checker: ts.TypeChecker;
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* The provable maximum non-negative integer a series-index expression
|
|
22
|
+
* can reach at runtime, or `null` when no sound upper bound exists.
|
|
23
|
+
* Over-approximates: a result is always `>=` the true max index, so the
|
|
24
|
+
* runtime buffer (sized `maxLookback + 1`) never under-sizes. `null`
|
|
25
|
+
* signals the caller to fall back to the 5000-slot dynamic buffer.
|
|
26
|
+
*
|
|
27
|
+
* Resolves any expression built from numeric literals, `const`
|
|
28
|
+
* numeric-literal bindings (`ctx.constEnv`), bounded-loop induction
|
|
29
|
+
* variables (resolved to their full range), the binary operators `+`,
|
|
30
|
+
* `−`, `*`, unary `±`, and parentheses, by computing its integer
|
|
31
|
+
* interval and returning the **upper** endpoint. Any other node (another
|
|
32
|
+
* identifier, a call, `/`, `%`, `**`, a bitwise op, a non-numeric
|
|
33
|
+
* literal) collapses the containing interval — and thus the whole
|
|
34
|
+
* index — to `null`.
|
|
35
|
+
*
|
|
36
|
+
* @since 0.1
|
|
37
|
+
* @stable
|
|
38
|
+
* @example
|
|
39
|
+
* // for (let i = 0; i < 5; i++) { series[i + 1]; }
|
|
40
|
+
* // resolveIndexUpperBound(<the `i + 1` arg>, <access node>, ctx) → 5
|
|
41
|
+
* const fn: typeof resolveIndexUpperBound = resolveIndexUpperBound;
|
|
42
|
+
* void fn;
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveIndexUpperBound(argument: ts.Expression, node: ts.Node, ctx: IndexBoundContext): number | null;
|
|
45
|
+
/**
|
|
46
|
+
* The `const <id> = <numeric literal>` bindings lexically visible at a
|
|
47
|
+
* specific series-index expression. Only `const` initialised with a
|
|
48
|
+
* numeric literal — or a unary `+`/`-` on one — is included (mirroring
|
|
49
|
+
* `extractInputs.readLiteral`'s numeric handling); a binary initialiser
|
|
50
|
+
* is left for Task 2's interval evaluator and is simply omitted. The walk
|
|
51
|
+
* runs from `useSite` outward through its lexical containers up to
|
|
52
|
+
* `scopeRoot`, collecting only declarations that occur before
|
|
53
|
+
* `useSite.pos` within each container, so it never sees a declaration
|
|
54
|
+
* after the read, inside a sibling block, or in a nested function/class
|
|
55
|
+
* that does not contain `useSite`. The innermost visible binding for a
|
|
56
|
+
* name wins (normal shadowing) — including binders that are not
|
|
57
|
+
* `var`/`let`/`const` statements: a `for`-init induction variable and a
|
|
58
|
+
* function parameter shadow an outer numeric `const` of the same name
|
|
59
|
+
* (`markContainerBinders`), so a reassigned `for (let i …)` index or a
|
|
60
|
+
* `request.security((k) => series[k])` callback parameter can never leak
|
|
61
|
+
* an unrelated outer `const k`'s value into the bound (which would
|
|
62
|
+
* under-size the buffer).
|
|
63
|
+
*
|
|
64
|
+
* @since 0.1
|
|
65
|
+
* @stable
|
|
66
|
+
* @example
|
|
67
|
+
* // const k = 3; series[k];
|
|
68
|
+
* // collectConstNumberEnv(<the `k` arg>, scope).get("k") → 3
|
|
69
|
+
* const fn: typeof collectConstNumberEnv = collectConstNumberEnv;
|
|
70
|
+
* void fn;
|
|
71
|
+
*/
|
|
72
|
+
export declare function collectConstNumberEnv(useSite: ts.Node, scopeRoot: ts.Node): ReadonlyMap<string, number>;
|
|
73
|
+
//# sourceMappingURL=resolveIndexBound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveIndexBound.d.ts","sourceRoot":"","sources":["../../src/analysis/resolveIndexBound.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,YAAY,CAAC;AAS5B;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACrC,+EAA+E;IAC/E,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,8EAA8E;IAC9E,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC;CAC3B,CAAC,CAAC;AAUH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,EAAE,CAAC,UAAU,EACvB,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,GAAG,EAAE,iBAAiB,GACvB,MAAM,GAAG,IAAI,CAGf;AAqLD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CACjC,OAAO,EAAE,EAAE,CAAC,IAAI,EAChB,SAAS,EAAE,EAAE,CAAC,IAAI,GACnB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CA+B7B"}
|