dsh-ptc-plus 0.3.1 → 0.4.1

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.
Files changed (200) hide show
  1. package/README.en.md +66 -73
  2. package/README.md +66 -73
  3. package/assets/ptc-plus-bindings-en.png +0 -0
  4. package/assets/ptc-plus-bindings-zh.png +0 -0
  5. package/assets/ptc-plus-repair-en.png +0 -0
  6. package/assets/ptc-plus-repair-zh.png +0 -0
  7. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  8. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  9. package/assets/ptc-plus-settings-en.png +0 -0
  10. package/assets/ptc-plus-settings-zh.png +0 -0
  11. package/client.js +30610 -722
  12. package/compiler-amaro.cjs +5 -0
  13. package/compiler-amaro.cjs.map +6 -0
  14. package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
  15. package/compiler-assets.json +3 -0
  16. package/compiler-core.cjs +554 -0
  17. package/compiler-core.cjs.map +6 -0
  18. package/compiler-platform.cjs +2 -0
  19. package/compiler-platform.cjs.map +6 -0
  20. package/compiler-typescript.cjs +388 -0
  21. package/compiler-typescript.cjs.map +6 -0
  22. package/docs/README.md +6 -0
  23. package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
  24. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +6 -4
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
  26. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
  27. package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
  28. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
  29. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
  30. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +9 -3
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +11 -5
  32. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
  33. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  34. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
  35. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
  36. package/docs/adr/0019-plugin-settings-and-kill-switch.md +8 -6
  37. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  38. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +17 -3
  39. package/docs/adr/0023-global-user-bindings.md +104 -0
  40. package/docs/adr/0024-repl-console-observation.md +33 -0
  41. package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
  42. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
  43. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
  44. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
  45. package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
  46. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
  47. package/docs/architecture.md +82 -22
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +96 -20
  51. package/docs/durability-design.md +61 -24
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/program-data-plane.md +6 -0
  55. package/docs/publishing.md +5 -3
  56. package/docs/runtime-reference.md +79 -25
  57. package/docs/semantic-validation.md +29 -0
  58. package/docs/stateful-binding-updates-proposal.md +755 -0
  59. package/docs/user-bindings.en.md +64 -0
  60. package/docs/user-bindings.md +64 -0
  61. package/docs/value-wire.md +3 -3
  62. package/docs/verification.md +57 -0
  63. package/index.js +374 -92
  64. package/internal/ast-traversal.js +5 -2
  65. package/internal/binding-descriptors.js +1 -0
  66. package/internal/binding-pattern-traversal.js +25 -0
  67. package/internal/binding-pattern.js +26 -29
  68. package/internal/binding-source-analysis.js +391 -0
  69. package/internal/binding-update-policy.js +66 -0
  70. package/internal/bounded-worker-round.js +56 -0
  71. package/internal/callable-definition-environment.js +42 -0
  72. package/internal/callable-source-catalog.js +129 -0
  73. package/internal/callable-source-encoding.js +47 -0
  74. package/internal/callable-source-facts.js +459 -0
  75. package/internal/callable-source-format.js +2 -0
  76. package/internal/callable-source-lowering.js +123 -0
  77. package/internal/cell-analysis-contract.js +25 -0
  78. package/internal/cell-analysis.js +252 -242
  79. package/internal/cell-completion.js +15 -0
  80. package/internal/cell-error.js +7 -0
  81. package/internal/cell-parser.js +66 -0
  82. package/internal/cell-rewriter.js +3 -553
  83. package/internal/commonjs-export-evidence.js +63 -0
  84. package/internal/commonjs-source-evidence.js +72 -0
  85. package/internal/compiler-amaro.cjs +2 -0
  86. package/internal/compiler-compression.js +12 -0
  87. package/internal/compiler-data.js +58 -0
  88. package/internal/compiler-descriptors.js +54 -0
  89. package/internal/compiler-dynamic-cache.js +48 -0
  90. package/internal/compiler-entry.js +35 -0
  91. package/internal/compiler-generated-names.js +17 -0
  92. package/internal/compiler-intrinsics.js +84 -0
  93. package/internal/compiler-module-links.js +34 -0
  94. package/internal/compiler-operations.js +61 -0
  95. package/internal/compiler-platform-factory.js +84 -0
  96. package/internal/compiler-platform.js +136 -0
  97. package/internal/compiler-record-roles.js +53 -0
  98. package/internal/compiler-region-output.js +259 -0
  99. package/internal/compiler-resource-lowering.js +103 -0
  100. package/internal/compiler-scope-facts.js +115 -0
  101. package/internal/compiler-service.js +109 -0
  102. package/internal/compiler-source-cache.js +32 -0
  103. package/internal/compiler-source-pieces.js +93 -0
  104. package/internal/compiler-source-regions.js +132 -0
  105. package/internal/compiler-storage-source.js +54 -0
  106. package/internal/compiler-text.js +15 -0
  107. package/internal/compiler-typescript.cjs +2 -0
  108. package/internal/config-spec.js +91 -25
  109. package/internal/cordis-tools-owner.js +130 -21
  110. package/internal/diagnostic.js +27 -1
  111. package/internal/direct-surface-owner.js +76 -23
  112. package/internal/dynamic-binding-evidence.js +16 -0
  113. package/internal/dynamic-environment-compiler.js +779 -0
  114. package/internal/dynamic-environment-integration.js +87 -0
  115. package/internal/dynamic-environment-runtime.js +362 -0
  116. package/internal/dynamic-native-calls.js +150 -0
  117. package/internal/dynamic-native-runtime.js +248 -0
  118. package/internal/dynamic-scope-analysis.js +90 -0
  119. package/internal/edit-transport-owner.js +28 -3
  120. package/internal/execution-seam-compat.js +193 -0
  121. package/internal/failure-reporting.js +118 -18
  122. package/internal/host-rpc.js +66 -0
  123. package/internal/isolated-worker.js +548 -0
  124. package/internal/kernel-child.js +139 -0
  125. package/internal/kernel-worker.js +911 -116
  126. package/internal/language-semantics.js +14 -0
  127. package/internal/legacy-binding-catalog.js +48 -0
  128. package/internal/legacy-cell-analysis.js +600 -0
  129. package/internal/legacy-cell-parser.js +50 -0
  130. package/internal/legacy-cell-rewriter.js +631 -0
  131. package/internal/legacy-repl-convenience.js +291 -0
  132. package/internal/managed-module-operations.js +170 -0
  133. package/internal/module-compilation.js +321 -0
  134. package/internal/module-runtime-platform.js +19 -0
  135. package/internal/module-transform-contract.js +12 -0
  136. package/internal/native-await-declarations.js +68 -0
  137. package/internal/native-root-compilation.js +54 -0
  138. package/internal/native-root-dynamic.js +67 -0
  139. package/internal/recovery-tips.js +27 -15
  140. package/internal/rejected-cell-editor.js +13 -8
  141. package/internal/repl-convenience.js +2 -201
  142. package/internal/repl-memory-projection.js +81 -12
  143. package/internal/repl-observation-interest.js +70 -0
  144. package/internal/repl-rewrite-contract.js +21 -0
  145. package/internal/repl-scope-normalizer.js +2091 -0
  146. package/internal/repl-value-observer.js +114 -0
  147. package/internal/rpc-contract.js +52 -0
  148. package/internal/runtime-bridge-owner.js +172 -28
  149. package/internal/runtime-config.js +48 -51
  150. package/internal/runtime-contexts.js +82 -10
  151. package/internal/runtime-messages.js +111 -0
  152. package/internal/session-cell-executor.js +455 -91
  153. package/internal/session-journal-recovery.js +684 -0
  154. package/internal/session-journal-schema.js +106 -0
  155. package/internal/session-journal.js +237 -440
  156. package/internal/session-log-view.js +42 -9
  157. package/internal/session-runtime.js +325 -80
  158. package/internal/session-state.js +249 -40
  159. package/internal/source-position-map.js +211 -71
  160. package/internal/stateful-module-compiler.js +154 -0
  161. package/internal/stateful-module-runtime.js +303 -0
  162. package/internal/stateful-root-compiler.js +598 -0
  163. package/internal/stateful-root-runtime.js +340 -0
  164. package/internal/typert-codec-compat.js +28 -0
  165. package/internal/typescript-transform.js +29 -0
  166. package/internal/user-binding-authoring.js +70 -0
  167. package/internal/user-binding-console-worker.js +184 -0
  168. package/internal/user-binding-console.js +148 -0
  169. package/internal/user-binding-draft-projection.js +335 -0
  170. package/internal/user-binding-model-context.js +31 -0
  171. package/internal/user-binding-runner.js +56 -0
  172. package/internal/user-bindings-owner.js +1065 -0
  173. package/internal/user-bindings-store.js +293 -0
  174. package/internal/user-bindings.js +353 -0
  175. package/internal/value-wire-schema.js +28 -0
  176. package/internal/value-wire.js +29 -20
  177. package/internal/worker-client.js +57 -67
  178. package/internal/worker-environment.js +45 -0
  179. package/internal/worker-output-relay.js +29 -0
  180. package/internal/worker-shutdown.js +14 -0
  181. package/package.json +81 -11
  182. package/src/client-activity.js +109 -45
  183. package/src/client-authoring-view.js +580 -0
  184. package/src/client-binding-review.js +230 -0
  185. package/src/client-bindings-data.js +29 -0
  186. package/src/client-catalog.js +127 -0
  187. package/src/client-code-editor.js +84 -0
  188. package/src/client-console.js +112 -0
  189. package/src/client-copy.js +370 -0
  190. package/src/client-feature-gates.js +62 -0
  191. package/src/client-host-compat.js +150 -0
  192. package/src/client-indicator-view.js +267 -0
  193. package/src/client-repl-view.js +292 -0
  194. package/src/client-rpc.js +20 -0
  195. package/src/client-settings-view.js +221 -0
  196. package/src/client-styles.js +106 -0
  197. package/src/client-tool-view.js +108 -0
  198. package/src/client-transport.js +159 -0
  199. package/src/client-workbench.js +521 -0
  200. package/src/client.js +233 -615
@@ -1,10 +1,10 @@
1
- import { stripTypeScriptTypes } from 'node:module'
1
+ import { ModuleRewriteError } from './cell-rewriter.js'
2
+ import { declarationSpan, PreflightError } from './cell-analysis-contract.js'
3
+ export { declarationSpan, PreflightError } from './cell-analysis-contract.js'
2
4
  import { parse } from 'acorn'
3
- import {
4
- STRIP_PREFIX,
5
- STRIP_SUFFIX,
6
- rewriteModuleImportsExports,
7
- } from './cell-rewriter.js'
5
+ import { parse as parseSource } from '@babel/parser'
6
+ import { parseExecutableCell } from './cell-parser.js'
7
+ import { redeclarationCommitTarget } from './repl-rewrite-contract.js'
8
8
  import {
9
9
  bindingNodes,
10
10
  createGeneratedNameAllocator,
@@ -17,9 +17,15 @@ import {
17
17
  renderDurabilityReason,
18
18
  renderDurabilityReasons,
19
19
  } from './module-policy.js'
20
- import { rewriteLooseRedeclarations } from './repl-convenience.js'
21
- import { applySourceEdits, mapSourceSpan } from './source-position-map.js'
20
+ import { applySourceEdits, mapSourcePosition, mapSourceSpan } from './source-position-map.js'
22
21
  import { SKIP_AST_CHILDREN, walkAst } from './ast-traversal.js'
22
+ import { compileStatefulRoot } from './stateful-root-compiler.js'
23
+ import { CELL_PARSER_PLUGINS, bindCompilerIntrinsics, lowerStatefulDecorators, lowerStatefulResources, lowerNativeLanguageSource, normalizeStatefulScopes } from './repl-scope-normalizer.js'
24
+ import { markCallableSources, collectCallableSources } from './callable-source-facts.js'
25
+ import { LegacyPreflightError, prepareLegacyProgram } from './legacy-cell-analysis.js'
26
+ import { adaptLegacyModuleImports } from './managed-module-operations.js'
27
+ import { adaptNativeRootDynamic } from './native-root-compilation.js'
28
+ import { sourceRegionData } from './compiler-region-output.js'
23
29
 
24
30
  /**
25
31
  * Pure AST analysis for PTC cells: binding inventory, durability classification,
@@ -27,35 +33,18 @@ import { SKIP_AST_CHILDREN, walkAst } from './ast-traversal.js'
27
33
  * or session state so its behavior is fully testable in isolation.
28
34
  */
29
35
 
30
- export function declarationSpan(node) {
31
- const start = node.loc?.start
32
- /* c8 ignore next */
33
- const end = node.loc?.end ?? start
34
- /* c8 ignore next */
35
- if (start === undefined) return undefined
36
- return {
37
- line: Math.max(1, start.line - 1),
38
- column: start.column + 1,
39
- /* c8 ignore next */
40
- ...(end === undefined ? {} : {
41
- end: {
42
- line: Math.max(1, end.line - 1),
43
- column: end.column + 1,
44
- },
45
- }),
46
- }
47
- }
48
36
 
49
37
  function addPatternBindings(pattern, names) {
50
38
  walkPattern(pattern, node => names.add(node.name))
51
39
  }
52
40
 
53
- function addPatternDeclarations(pattern, declarations, kind = 'variable', definitionSpan = undefined) {
41
+ function addPatternDeclarations(pattern, declarations, kind = 'variable', definitionSpan = undefined, writable = false) {
54
42
  bindingNodes(pattern).forEach(node => declarations.push({
55
43
  name: node.name,
56
44
  kind,
57
45
  span: declarationSpan(node),
58
46
  definitionSpan,
47
+ writable,
59
48
  }))
60
49
  }
61
50
 
@@ -63,13 +52,35 @@ function topLevelBindings(body) {
63
52
  return new Set(topLevelDeclarations(body).map(declaration => declaration.name))
64
53
  }
65
54
 
66
- function topLevelDeclarations(body) {
55
+ function rootVarDeclarations(body, declarations, seen) {
56
+ walkAst({ type: 'Program', body }, node => {
57
+ if (node.type === 'VariableDeclaration' && node.kind === 'var' && !seen.has(node)) {
58
+ seen.add(node)
59
+ const definitionSpan = declarationSpan(node)
60
+ for (const declaration of node.declarations) {
61
+ addPatternDeclarations(declaration.id, declarations, 'variable', definitionSpan, true)
62
+ }
63
+ }
64
+ if (isFunction(node) || node.type === 'ClassDeclaration' || node.type === 'ClassExpression'
65
+ || node.type === 'StaticBlock') return SKIP_AST_CHILDREN
66
+ })
67
+ }
68
+
69
+ function topLevelDeclarations(body, variableRedeclarations = false) {
67
70
  const declarations = []
71
+ const seen = new Set()
68
72
  for (const statement of body) {
69
73
  if (statement.type === 'VariableDeclaration') {
74
+ seen.add(statement)
70
75
  const definitionSpan = declarationSpan(statement)
71
76
  for (const declaration of statement.declarations) {
72
- addPatternDeclarations(declaration.id, declarations, 'variable', definitionSpan)
77
+ addPatternDeclarations(
78
+ declaration.id,
79
+ declarations,
80
+ 'variable',
81
+ definitionSpan,
82
+ statement.kind !== 'const' || variableRedeclarations,
83
+ )
73
84
  }
74
85
  } else if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration')
75
86
  && statement.id !== null) {
@@ -78,9 +89,12 @@ function topLevelDeclarations(body) {
78
89
  kind: statement.type === 'ClassDeclaration' ? 'class' : 'function',
79
90
  span: declarationSpan(statement.id),
80
91
  definitionSpan: declarationSpan(statement),
92
+ commitDependency: redeclarationCommitTarget(statement.id.name, statement.start),
93
+ writable: true,
81
94
  })
82
95
  }
83
96
  }
97
+ rootVarDeclarations(body, declarations, seen)
84
98
  return declarations
85
99
  }
86
100
 
@@ -97,21 +111,56 @@ function directBlockBindings(body) {
97
111
  return names
98
112
  }
99
113
 
100
- function functionBindings(node) {
114
+ function functionParameterBindings(node) {
101
115
  const names = new Set()
102
116
  if (node.id !== null && node.id !== undefined) names.add(node.id.name)
103
117
  /* c8 ignore next */
104
118
  for (const param of node.params ?? []) addPatternBindings(param, names)
105
- walkAst(node.body, (current) => {
106
- if (current !== node && isFunction(current)) {
107
- if (current.type === 'FunctionDeclaration' && current.id !== null) names.add(current.id.name)
119
+ return names
120
+ }
121
+
122
+ function hasStrictDirective(body) {
123
+ if (body.type !== 'BlockStatement') return false
124
+ for (const statement of body.body) {
125
+ if (statement.directive === undefined) break
126
+ if (statement.directive === 'use strict') return true
127
+ }
128
+ return false
129
+ }
130
+
131
+ function addFunctionVariables(body, names, strict) {
132
+ const addBlockFunction = (node, blocked) => {
133
+ if (!strict && node.type === 'FunctionDeclaration' && !node.async && !node.generator
134
+ && !blocked.has(node.id.name)) names.add(node.id.name)
135
+ }
136
+ walkAst(body, (current, parent, key, blocked) => {
137
+ if (isFunction(current)) {
138
+ addBlockFunction(current, blocked)
108
139
  return SKIP_AST_CHILDREN
109
140
  }
141
+ if (current.type === 'StaticBlock') return SKIP_AST_CHILDREN
110
142
  if (current.type === 'VariableDeclaration' && current.kind === 'var') {
111
143
  for (const declaration of current.declarations) addPatternBindings(declaration.id, names)
112
144
  }
113
- })
114
- return names
145
+ const statements = current.type === 'BlockStatement' ? current.body
146
+ : current.type === 'SwitchCase' ? parent.cases.flatMap(branch => branch.consequent) : undefined
147
+ if (statements !== undefined) {
148
+ // Annex B creates a function-scope var only when intervening lexical
149
+ // declarations permit it. The declaration's own block binding is excluded.
150
+ for (const statement of statements) addBlockFunction(statement, blocked)
151
+ return new Set([...blocked, ...directBlockBindings(statements)])
152
+ }
153
+ if (['ForStatement', 'ForInStatement', 'ForOfStatement'].includes(current.type)) {
154
+ return new Set([...blocked, ...loopBindings(current)])
155
+ }
156
+ if (current.type === 'CatchClause' && current.param !== null && current.param.type !== 'Identifier') {
157
+ // The Annex B catch exception admits var names through a simple parameter,
158
+ // but a destructured catch binding still blocks function-scope promotion.
159
+ const nested = new Set(blocked)
160
+ addPatternBindings(current.param, nested)
161
+ return nested
162
+ }
163
+ }, undefined, new Set())
115
164
  }
116
165
 
117
166
  function loopBindings(node) {
@@ -157,10 +206,10 @@ function staticMemberName(node) {
157
206
  return undefined
158
207
  }
159
208
 
160
- function globalThisMember(node) {
209
+ function globalObjectMember(node) {
161
210
  return node?.type === 'MemberExpression'
162
211
  && node.object?.type === 'Identifier'
163
- && node.object.name === 'globalThis'
212
+ && ['global', 'globalThis'].includes(node.object.name)
164
213
  ? staticMemberName(node)
165
214
  : undefined
166
215
  }
@@ -171,12 +220,6 @@ function isFunction(node) {
171
220
  || node.type === 'ArrowFunctionExpression'
172
221
  }
173
222
 
174
- export class PreflightError extends Error {
175
- constructor(message, node, span = undefined) {
176
- super(message)
177
- this.span = span ?? declarationSpan(node)
178
- }
179
- }
180
223
 
181
224
  function staticModuleClassification(moduleLoads) {
182
225
  const reasons = new Map()
@@ -197,15 +240,46 @@ function staticModuleClassification(moduleLoads) {
197
240
  }
198
241
 
199
242
  /** Conservatively classify a cell before giving it non-journalable capability. */
200
- export function classifyDurability(code, knownBindings = new Set()) {
201
- const tree = parse(`${STRIP_PREFIX}${code}${STRIP_SUFFIX}`, { ecmaVersion: 'latest', sourceType: 'script', locations: true })
202
- const outer = tree.body[0]
203
- /* c8 ignore next */
204
- if (outer?.type !== 'FunctionDeclaration') throw new Error('ptc-plus: failed to parse cell wrapper')
205
- const declared = topLevelBindings(outer.body.body)
243
+ export function classifyDurability(code, knownBindings = new Set(), { sourceType = 'script', internalModules = new Set() } = {}) {
244
+ let body
245
+ let moduleLoads = []
246
+ if (sourceType === 'module') {
247
+ const program = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true })
248
+ knownBindings = new Set(knownBindings)
249
+ moduleLoads = program.body.filter(statement => statement.source != null
250
+ && ['ImportDeclaration', 'ExportNamedDeclaration', 'ExportAllDeclaration'].includes(statement.type)
251
+ && !internalModules.has(statement.source.value)).map(statement => ({
252
+ source: statement.source.value, position: declarationSpan(statement.source),
253
+ }))
254
+ body = { type: 'BlockStatement', body: program.body.flatMap(statement => {
255
+ if (statement.type === 'ImportDeclaration') {
256
+ for (const specifier of statement.specifiers) knownBindings.add(specifier.local.name)
257
+ return []
258
+ }
259
+ if (statement.type === 'ExportAllDeclaration') return []
260
+ if (statement.type === 'ExportNamedDeclaration') return statement.declaration === null ? [] : [statement.declaration]
261
+ if (statement.type === 'ExportDefaultDeclaration') return [statement.declaration]
262
+ return [statement]
263
+ }) }
264
+ } else ({ body } = parseExecutableCell(code))
265
+ const declared = topLevelBindings(body.body)
206
266
  const rootBindings = new Set([...knownBindings, ...declared])
267
+ const rootStrict = sourceType === 'module' || hasStrictDirective(body)
268
+ addFunctionVariables(body, rootBindings, rootStrict)
269
+ // A `var` is instantiated with the cell even on the path that never runs its
270
+ // initializer, so it occupies the shared environment but does not by itself
271
+ // replace a name an earlier cell bound.
272
+ const hoistedVars = new Set()
273
+ walkAst(body, (node) => {
274
+ if (isFunction(node)) return SKIP_AST_CHILDREN
275
+ if (node.type === 'StaticBlock') return SKIP_AST_CHILDREN
276
+ if (node.type === 'VariableDeclaration' && node.kind === 'var') {
277
+ for (const declaration of node.declarations) addPatternBindings(declaration.id, hoistedVars)
278
+ }
279
+ })
207
280
  const reasons = new Map()
208
281
  const addReason = reason => reasons.set(renderDurabilityReason(reason), reason)
282
+ for (const reason of staticModuleClassification(moduleLoads)) addReason(reason)
209
283
  const classifyModule = (source) => {
210
284
  if (source?.type !== 'Literal' || typeof source.value !== 'string') {
211
285
  addReason(DYNAMIC_MODULE_REASON)
@@ -223,12 +297,25 @@ export function classifyDurability(code, knownBindings = new Set()) {
223
297
  }
224
298
  return false
225
299
  }
226
- walkAst(outer.body, (node, parent, parentKey, scopes) => {
300
+ walkAst(body, (node, parent, parentKey, { scopes, strict }) => {
227
301
  let nestedScopes = scopes
228
302
  if (isFunction(node)) {
229
- nestedScopes = [...scopes, functionBindings(node)]
230
- } else if (node.type === 'BlockStatement' && node !== outer.body) {
231
- nestedScopes = [...scopes, directBlockBindings(node.body)]
303
+ strict ||= hasStrictDirective(node.body)
304
+ nestedScopes = [...scopes, functionParameterBindings(node)]
305
+ } else if (node.type === 'BlockStatement' && node !== body) {
306
+ const names = directBlockBindings(node.body)
307
+ if (isFunction(parent)) addFunctionVariables(node, names, strict)
308
+ nestedScopes = [...scopes, names]
309
+ } else if (node.type === 'StaticBlock') {
310
+ const names = directBlockBindings(node.body)
311
+ for (const statement of node.body) addFunctionVariables(statement, names, true)
312
+ strict = true
313
+ nestedScopes = [...scopes, names]
314
+ } else if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
315
+ strict = true
316
+ nestedScopes = [...scopes, new Set(node.id === null ? [] : [node.id.name])]
317
+ } else if (node.type === 'SwitchCase') {
318
+ nestedScopes = [...scopes, directBlockBindings(parent.cases.flatMap(branch => branch.consequent))]
232
319
  } else if (node.type === 'CatchClause') {
233
320
  const names = new Set()
234
321
  addPatternBindings(node.param, names)
@@ -252,233 +339,156 @@ export function classifyDurability(code, knownBindings = new Set()) {
252
339
  && !isBound('process', nestedScopes) && node.name === 'process' && !isStableProcessMember(node, parent)) {
253
340
  addReason(Object.freeze({ kind: 'ambient', name: 'process' }))
254
341
  }
342
+ if (node.type === 'Identifier' && ['global', 'globalThis'].includes(node.name)
343
+ && isReferenceIdentifier(node, parent, parentKey) && !isBound(node.name, nestedScopes)
344
+ && !(parent?.type === 'MemberExpression' && parent.object === node)) {
345
+ addReason(Object.freeze({ kind: 'ambient', name: node.name }))
346
+ }
347
+ if (node.type === 'Identifier' && node.name === 'Math'
348
+ && isReferenceIdentifier(node, parent, parentKey) && !isBound('Math', nestedScopes)
349
+ && !(parent?.type === 'MemberExpression' && parent.object === node && staticMemberName(parent) !== undefined)) {
350
+ addReason(Object.freeze({ kind: 'ambient', name: 'Math' }))
351
+ }
255
352
  if (node.type === 'MemberExpression'
256
353
  && ((node.object?.type === 'Identifier' && node.object.name === 'Math' && !isBound('Math', nestedScopes))
257
- || (globalThisMember(node.object) === 'Math' && !isBound('globalThis', nestedScopes)))
354
+ || (globalObjectMember(node.object) === 'Math' && !isBound(node.object.object.name, nestedScopes)))
258
355
  && staticMemberName(node) === 'random') {
259
356
  addReason(Object.freeze({ kind: 'math-random' }))
260
357
  }
261
358
  if (node.type === 'MemberExpression' && node.object?.type === 'Identifier'
262
- && node.object.name === 'globalThis' && !isBound('globalThis', nestedScopes)) {
359
+ && ['global', 'globalThis'].includes(node.object.name) && !isBound(node.object.name, nestedScopes)) {
263
360
  const member = staticMemberName(node)
264
361
  if (member === undefined) addReason(Object.freeze({ kind: 'computed-global-access' }))
265
362
  else if (member === 'process') addReason(Object.freeze({ kind: 'ambient', name: 'process' }))
266
363
  else if (AMBIENT_GLOBALS.has(member)) addReason(Object.freeze({ kind: 'ambient', name: member }))
364
+ else if (member === 'global' || member === 'globalThis') addReason(Object.freeze({ kind: 'ambient', name: member }))
365
+ else if (member === 'Math' && !(parent?.type === 'MemberExpression' && parent.object === node
366
+ && staticMemberName(parent) !== undefined)) {
367
+ addReason(Object.freeze({ kind: 'ambient', name: 'Math' }))
368
+ }
267
369
  }
268
- return nestedScopes
269
- }, undefined, [rootBindings], outer, 'body')
370
+ return { scopes: nestedScopes, strict }
371
+ }, undefined, { scopes: [rootBindings], strict: rootStrict })
270
372
  return {
271
373
  durability: reasons.size === 0 ? 'durable' : 'volatile',
272
374
  reasons: Object.freeze([...reasons.values()]),
273
375
  declared,
376
+ hoistedVars,
274
377
  }
275
378
  }
276
379
 
277
380
  function rewriteCellReturns(code, sourceMap, unavailableNames) {
278
- const wrapped = STRIP_PREFIX + code + STRIP_SUFFIX
279
- const tree = parse(wrapped, { ecmaVersion: 'latest', sourceType: 'script' })
280
- const outer = tree.body[0]
281
- /* c8 ignore next */
282
- if (outer?.type !== 'FunctionDeclaration') throw new Error('ptc-plus: failed to parse cell wrapper')
283
- const offset = STRIP_PREFIX.length
381
+ const { body } = parseExecutableCell(code)
284
382
  const edits = []
285
- const allocateName = createGeneratedNameAllocator(tree, unavailableNames)
383
+ const allocateName = createGeneratedNameAllocator(body, unavailableNames)
286
384
  const returnSignal = allocateName('return_signal')
287
385
  const signalReference = `this[${JSON.stringify(returnSignal)}]`
386
+ const completion = allocateName('cell_completion')
387
+ const exit = allocateName('cell_exit')
388
+ const ignored = allocateName('completion_effect')
389
+ let asyncCompletion = false
288
390
 
289
- walkAst(outer.body, (node) => {
290
- if (node !== outer.body && isFunction(node)) return SKIP_AST_CHILDREN
391
+ walkAst(body, (node) => {
392
+ if (node !== body && isFunction(node)) return SKIP_AST_CHILDREN
393
+ if (node.type === 'AwaitExpression' || node.await === true
394
+ || node.type === 'VariableDeclaration' && node.kind === 'await using') asyncCompletion = true
291
395
  if (node.type === 'ReturnStatement') {
292
- const start = node.start - offset
293
- const end = node.end - offset
396
+ const start = node.start
397
+ const end = node.end
294
398
  const argument = node.argument === null
295
399
  ? ''
296
- : code.slice(node.argument.start - offset, node.argument.end - offset)
297
- // A ReturnStatement may be the last clause body in a switch case. The
298
- // generated throw must terminate explicitly because the original return
299
- // statement's semicolon is part of the replaced AST range.
300
- edits.push({ start, end, text: `throw new ${signalReference}(${argument});` })
301
- return SKIP_AST_CHILDREN
302
- }
303
- if (node.type === 'CatchClause') {
304
- const bodyStart = node.body.start - offset + 1
305
- const temporary = allocateName('caught')
306
- if (node.param === null) {
307
- edits.push({ start: node.start - offset + 5, end: node.start - offset + 5, text: ` (${temporary})` })
308
- edits.push({ start: bodyStart, end: bodyStart, text: `\nif (${temporary} instanceof ${signalReference}) throw ${temporary};` })
309
- } else if (node.param.type === 'Identifier') {
310
- edits.push({
311
- start: bodyStart,
312
- end: bodyStart,
313
- text: `\nif (${node.param.name} instanceof ${signalReference}) throw ${node.param.name};`,
314
- })
315
- } else {
316
- const pattern = code.slice(node.param.start - offset, node.param.end - offset)
317
- edits.push({ start: node.param.start - offset, end: node.param.end - offset, text: temporary })
318
- edits.push({
319
- start: bodyStart,
320
- end: bodyStart,
321
- text: `\nif (${temporary} instanceof ${signalReference}) throw ${temporary};\nconst ${pattern} = ${temporary};`,
322
- })
323
- }
400
+ : code.slice(node.argument.start, node.argument.end)
401
+ // Break leaves resource and finally scopes without making disposal
402
+ // suppress a private error for an otherwise successful return.
403
+ edits.push({ start, end, text: `{let ${ignored}=(${completion}=new ${signalReference}(${argument}));break ${exit};}` })
324
404
  }
325
405
  })
326
406
 
327
- /* c8 ignore next */
328
- edits.sort((left, right) => right.start - left.start || right.end - left.end)
329
- return { ...applySourceEdits(code, sourceMap, edits), returnSignal }
407
+ const directiveEnd = body.body.findLast(node => node.directive !== undefined)?.end ?? 0
408
+ // Fallthrough cancels a return overridden by finally break/continue. Empty
409
+ // declaration completions preserve the source's ordinary expression value.
410
+ edits.push({ start: directiveEnd, end: directiveEnd,
411
+ text: `\n{let ${completion};${exit}:{\n` })
412
+ edits.push({ start: code.length, end: code.length,
413
+ text: `\n;{let ${ignored}=(${completion}=void 0);}}{let ${ignored}=${completion}&&(()=>{throw ${completion}})();}}\n` })
414
+ let lowered = applySourceEdits(code, sourceMap, edits)
415
+ if (asyncCompletion) {
416
+ // The frame result is compiler-owned transport. Letting Node create it
417
+ // would hide the promise from our observer and re-enter source protocols
418
+ // when the host REPL awaits this realm's completion.
419
+ lowered = applySourceEdits(lowered.code, lowered.sourceMap, [
420
+ { start: 0, end: 0, text: '(async()=>{' },
421
+ { start: lowered.code.length, end: lowered.code.length, text: '\n})()' },
422
+ ])
423
+ }
424
+ return { ...lowered, returnSignal, asyncCompletion }
425
+ }
426
+
427
+ /** Workbench completion uses the same source grammar and compilation boundary. */
428
+ export function prepareConsoleProgram(source, options) {
429
+ const program = parseSource(source, { sourceType: 'script', errorRecovery: true,
430
+ allowImportExportEverywhere: true, allowUndeclaredExports: true,
431
+ allowAwaitOutsideFunction: true, allowReturnOutsideFunction: true, plugins: CELL_PARSER_PLUGINS }).program
432
+ const last = program.body.at(-1)
433
+ const input = last?.type !== 'ExpressionStatement' ? source
434
+ : `${source.slice(0, last.start)}return (${source.slice(last.expression.start, last.expression.end)});${source.slice(last.end)}`
435
+ return prepareProgram(input, options)
330
436
  }
331
437
 
332
- export function prepareProgram(program, knownBindings, looseTopLevelRedeclarations, reservedBindings = new Set(), rewritesEnabled, importBindings = new Map(), importNamespaces = new Set()) {
438
+ export function prepareProgram(program, options = {}) {
333
439
  if (typeof program !== 'string') throw new TypeError('ptc-plus: program must be a string')
334
- if (rewritesEnabled === null || typeof rewritesEnabled !== 'object' || Array.isArray(rewritesEnabled)
335
- || typeof rewritesEnabled.autoRewriteImports !== 'boolean'
336
- || typeof rewritesEnabled.autoStripExports !== 'boolean'
337
- || typeof rewritesEnabled.autoSplitRedeclarations !== 'boolean') {
338
- throw new TypeError('ptc-plus: rewrite policy must define autoRewriteImports, autoStripExports, and autoSplitRedeclarations booleans')
339
- }
340
- const unavailableGeneratedNames = new Set([
341
- ...knownBindings,
342
- ...reservedBindings,
343
- ...importBindings.keys(),
344
- ...importNamespaces,
345
- ])
346
- const moduleRewrite = rewriteModuleImportsExports(
347
- program,
348
- rewritesEnabled,
349
- importBindings,
350
- importNamespaces,
351
- unavailableGeneratedNames,
352
- )
353
- const staticModuleReasons = staticModuleClassification(moduleRewrite.moduleLoads)
354
- const wrapped = STRIP_PREFIX + moduleRewrite.code + STRIP_SUFFIX
355
- let stripped = wrapped
356
- let tree
357
- try {
358
- tree = parse(wrapped, { ecmaVersion: 'latest', sourceType: 'script', locations: true })
359
- } catch (javascriptError) {
440
+ if (options.languageSemantics === undefined || options.languageSemantics === 'legacy-v1') {
360
441
  try {
361
- stripped = stripTypeScriptTypes(wrapped)
362
- tree = parse(stripped, { ecmaVersion: 'latest', sourceType: 'script', locations: true })
363
- } catch {
364
- throw javascriptError
365
- }
366
- }
367
- const code = stripped.slice(STRIP_PREFIX.length, stripped.length - STRIP_SUFFIX.length)
368
- const sourceMap = moduleRewrite.sourceMap
369
- const outer = tree.body[0]
370
- /* c8 ignore next */
371
- const generatedDeclarations = outer?.type === 'FunctionDeclaration' ? topLevelDeclarations(outer.body.body) : []
372
- const collisionFor = (declaration) => {
373
- /* c8 ignore next */
374
- const mapped = declaration.original === true ? declaration.span : declaration.span === undefined
375
- ? { line: 1, column: 1 }
376
- : mapSourceSpan(declaration.span, code, program, sourceMap)
377
- return {
378
- name: declaration.name,
379
- start: { line: mapped.line, column: mapped.column },
380
- /* c8 ignore next */
381
- ...(mapped.end === undefined ? {} : { end: mapped.end }),
382
- }
383
- }
384
- const classifyPrepared = (preparedCode, preparedSourceMap) => {
385
- try {
386
- const classification = classifyDurability(preparedCode, knownBindings)
387
- const reasons = new Map()
388
- for (const reason of [...staticModuleReasons, ...classification.reasons]) {
389
- reasons.set(renderDurabilityReason(reason), reason)
390
- }
391
- return {
392
- ...classification,
393
- durability: reasons.size === 0 ? 'durable' : 'volatile',
394
- reasons: Object.freeze([...reasons.values()]),
395
- reason: renderDurabilityReasons([...reasons.values()]),
396
- }
442
+ // Logical imports have no persistent native namespace slot. Their proved
443
+ // identity enters the native execution environment with the other roots.
444
+ const nativeOptions = { ...options, importBindings: new Map([...options.importBindings ?? []]
445
+ .filter(([name]) => !options.establishedRoots?.has(name) || options.nativeBindings?.has(name))) }
446
+ const prepared = adaptNativeRootDynamic(adaptLegacyModuleImports(prepareLegacyProgram(program, nativeOptions), nativeOptions), options, program)
447
+ return { ...prepared, sourceRegions: sourceRegionData(prepared.sourceRegions), languageSemantics: 'legacy-v1' }
397
448
  } catch (error) {
398
- if (error instanceof PreflightError && error.span !== undefined) {
399
- error.span = mapSourceSpan(error.span, preparedCode, program, preparedSourceMap)
400
- }
449
+ if (error instanceof LegacyPreflightError) throw new PreflightError(error.message, undefined, error.span)
401
450
  throw error
402
451
  }
403
452
  }
404
- const importedNames = new Set(moduleRewrite.imports.keys())
405
- const originalDeclarationNames = new Set(moduleRewrite.exportDeclarations.map(declaration => declaration.name))
406
- const declarations = [
407
- ...moduleRewrite.importDeclarations,
408
- ...moduleRewrite.exportDeclarations,
409
- ...generatedDeclarations
410
- .filter(declaration => !moduleRewrite.generatedNamespaces.has(declaration.name)
411
- && !originalDeclarationNames.has(declaration.name))
412
- .map(declaration => ({
413
- ...declaration,
414
- ...(declaration.definitionSpan === undefined ? {} : {
415
- definitionSpan: mapSourceSpan(declaration.definitionSpan, code, program, sourceMap),
416
- }),
417
- })),
418
- ]
419
- const normalizeClassification = classification => {
420
- const imports = new Map(moduleRewrite.imports)
421
- for (const name of classification.declared) {
422
- if (!moduleRewrite.generatedNamespaces.has(name)) imports.delete(name)
423
- }
424
- return {
425
- ...classification,
426
- imports,
427
- declared: new Set([...classification.declared, ...importedNames]),
453
+ if (options.languageSemantics === 'stateful-v1' || options.languageSemantics === 'protected-v1') {
454
+ let normalized
455
+ let prepared
456
+ let callableSources
457
+ const intrinsicContext = { bindings: new Set() }
458
+ try {
459
+ const marked = markCallableSources(program, undefined, { plugins: CELL_PARSER_PLUGINS, allowImportExportEverywhere: true, allowUndeclaredExports: true },
460
+ { lowerNativeSource: lowerNativeLanguageSource, nativeUsing: options.nativeUsing })
461
+ callableSources = marked.callableSources
462
+ normalized = normalizeStatefulScopes(marked.code, marked.sourceMap, { mode: options.languageSemantics,
463
+ nativeJavaScript: marked.nativeJavaScript, deferDecorators: true, deferResources: true, intrinsicContext })
464
+ prepared = compileStatefulRoot(normalized.code, { ...options, sourceMap: normalized.sourceMap,
465
+ internalBindings: normalized.internalBindings, dynamicBindings: normalized.dynamicBindings,
466
+ privateBindings: normalized.privateBindings, originalSource: program })
467
+ } catch (error) {
468
+ if (error instanceof ModuleRewriteError) throw error
469
+ const position = error.loc === undefined ? undefined : { line: error.loc.line, column: error.loc.column + 1 }
470
+ throw new ModuleRewriteError(error.message, normalized === undefined ? position
471
+ : mapSourcePosition(position, normalized.code, program, normalized.sourceMap))
428
472
  }
429
- }
430
- const reserved = declarations.filter(declaration => (
431
- reservedBindings.has(declaration.name) || moduleRewrite.importNamespaces.has(declaration.name)
432
- ))
433
- if (reserved.length > 0) {
434
- const classification = normalizeClassification(classifyPrepared(code, sourceMap))
435
- return {
436
- code,
437
- ...classification,
438
- declarations,
439
- imports: classification.imports,
440
- importNamespaces: moduleRewrite.importNamespaces,
441
- collisions: reserved.map(collisionFor),
442
- redeclared: [],
443
- rewrites: moduleRewrite.rewrites,
444
- moduleLoads: moduleRewrite.moduleLoads,
473
+ prepared = { ...prepared, deferredHelpers: normalized.deferredHelpers }
474
+ intrinsicContext.expression = `this[${JSON.stringify(prepared.rootRuntimeName)}].intrinsics`
475
+ const classificationSource = bindCompilerIntrinsics(lowerStatefulDecorators(
476
+ { ...prepared, code: prepared.classificationCode }, undefined, intrinsicContext), intrinsicContext).code
477
+ prepared = bindCompilerIntrinsics(lowerStatefulDecorators(prepared, undefined, intrinsicContext), intrinsicContext)
478
+ prepared = lowerStatefulResources(prepared, { intrinsicContext, nativeUsing: options.nativeUsing })
479
+ const parsed = parseExecutableCell(prepared.code, { eraseTypes: true })
480
+ const classification = classifyDurability(parseExecutableCell(classificationSource, { eraseTypes: true }).code,
481
+ new Set([...(options.knownBindings ?? []), ...prepared.declared]))
482
+ const reasons = [...staticModuleClassification(prepared.moduleLoads), ...classification.reasons]
483
+ const lowered = rewriteCellReturns(parsed.code, prepared.sourceMap,
484
+ new Set([...prepared.declared, ...(options.knownBindings ?? [])]))
485
+ prepared.rootBindings.callableSources = collectCallableSources(lowered.code, callableSources)
486
+ return { ...prepared, ...lowered,
487
+ sourceRegions: sourceRegionData(prepared.sourceRegions),
488
+ returnSignal: lowered.returnSignal,
489
+ durability: reasons.length === 0 ? 'durable' : 'volatile', reasons: Object.freeze(reasons),
490
+ reason: renderDurabilityReasons(reasons),
445
491
  }
446
492
  }
447
- const convenience = rewriteLooseRedeclarations({
448
- code,
449
- sourceMap,
450
- body: looseTopLevelRedeclarations && outer?.type === 'FunctionDeclaration' ? outer.body.body : undefined,
451
- offset: STRIP_PREFIX.length,
452
- knownBindings: new Set([...knownBindings].filter(name => !importBindings.has(name))),
453
- declarations,
454
- declarationSpan,
455
- collisionFor,
456
- autoSplitRedeclarations: rewritesEnabled.autoSplitRedeclarations,
457
- })
458
- const { executableCode, executableSourceMap, collisions, redeclared, rewrites } = convenience
459
- // Classification sees the rewritten program so declared bindings reflect the
460
- // split form; it must never see the return rewrite, whose computed
461
- // globalThis access would mark every returning cell volatile.
462
- const classification = normalizeClassification(classifyPrepared(executableCode, executableSourceMap))
463
- let lowered = { code, sourceMap }
464
- if (collisions.length === 0) {
465
- lowered = rewriteCellReturns(
466
- executableCode,
467
- executableSourceMap,
468
- unavailableGeneratedNames,
469
- )
470
- }
471
- return {
472
- code: lowered.code,
473
- sourceMap: lowered.sourceMap,
474
- ...(lowered.returnSignal === undefined ? {} : { returnSignal: lowered.returnSignal }),
475
- ...classification,
476
- declarations,
477
- imports: classification.imports,
478
- importNamespaces: moduleRewrite.importNamespaces,
479
- collisions,
480
- redeclared,
481
- rewrites: [...moduleRewrite.rewrites, ...rewrites],
482
- moduleLoads: moduleRewrite.moduleLoads,
483
- }
493
+ throw new TypeError('ptc-plus: unsupported language semantics')
484
494
  }
@@ -0,0 +1,15 @@
1
+ import { captureCompilerIntrinsics } from './compiler-intrinsics.js'
2
+
3
+ /** Execution owns its frame promise; the returned source value still uses
4
+ * Await in the source realm. Neither consumer performs host-realm assimilation
5
+ * of an unexamined source promise. */
6
+ export function createCellCompletionObserver(realmFunction) {
7
+ const intrinsics = captureCompilerIntrinsics(realmFunction)
8
+ return {
9
+ observe: intrinsics.observeOwnedPromise,
10
+ settle(value, fulfilled, rejected) {
11
+ const completion = intrinsics.awaitValue(value, value => ({ value }))
12
+ intrinsics.observeOwnedPromise(completion, result => fulfilled(result.value), rejected)
13
+ },
14
+ }
15
+ }