semantifold 0.4.0 → 0.6.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.
Files changed (203) hide show
  1. package/README.md +13 -7
  2. package/build/index.d.ts +12 -8
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +10 -7
  5. package/build/src/backends/android.d.ts +49 -0
  6. package/build/src/backends/android.d.ts.map +1 -0
  7. package/build/src/backends/android.js +1278 -0
  8. package/build/src/backends/identifiers.d.ts +6 -0
  9. package/build/src/backends/identifiers.d.ts.map +1 -1
  10. package/build/src/backends/identifiers.js +30 -1
  11. package/build/src/backends/ios.js +2 -2
  12. package/build/src/backends/kotlin.d.ts +10 -0
  13. package/build/src/backends/kotlin.d.ts.map +1 -1
  14. package/build/src/backends/kotlin.js +11 -9
  15. package/build/src/backends/native-validation.d.ts +2 -2
  16. package/build/src/backends/native-validation.d.ts.map +1 -1
  17. package/build/src/backends/native-validation.js +2 -2
  18. package/build/src/backends/program.d.ts +6 -4
  19. package/build/src/backends/program.d.ts.map +1 -1
  20. package/build/src/backends/program.js +5 -4
  21. package/build/src/backends/scalars.d.ts.map +1 -1
  22. package/build/src/backends/scalars.js +2 -1
  23. package/build/src/backends/shared.d.ts.map +1 -1
  24. package/build/src/backends/shared.js +20 -4
  25. package/build/src/backends/writer.d.ts +21 -1
  26. package/build/src/backends/writer.d.ts.map +1 -1
  27. package/build/src/backends/writer.js +25 -2
  28. package/build/src/backends/zig-runtime.d.ts +5 -0
  29. package/build/src/backends/zig-runtime.d.ts.map +1 -0
  30. package/build/src/backends/zig-runtime.js +92 -0
  31. package/build/src/backends/zig-validation.d.ts +9 -0
  32. package/build/src/backends/zig-validation.d.ts.map +1 -0
  33. package/build/src/backends/zig-validation.js +73 -0
  34. package/build/src/backends/zig.d.ts +42 -0
  35. package/build/src/backends/zig.d.ts.map +1 -0
  36. package/build/src/backends/zig.js +584 -0
  37. package/build/src/frontends/scalars.d.ts.map +1 -1
  38. package/build/src/frontends/scalars.js +2 -1
  39. package/build/src/frontends/zig-parser.d.ts +31 -0
  40. package/build/src/frontends/zig-parser.d.ts.map +1 -0
  41. package/build/src/frontends/zig-parser.js +17 -0
  42. package/build/src/frontends/zig.d.ts +14 -0
  43. package/build/src/frontends/zig.d.ts.map +1 -0
  44. package/build/src/frontends/zig.js +970 -0
  45. package/build/src/language-registry.d.ts.map +1 -1
  46. package/build/src/language-registry.js +40 -1
  47. package/build/src/mapping.js +2 -2
  48. package/build/src/semantic/provenance.js +3 -3
  49. package/build/src/semantic/types.d.ts +184 -4
  50. package/build/src/semantic/types.d.ts.map +1 -1
  51. package/build/src/semantic/types.js +45 -3
  52. package/build/src/semantic/validate.js +2 -2
  53. package/build/src/toolchains.d.ts +6 -0
  54. package/build/src/toolchains.d.ts.map +1 -1
  55. package/build/src/toolchains.js +2 -1
  56. package/build/src/zig-bindings.d.ts +10 -0
  57. package/build/src/zig-bindings.d.ts.map +1 -0
  58. package/build/src/zig-bindings.js +54 -0
  59. package/docs/android.md +79 -0
  60. package/docs/architecture.md +13 -5
  61. package/docs/goals.md +1 -1
  62. package/docs/ios.md +1 -1
  63. package/docs/language-support.md +15 -4
  64. package/docs/parser-qualification.md +31 -1
  65. package/docs/source-maps.md +7 -3
  66. package/docs/specification.md +5 -0
  67. package/docs/testing.md +40 -3
  68. package/docs/zig.md +33 -0
  69. package/index.js +9 -6
  70. package/node_modules/semantifold-tree-sitter-zig-internal/LICENSE +15 -0
  71. package/node_modules/semantifold-tree-sitter-zig-internal/README.md +5 -0
  72. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/LICENSE +21 -0
  73. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/README.md +21 -0
  74. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/binding.gyp +29 -0
  75. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/bindings/node/binding.cc +20 -0
  76. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/bindings/node/binding_test.js +9 -0
  77. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/bindings/node/index.d.ts +28 -0
  78. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/bindings/node/index.js +11 -0
  79. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/grammar.js +900 -0
  80. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/package.json +57 -0
  81. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/darwin-arm64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
  82. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/darwin-x64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
  83. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/linux-arm64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
  84. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/linux-x64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
  85. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/win32-arm64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
  86. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/win32-x64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
  87. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/queries/folds.scm +23 -0
  88. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/queries/highlights.scm +291 -0
  89. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/queries/indents.scm +22 -0
  90. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/queries/injections.scm +10 -0
  91. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/grammar.json +6570 -0
  92. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/node-types.json +3178 -0
  93. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/parser.c +169829 -0
  94. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/tree_sitter/alloc.h +54 -0
  95. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/tree_sitter/array.h +291 -0
  96. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/tree_sitter/parser.h +266 -0
  97. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/tree-sitter-zig.wasm +0 -0
  98. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/tree-sitter.json +38 -0
  99. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/LICENSE +21 -0
  100. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/README.md +128 -0
  101. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/binding.gyp +80 -0
  102. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/index.js +916 -0
  103. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/package.json +76 -0
  104. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/darwin-arm64/tree-sitter.node +0 -0
  105. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/darwin-x64/tree-sitter.node +0 -0
  106. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/linux-arm64/tree-sitter.node +0 -0
  107. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/linux-x64/tree-sitter.node +0 -0
  108. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/win32-arm64/tree-sitter.node +0 -0
  109. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/win32-x64/tree-sitter.node +0 -0
  110. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/addon_data.h +47 -0
  111. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/binding.cc +35 -0
  112. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/conversions.cc +140 -0
  113. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/conversions.h +22 -0
  114. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/language.cc +106 -0
  115. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/language.h +17 -0
  116. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/logger.cc +70 -0
  117. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/logger.h +19 -0
  118. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/lookaheaditerator.cc +122 -0
  119. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/lookaheaditerator.h +33 -0
  120. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/node.cc +1088 -0
  121. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/node.h +30 -0
  122. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/parser.cc +306 -0
  123. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/parser.h +35 -0
  124. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/query.cc +397 -0
  125. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/query.h +40 -0
  126. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/tree.cc +316 -0
  127. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/tree.h +45 -0
  128. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/tree_cursor.cc +213 -0
  129. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/tree_cursor.h +52 -0
  130. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/tree-sitter.d.ts +1042 -0
  131. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/include/tree_sitter/api.h +1318 -0
  132. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/alloc.c +48 -0
  133. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/alloc.h +41 -0
  134. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/array.h +291 -0
  135. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/atomic.h +68 -0
  136. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/clock.h +146 -0
  137. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/error_costs.h +11 -0
  138. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/get_changed_ranges.c +501 -0
  139. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/get_changed_ranges.h +36 -0
  140. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/host.h +21 -0
  141. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/language.c +223 -0
  142. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/language.h +297 -0
  143. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/length.h +52 -0
  144. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lexer.c +435 -0
  145. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lexer.h +49 -0
  146. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lib.c +12 -0
  147. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/node.c +920 -0
  148. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/parser.c +2165 -0
  149. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/parser.h +266 -0
  150. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/point.h +62 -0
  151. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/portable/endian.h +206 -0
  152. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/query.c +4187 -0
  153. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/reduce_action.h +34 -0
  154. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/reusable_node.h +95 -0
  155. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/stack.c +899 -0
  156. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/stack.h +130 -0
  157. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/subtree.c +1066 -0
  158. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/subtree.h +399 -0
  159. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree.c +170 -0
  160. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree.h +31 -0
  161. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree_cursor.c +712 -0
  162. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree_cursor.h +48 -0
  163. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/ts_assert.h +11 -0
  164. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/ICU_SHA +1 -0
  165. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/LICENSE +414 -0
  166. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/README.md +29 -0
  167. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/ptypes.h +1 -0
  168. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/umachine.h +448 -0
  169. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/urename.h +1 -0
  170. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf.h +1 -0
  171. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf16.h +733 -0
  172. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf8.h +881 -0
  173. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode.h +83 -0
  174. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/stdlib-symbols.txt +24 -0
  175. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/stdlib.c +109 -0
  176. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/wasm-stdlib.h +1302 -0
  177. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm_store.c +1859 -0
  178. package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm_store.h +31 -0
  179. package/node_modules/semantifold-tree-sitter-zig-internal/package.json +21 -0
  180. package/node_modules/semantifold-tree-sitter-zig-internal/src/zig.js +134 -0
  181. package/package.json +11 -6
  182. package/src/backends/android.js +1374 -0
  183. package/src/backends/identifiers.js +30 -0
  184. package/src/backends/ios.js +1 -1
  185. package/src/backends/kotlin.js +11 -8
  186. package/src/backends/native-validation.js +1 -1
  187. package/src/backends/program.js +4 -3
  188. package/src/backends/scalars.js +1 -0
  189. package/src/backends/shared.js +22 -3
  190. package/src/backends/writer.js +27 -2
  191. package/src/backends/zig-runtime.js +93 -0
  192. package/src/backends/zig-validation.js +68 -0
  193. package/src/backends/zig.js +579 -0
  194. package/src/frontends/scalars.js +1 -0
  195. package/src/frontends/zig-parser.js +20 -0
  196. package/src/frontends/zig.js +994 -0
  197. package/src/language-registry.js +40 -0
  198. package/src/mapping.js +1 -1
  199. package/src/semantic/provenance.js +2 -2
  200. package/src/semantic/types.js +47 -2
  201. package/src/semantic/validate.js +1 -1
  202. package/src/toolchains.js +1 -0
  203. package/src/zig-bindings.js +46 -0
@@ -0,0 +1,1374 @@
1
+ // @ts-check
2
+
3
+ import {createHash} from "node:crypto"
4
+ import {isDenseArray} from "../array.js"
5
+ import {findPortableArtifactPathConflict, isSafeArtifactPath} from "../artifact-path.js"
6
+ import {createByteMapping} from "../binary-mapping.js"
7
+ import {SemantifoldDiagnostic, unsupportedCapability} from "../diagnostic.js"
8
+ import {finalizeMapping, toSourceMapV3} from "../mapping.js"
9
+ import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
10
+ import {preflightSemanticProgram} from "./program.js"
11
+ import {emitScalarType} from "./scalars.js"
12
+ import {kotlinRuntime} from "./kotlin-runtime.js"
13
+ import {emitKotlinBlock} from "./kotlin.js"
14
+ import {SourceWriter} from "./writer.js"
15
+
16
+ const projectRoot = "generated/android-app"
17
+ const generatorVersion = "0.4.0"
18
+ /** @type {Readonly<Set<import("../semantic/types.js").SemanticLanguage>>} */
19
+ const androidSourceLanguages = new Set([
20
+ "php", "ruby", "javascript", "typescript", "java", "kotlin", "python", "csharp", "go", "c", "cpp", "rust", "swift", "dart", "zig"
21
+ ])
22
+ const requestFields = new Set(["assets", "configuration", "language", "module", "program", "resources", "role"])
23
+ const configurationFields = new Set([
24
+ "activityClassName", "applicationId", "buildToolsVersion", "compileSdk", "displayName", "minimumSdk", "namespace",
25
+ "orientation", "packageName", "permissions", "productName", "targetSdk", "theme", "versionCode", "versionName"
26
+ ])
27
+ const resourceFields = new Set(["content", "mediaType", "path", "sha256"])
28
+ const kotlinKeywords = new Set([
29
+ "as", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "interface", "is", "null",
30
+ "object", "package", "return", "super", "this", "throw", "true", "try", "typealias", "typeof", "val", "var", "when", "while"
31
+ ])
32
+ const androidScaffoldSymbols = new Set([
33
+ "Activity", "Base64", "Bundle", "IllegalStateException", "Instrumentation", "Intent", "R", "SemantifoldEntryTest",
34
+ "SemantifoldUiInstrumentation", "StandardCharsets", "String", "SuppressLint", "Test", "TextView", "Throwable", "View"
35
+ ])
36
+ const mediaTypePattern = /^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+(?:;[\u0020-\u007e]+)?$/u
37
+ const semanticVersionPattern = /^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[A-Za-z-][A-Za-z0-9-]*)(?:\.(?:0|[1-9][0-9]*|[A-Za-z-][A-Za-z0-9-]*))*)?(?:\+[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*)?$/u
38
+ const toolchain = Object.freeze({
39
+ androidGradlePlugin: "8.11.1",
40
+ archives: {
41
+ buildTools: {
42
+ revision: "35.0.0",
43
+ sha256: "bd3a4966912eb8b30ed0d00b0cda6b6543b949d5ffe00bea54c04c81e1561d88",
44
+ url: "https://dl.google.com/android/repository/build-tools_r35_linux.zip"
45
+ },
46
+ commandLineTools: {
47
+ revision: "11076708",
48
+ sha256: "2d2d50857e4eb553af5a6dc3ad507a17adf43d115264b1afc116f95c92e5e258",
49
+ url: "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip"
50
+ },
51
+ emulator: {
52
+ buildId: "13610412",
53
+ revision: "35.6.11",
54
+ sha256: "2fe2b56fe93ce75e1d478a40162131381d911c355efeaedb54dd1e0d0897a5cf",
55
+ url: "https://edgedl.me.gvt1.com/edgedl/android/repository/emulator-linux_x64-13610412.zip"
56
+ },
57
+ platform: {
58
+ revision: "35-r2",
59
+ sha256: "0988cacad01b38a18a47bac14a0695f246bc76c1b06c0eeb8eb0dc825ab0c8e0",
60
+ url: "https://dl.google.com/android/repository/platform-35_r02.zip"
61
+ },
62
+ platformTools: {
63
+ revision: "37.0.1",
64
+ sha256: "d230f13842f60f782a8645f9c813f8f845bf36089ea7289f28c48f17979313f1",
65
+ url: "https://dl.google.com/android/repository/platform-tools_r37.0.1-linux.zip"
66
+ },
67
+ systemImage: {
68
+ revision: "35-google_apis-x86_64-r9",
69
+ sha256: "c67b9ba0ff5bc0eb6d046871bfa228af14d4d47b02f0cdae94f048e511b7566e",
70
+ url: "https://dl.google.com/android/repository/sys-img/google_apis/x86_64-35_r09.zip"
71
+ }
72
+ },
73
+ buildTools: "35.0.0",
74
+ commandLineTools: "11076708",
75
+ compileSdk: 35,
76
+ emulator: "35.6.11",
77
+ gradle: "8.13",
78
+ java: "21.0.8",
79
+ kotlin: "2.2.10",
80
+ kotlinCompilerSha256: "302d1d8e671e5c3207e6ed62ff11fb555462a628e22a1158254dcaaf7e7394bc",
81
+ systemImage: "system-images;android-35;google_apis;x86_64",
82
+ targetSdk: 35
83
+ })
84
+ const excludedPathPatterns = Object.freeze([".gradle/**", "**/build/**", "local.properties"])
85
+ const androidKotlinRuntime = `@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
86
+
87
+ package __PACKAGE__.semantic
88
+
89
+ ${kotlinRuntime.replaceAll("private ", "internal ")}
90
+ internal class SemantifoldOutputSink {
91
+ private val values: java.util.ArrayList<String> = java.util.ArrayList<String>()
92
+
93
+ fun reset() {
94
+ values.clear()
95
+ }
96
+
97
+ fun write(value: Long) {
98
+ values.add(value.toString())
99
+ }
100
+
101
+ fun write(value: Boolean) {
102
+ values.add(if (value) "true" else "false")
103
+ }
104
+
105
+ fun write(value: String) {
106
+ values.add(value)
107
+ }
108
+
109
+ fun snapshot(): java.util.ArrayList<String> = java.util.ArrayList<String>(values)
110
+ }
111
+ `
112
+
113
+ /** @typedef {{content: string | Uint8Array, contentKind: "binary" | "text", fullPath: string, mediaType: string, path: string, sha256: string}} AndroidInputFile */
114
+ /** @typedef {{end: number, field: string, path: string, start: number}} ConfigurationSpan */
115
+
116
+ /**
117
+ * Preflights a complete Android application request before allocating a source writer.
118
+ * @param {Record<string, unknown>} input - Candidate application request.
119
+ * @returns {{assets: AndroidInputFile[], configuration: import("../semantic/types.js").AndroidApplicationConfiguration, modulePaths: Map<string, string>, modules: import("../semantic/types.js").SemanticModule[], packagePath: string, program: import("../semantic/types.js").SemanticProgram, resources: AndroidInputFile[], sources: {content: string, filename: string, language?: import("../semantic/types.js").SemanticLanguage}[]}} Prepared application.
120
+ */
121
+ export function preflightAndroidApplication(input) {
122
+ if (!isPlainObject(input) || Object.keys(input).some(key => !requestFields.has(key)) ||
123
+ Boolean(input.module) == Boolean(input.program)) {
124
+ invalidInput("Android application generation requires a closed request with exactly one semantic module or program.")
125
+ }
126
+ if (input.language !== undefined && input.language != "android" || input.role !== undefined && input.role != "application") {
127
+ invalidInput("Android application generation accepts only the Android application role.")
128
+ }
129
+ const configuration = normalizeConfiguration(input.configuration)
130
+ const program = input.program ?? normalizeSingleModule(
131
+ /** @type {import("../semantic/types.js").SemanticModule | undefined} */ (input.module))
132
+ const prepared = preflightSemanticProgram({
133
+ backendLanguage: "kotlin",
134
+ diagnosticLanguage: "android",
135
+ program: /** @type {import("../semantic/types.js").SemanticProgram} */ (program),
136
+ sourceLanguages: androidSourceLanguages
137
+ })
138
+ validateActivityClassName(configuration.activityClassName, prepared.program.entryModule)
139
+ const packagePath = configuration.packageName.replaceAll(".", "/")
140
+ const semanticRoot = `${projectRoot}/app/src/main/kotlin/${packagePath}/semantic`
141
+ const modulePaths = new Map(prepared.modules.map(module => [
142
+ /** @type {string} */ (Reflect.get(module, "id")),
143
+ `${semanticRoot}/${moduleName(/** @type {string} */ (Reflect.get(module, "id")))}.kt`
144
+ ]))
145
+ const resources = normalizeInputFiles(input.resources, "resource")
146
+ const assets = normalizeInputFiles(input.assets, "asset")
147
+ const fixedPaths = applicationPaths(configuration, modulePaths)
148
+ const completePaths = [
149
+ ...fixedPaths,
150
+ ...resources.map(({fullPath}) => fullPath),
151
+ ...assets.map(({fullPath}) => fullPath)
152
+ ]
153
+ const conflict = findPortableArtifactPathConflict(completePaths)
154
+
155
+ if (conflict) {
156
+ invalidPath(`Application artifact path '${conflict.path}' has a ${conflict.kind} conflict${conflict.other ? ` with '${conflict.other}'` : ""}.`)
157
+ }
158
+
159
+ return {...prepared, assets, configuration, modulePaths, packagePath, resources}
160
+ }
161
+
162
+ /**
163
+ * Generates one complete deterministic Android/Kotlin project.
164
+ * @param {Record<string, unknown>} input - Application generation request.
165
+ * @returns {{artifacts: import("../semantic/types.js").GeneratedSetArtifact[], metadata: {applicationManifest: string, platformQualification: string}, target: "android"}} Candidate artifact set.
166
+ */
167
+ export function generateAndroidApplication(input) {
168
+ const prepared = preflightAndroidApplication(input)
169
+ const configurationSpans = /** @type {ConfigurationSpan[]} */ ([])
170
+ const configuration = prepared.configuration
171
+ const packagePath = prepared.packagePath
172
+ const semanticArtifacts = renderSemanticArtifacts(prepared)
173
+ const artifacts = /** @type {import("../semantic/types.js").GeneratedSetArtifact[]} */ ([
174
+ configuredText(`${projectRoot}/app/build.gradle.kts`, "manifest", renderAppBuild(configuration, configurationSpans),
175
+ "Pinned dependency-minimal Android application build."),
176
+ configuredText(`${projectRoot}/app/src/androidTest/AndroidManifest.xml`, "manifest",
177
+ renderAndroidTestManifest(configuration, configurationSpans), "Platform-only Android instrumentation declaration."),
178
+ configuredText(`${projectRoot}/app/src/androidTest/kotlin/${packagePath}/SemantifoldUiInstrumentation.kt`, "support",
179
+ renderInstrumentation(configuration, configurationSpans), "Platform-only accessibility and lifecycle assertion."),
180
+ configuredText(`${projectRoot}/app/src/main/AndroidManifest.xml`, "manifest",
181
+ renderAndroidManifest(configuration, configurationSpans), "Closed allowlisted Android application manifest."),
182
+ configuredText(`${projectRoot}/app/src/main/kotlin/${packagePath}/${configuration.activityClassName}.kt`, "entry",
183
+ renderActivity(configuration, prepared.program.entryModule, configurationSpans),
184
+ "Synthetic Android activity lifecycle and native output view binding."),
185
+ ...semanticArtifacts,
186
+ syntheticText(`${projectRoot}/app/src/main/res/layout/activity_main.xml`, "resource", renderLayout(),
187
+ "Synthetic one-view Android layout and accessibility identity."),
188
+ configuredText(`${projectRoot}/app/src/main/res/values/strings.xml`, "resource",
189
+ renderStrings(configuration, configurationSpans), "Escaped application labels derived from configuration."),
190
+ syntheticText(`${projectRoot}/app/src/main/res/values/themes.xml`, "resource", renderTheme(),
191
+ "Closed platform-native application theme."),
192
+ syntheticText(`${projectRoot}/app/src/main/res/xml/backup_rules.xml`, "resource", renderBackupRules(),
193
+ "Closed API 23-30 full-backup exclusions."),
194
+ syntheticText(`${projectRoot}/app/src/main/res/xml/data_extraction_rules.xml`, "resource", renderDataExtractionRules(),
195
+ "Closed Android 12+ cloud-backup and device-transfer exclusions."),
196
+ configuredText(`${projectRoot}/app/src/test/kotlin/${packagePath}/SemantifoldEntryTest.kt`, "support",
197
+ renderUnitTest(configuration, prepared.program.entryModule, configurationSpans),
198
+ "JUnit test-scope-only semantic determinism assertion."),
199
+ syntheticText(`${projectRoot}/build.gradle.kts`, "manifest", renderRootBuild(),
200
+ "Pinned Android and Kotlin build plugins."),
201
+ syntheticText(`${projectRoot}/gradle.properties`, "support", renderGradleProperties(),
202
+ "Pinned hermetic Gradle process properties."),
203
+ configuredText(`${projectRoot}/settings.gradle.kts`, "manifest", renderSettings(configuration, configurationSpans),
204
+ "Closed official Gradle plugin and dependency repositories."),
205
+ ...prepared.resources.map(inputFileArtifact),
206
+ ...prepared.assets.map(inputFileArtifact)
207
+ ])
208
+
209
+ artifacts.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0)
210
+ const manifestPath = `${projectRoot}/semantifold-project.json`
211
+
212
+ artifacts.push(syntheticText(manifestPath, "manifest",
213
+ renderProjectManifest(prepared, artifacts, manifestPath, configurationSpans),
214
+ "Versioned Android project ownership, toolchain, and provenance manifest."))
215
+
216
+ return {
217
+ artifacts,
218
+ metadata: {applicationManifest: manifestPath, platformQualification: "tensorbuzz-kvm"},
219
+ target: "android"
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Renders the mapped semantic Kotlin artifacts for every program module.
225
+ * @param {ReturnType<typeof preflightAndroidApplication>} prepared - Prepared application.
226
+ * @returns {import("../semantic/types.js").GeneratedSetArtifact[]} Semantic Kotlin artifacts.
227
+ */
228
+ function renderSemanticArtifacts(prepared) {
229
+ const semanticRoot = `${projectRoot}/app/src/main/kotlin/${prepared.packagePath}/semantic`
230
+ /** @type {import("../semantic/types.js").GeneratedSetArtifact[]} */
231
+ const artifacts = [syntheticText(`${semanticRoot}/SemantifoldRuntime.kt`, "support",
232
+ androidKotlinRuntime.replaceAll("__PACKAGE__", prepared.configuration.packageName),
233
+ "Shared Android application output capture and checked Kotlin scalar support.")]
234
+ const declarations = new Map()
235
+
236
+ for (const module of prepared.modules) {
237
+ for (const declaration of module.functions) declarations.set(declaration.id, {
238
+ declaration,
239
+ moduleId: /** @type {string} */ (Reflect.get(module, "id"))
240
+ })
241
+ }
242
+ for (const module of prepared.modules) {
243
+ const moduleId = /** @type {string} */ (Reflect.get(module, "id"))
244
+ const filename = /** @type {string} */ (prepared.modulePaths.get(moduleId))
245
+ const sinkName = outputSinkName(module)
246
+ const writer = new SourceWriter({
247
+ callArgumentSuffix: sinkName,
248
+ callNameResolver: expression => {
249
+ const resolved = declarations.get(expression.resolution?.declarationId)
250
+
251
+ if (!resolved) throw new TypeError(`Unknown validated Android function '${expression.resolution?.declarationId}'.`)
252
+
253
+ return `SemantifoldModule${moduleName(resolved.moduleId)}.${resolved.declaration.name}`
254
+ },
255
+ filename,
256
+ language: "kotlin",
257
+ methodStringEquality: true,
258
+ module,
259
+ program: prepared.program,
260
+ programPaths: prepared.modulePaths,
261
+ sources: prepared.sources
262
+ })
263
+
264
+ writer.synthetic(`@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")\n\npackage ${prepared.configuration.packageName}.semantic\n\n`,
265
+ "Android semantic package namespace", [module], [""])
266
+ emitSemanticModule(writer, module, moduleId, moduleId == prepared.program.entryModule, sinkName)
267
+ const mapping = finalizeMapping(writer.finish())
268
+
269
+ artifacts.push({
270
+ content: mapping.generated.content,
271
+ contentKind: "text",
272
+ mediaType: "text/x-kotlin",
273
+ ownership: "generated",
274
+ path: filename,
275
+ provenance: {kind: "text", mapping, sourceMap: toSourceMapV3(mapping), sourceMapFilename: `${filename}.map`},
276
+ role: "source"
277
+ })
278
+ }
279
+
280
+ return artifacts
281
+ }
282
+
283
+ /**
284
+ * Emits one namespaced semantic module and its entry bridge when applicable.
285
+ * @param {SourceWriter} writer - Mapped Kotlin writer.
286
+ * @param {import("../semantic/types.js").SemanticModule} module - Prepared module.
287
+ * @param {string} moduleId - Stable module identity.
288
+ * @param {boolean} entry - Whether this module owns the entry block.
289
+ * @param {string} sinkName - Collision-safe output sink binding.
290
+ */
291
+ function emitSemanticModule(writer, module, moduleId, entry, sinkName) {
292
+ writer.synthetic(`internal object SemantifoldModule${moduleName(moduleId)} {\n`,
293
+ "Android semantic module namespace", [module], [""])
294
+ module.functions.forEach((declaration, functionIndex) => {
295
+ const path = `/functions/${functionIndex}`
296
+
297
+ writer.synthetic(" ", "Android namespaced function indentation", [declaration], [path])
298
+ writer.mapped("fun", {mappingKind: "anchor", node: declaration, path})
299
+ writer.synthetic(" ", "declaration spacing", [declaration], [path])
300
+ writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, path, role: "name"})
301
+ writer.synthetic("(", "function parameter scaffold", [declaration], [path])
302
+ declaration.parameters.forEach((parameter, parameterIndex) => {
303
+ const parameterPath = `${path}/parameters/${parameterIndex}`
304
+
305
+ if (parameterIndex) writer.synthetic(", ", "parameter separator", [parameter], [parameterPath])
306
+ writer.mapped(parameter.name, {mappingKind: "exact", node: parameter, path: parameterPath, role: "name"})
307
+ writer.synthetic(": ", "parameter annotation scaffold", [parameter], [parameterPath])
308
+ writer.mapped(emitScalarType("kotlin", parameter.type), {mappingKind: "exact", node: parameter.type,
309
+ path: `${parameterPath}/type`, role: "type"})
310
+ })
311
+ if (declaration.parameters.length) writer.synthetic(", ", "output sink parameter separator", [declaration], [path])
312
+ writer.synthetic(`${sinkName}: SemantifoldOutputSink): `, "shared Android output sink parameter", [declaration], [path])
313
+ writer.mapped(emitScalarType("kotlin", declaration.returnType), {mappingKind: "exact", node: declaration.returnType,
314
+ path: `${path}/returnType`, role: "type"})
315
+ writer.synthetic(" {\n", "Kotlin function body scaffold", [declaration], [path])
316
+ emitKotlinBlock(writer, declaration.body, " ", `${path}/body`, `${sinkName}.write`)
317
+ writer.synthetic(" }\n", "Kotlin function body scaffold", [declaration], [path])
318
+ })
319
+ if (entry) {
320
+ writer.synthetic(" fun semantifoldEntry(): java.util.ArrayList<String> {\n", "Android semantic entry scaffold", [module.entryPoint], ["/entryPoint"])
321
+ writer.synthetic(` val ${sinkName} = SemantifoldOutputSink()\n`, "Android output sink allocation",
322
+ [module.entryPoint], ["/entryPoint"])
323
+ writer.synthetic(` ${sinkName}.reset()\n`, "Android lifecycle-safe output reset", [module.entryPoint], ["/entryPoint"])
324
+ emitKotlinBlock(writer, module.entryPoint.body, " ", "/entryPoint/body", `${sinkName}.write`)
325
+ writer.synthetic(` return ${sinkName}.snapshot()\n`, "Android semantic output return",
326
+ [module.entryPoint], ["/entryPoint"])
327
+ writer.synthetic(" }\n", "Android semantic entry scaffold", [module.entryPoint], ["/entryPoint"])
328
+ }
329
+ writer.synthetic("}\n", "Android semantic module namespace", [module], [""])
330
+ }
331
+
332
+ /**
333
+ * Lists every fixed generated path before any writer is allocated.
334
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized application configuration.
335
+ * @param {Map<string, string>} modulePaths - Semantic module output paths.
336
+ * @returns {string[]} Fixed owned artifact paths.
337
+ */
338
+ function applicationPaths(configuration, modulePaths) {
339
+ const packagePath = configuration.packageName.replaceAll(".", "/")
340
+
341
+ return [
342
+ `${projectRoot}/app/build.gradle.kts`,
343
+ `${projectRoot}/app/src/androidTest/AndroidManifest.xml`,
344
+ `${projectRoot}/app/src/androidTest/kotlin/${packagePath}/SemantifoldUiInstrumentation.kt`,
345
+ `${projectRoot}/app/src/main/AndroidManifest.xml`,
346
+ `${projectRoot}/app/src/main/kotlin/${packagePath}/${configuration.activityClassName}.kt`,
347
+ ...modulePaths.values(),
348
+ `${projectRoot}/app/src/main/kotlin/${packagePath}/semantic/SemantifoldRuntime.kt`,
349
+ `${projectRoot}/app/src/main/res/layout/activity_main.xml`,
350
+ `${projectRoot}/app/src/main/res/values/strings.xml`,
351
+ `${projectRoot}/app/src/main/res/values/themes.xml`,
352
+ `${projectRoot}/app/src/main/res/xml/backup_rules.xml`,
353
+ `${projectRoot}/app/src/main/res/xml/data_extraction_rules.xml`,
354
+ `${projectRoot}/app/src/test/kotlin/${packagePath}/SemantifoldEntryTest.kt`,
355
+ `${projectRoot}/build.gradle.kts`,
356
+ `${projectRoot}/gradle.properties`,
357
+ `${projectRoot}/settings.gradle.kts`,
358
+ `${projectRoot}/semantifold-project.json`
359
+ ]
360
+ }
361
+
362
+ /**
363
+ * Renders the pinned root plugin declarations.
364
+ * @returns {string} Root Gradle Kotlin script.
365
+ */
366
+ function renderRootBuild() {
367
+ return `plugins {
368
+ id("com.android.application") version "${toolchain.androidGradlePlugin}" apply false
369
+ id("org.jetbrains.kotlin.android") version "${toolchain.kotlin}" apply false
370
+ }
371
+ `
372
+ }
373
+
374
+ /**
375
+ * Renders closed project settings and repositories.
376
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized configuration.
377
+ * @param {ConfigurationSpan[]} spans - Collected configuration provenance spans.
378
+ * @returns {string} Settings Gradle Kotlin script.
379
+ */
380
+ function renderSettings(configuration, spans) {
381
+ return configured(`${projectRoot}/settings.gradle.kts`, spans, [`pluginManagement {
382
+ repositories {
383
+ google()
384
+ mavenCentral()
385
+ gradlePluginPortal()
386
+ }
387
+ }
388
+
389
+ dependencyResolutionManagement {
390
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
391
+ repositories {
392
+ google()
393
+ mavenCentral()
394
+ }
395
+ }
396
+
397
+ rootProject.name = "`, field("productName", kotlinEscape(configuration.productName)), `"
398
+ include(":app")
399
+ `])
400
+ }
401
+
402
+ /**
403
+ * Renders deterministic Gradle process properties.
404
+ * @returns {string} Gradle properties file.
405
+ */
406
+ function renderGradleProperties() {
407
+ return `org.gradle.caching=false
408
+ org.gradle.configuration-cache=false
409
+ org.gradle.daemon=false
410
+ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -Duser.language=en -Duser.country=US -Duser.timezone=UTC
411
+ org.gradle.parallel=false
412
+ kotlin.stdlib.default.dependency=false
413
+ kotlin.code.style=official
414
+ `
415
+ }
416
+
417
+ /**
418
+ * Renders the application module build contract.
419
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized configuration.
420
+ * @param {ConfigurationSpan[]} spans - Collected configuration provenance spans.
421
+ * @returns {string} Application Gradle Kotlin script.
422
+ */
423
+ function renderAppBuild(configuration, spans) {
424
+ return configured(`${projectRoot}/app/build.gradle.kts`, spans, [
425
+ `import org.jetbrains.kotlin.gradle.dsl.JvmTarget
426
+ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
427
+
428
+ plugins {
429
+ id("com.android.application")
430
+ id("org.jetbrains.kotlin.android")
431
+ }
432
+
433
+ dependencies {
434
+ testRuntimeOnly("org.hamcrest:hamcrest-core:1.3") {
435
+ isTransitive = false
436
+ }
437
+ }
438
+
439
+ val semantifoldKotlinHome = providers.environmentVariable("SEMANTIFOLD_KOTLIN_HOME").orNull
440
+ ?: throw GradleException("SEMANTIFOLD_ANDROID_INFRASTRUCTURE: SEMANTIFOLD_KOTLIN_HOME is required")
441
+ val semantifoldKotlinStdlib = file("$semantifoldKotlinHome/lib/kotlin-stdlib.jar")
442
+ if (!semantifoldKotlinStdlib.isFile) {
443
+ throw GradleException("SEMANTIFOLD_ANDROID_INFRASTRUCTURE: pinned Kotlin stdlib is missing")
444
+ }
445
+
446
+ dependencies {
447
+ androidTestImplementation(files(semantifoldKotlinStdlib))
448
+ }
449
+ val semantifoldAcceptanceRoot = providers.environmentVariable("SEMANTIFOLD_ANDROID_ACCEPTANCE_ROOT").orNull
450
+ ?: throw GradleException("SEMANTIFOLD_ANDROID_INFRASTRUCTURE: SEMANTIFOLD_ANDROID_ACCEPTANCE_ROOT is required")
451
+ val semantifoldDebugKeystore = file("$semantifoldAcceptanceRoot/debug.keystore")
452
+ if (!semantifoldDebugKeystore.isFile) {
453
+ throw GradleException("SEMANTIFOLD_ANDROID_INFRASTRUCTURE: ephemeral debug keystore is missing")
454
+ }
455
+
456
+ android {
457
+ namespace = "`,
458
+ field("namespace", configuration.namespace), `"\n compileSdk = `, field("compileSdk", String(configuration.compileSdk)),
459
+ `\n buildToolsVersion = "`, field("buildToolsVersion", configuration.buildToolsVersion), `"\n\n defaultConfig {\n applicationId = "`,
460
+ field("applicationId", configuration.applicationId), `"\n minSdk = `, field("minimumSdk", String(configuration.minimumSdk)),
461
+ `\n targetSdk = `, field("targetSdk", String(configuration.targetSdk)), `\n versionCode = `,
462
+ field("versionCode", String(configuration.versionCode)), `\n versionName = "`, field("versionName", kotlinEscape(configuration.versionName)),
463
+ `"\n testApplicationId = "`, field("applicationId", configuration.applicationId),
464
+ `.test"\n testInstrumentationRunner = "`, field("packageName", configuration.packageName),
465
+ `.SemantifoldUiInstrumentation"\n }\n\n signingConfigs {\n getByName("debug") {\n storeFile = semantifoldDebugKeystore\n storePassword = "android"\n keyAlias = "androiddebugkey"\n keyPassword = "android"\n }\n }\n buildTypes {\n getByName("debug") {\n signingConfig = signingConfigs.getByName("debug")\n }\n }\n buildFeatures {\n buildConfig = false\n }\n compileOptions {\n sourceCompatibility = JavaVersion.VERSION_21\n targetCompatibility = JavaVersion.VERSION_21\n }\n lint {\n abortOnError = true\n checkDependencies = true\n disable += "MissingApplicationIcon"\n disable += "OldTargetApi"\n disable += "GradleDependency"\n warningsAsErrors = true\n }\n packaging {\n resources.excludes += setOf("META-INF/LICENSE*", "META-INF/NOTICE*")\n }\n}\n\ntasks.withType<KotlinJvmCompile>().configureEach {\n libraries.from(files(semantifoldKotlinStdlib))\n compilerOptions {\n allWarningsAsErrors.set(true)\n freeCompilerArgs.addAll("-Xno-call-assertions", "-Xno-param-assertions", "-Xno-receiver-assertions")\n jvmTarget.set(JvmTarget.JVM_21)\n }\n}\n\ndependencies {\n testImplementation("junit:junit:4.13.2") {\n isTransitive = false\n }\n}\n`
466
+ ])
467
+ }
468
+
469
+ /**
470
+ * Renders the allowlisted Android application manifest.
471
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized configuration.
472
+ * @param {ConfigurationSpan[]} spans - Collected configuration provenance spans.
473
+ * @returns {string} Android application manifest.
474
+ */
475
+ function renderAndroidManifest(configuration, spans) {
476
+ const orientation = configuration.orientation == "unspecified" ? "" : ` android:screenOrientation="${configuration.orientation}"`
477
+
478
+ return configured(`${projectRoot}/app/src/main/AndroidManifest.xml`, spans, [
479
+ `<?xml version="1.0" encoding="utf-8"?>\n<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n <application\n android:allowBackup="false"\n android:dataExtractionRules="@xml/data_extraction_rules"\n android:fullBackupContent="@xml/backup_rules"\n android:label="@string/app_name"\n android:supportsRtl="true"\n android:theme="@style/Theme.Semantifold">\n <activity\n android:name="`,
480
+ field("packageName", configuration.packageName), `.`, field("activityClassName", configuration.activityClassName),
481
+ `"\n android:exported="true"`,
482
+ orientation ? field("orientation", orientation) : "", `>\n <intent-filter>\n <action android:name="android.intent.action.MAIN" />\n <category android:name="android.intent.category.LAUNCHER" />\n </intent-filter>\n </activity>\n </application>\n</manifest>\n`
483
+ ])
484
+ }
485
+
486
+ /**
487
+ * Renders explicit API 23-30 exclusions for every full-backup storage domain.
488
+ * @returns {string} Android full-backup rules XML.
489
+ */
490
+ function renderBackupRules() {
491
+ return `<?xml version="1.0" encoding="utf-8"?>
492
+ <full-backup-content>
493
+ <exclude domain="root" path="." />
494
+ <exclude domain="file" path="." />
495
+ <exclude domain="database" path="." />
496
+ <exclude domain="sharedpref" path="." />
497
+ <exclude domain="external" path="." />
498
+ <exclude domain="device_root" path="." />
499
+ <exclude domain="device_file" path="." />
500
+ <exclude domain="device_database" path="." />
501
+ <exclude domain="device_sharedpref" path="." />
502
+ </full-backup-content>
503
+ `
504
+ }
505
+
506
+ /**
507
+ * Renders explicit Android 12+ exclusions for every cloud-backup and device-transfer storage domain.
508
+ * @returns {string} Android data extraction rules XML.
509
+ */
510
+ function renderDataExtractionRules() {
511
+ return `<?xml version="1.0" encoding="utf-8"?>
512
+ <data-extraction-rules>
513
+ <cloud-backup>
514
+ <exclude domain="root" path="." />
515
+ <exclude domain="file" path="." />
516
+ <exclude domain="database" path="." />
517
+ <exclude domain="sharedpref" path="." />
518
+ <exclude domain="external" path="." />
519
+ <exclude domain="device_root" path="." />
520
+ <exclude domain="device_file" path="." />
521
+ <exclude domain="device_database" path="." />
522
+ <exclude domain="device_sharedpref" path="." />
523
+ </cloud-backup>
524
+ <device-transfer>
525
+ <exclude domain="root" path="." />
526
+ <exclude domain="file" path="." />
527
+ <exclude domain="database" path="." />
528
+ <exclude domain="sharedpref" path="." />
529
+ <exclude domain="external" path="." />
530
+ <exclude domain="device_root" path="." />
531
+ <exclude domain="device_file" path="." />
532
+ <exclude domain="device_database" path="." />
533
+ <exclude domain="device_sharedpref" path="." />
534
+ </device-transfer>
535
+ </data-extraction-rules>
536
+ `
537
+ }
538
+
539
+ /**
540
+ * Renders the platform-only instrumentation manifest.
541
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized configuration.
542
+ * @param {ConfigurationSpan[]} spans - Collected configuration provenance spans.
543
+ * @returns {string} Android test manifest.
544
+ */
545
+ function renderAndroidTestManifest(configuration, spans) {
546
+ return configured(`${projectRoot}/app/src/androidTest/AndroidManifest.xml`, spans, [
547
+ `<?xml version="1.0" encoding="utf-8"?>\n<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n <instrumentation\n android:name="`,
548
+ field("packageName", configuration.packageName), `.SemantifoldUiInstrumentation"\n android:targetPackage="`,
549
+ field("applicationId", xmlEscape(configuration.applicationId)), `" />\n</manifest>\n`
550
+ ])
551
+ }
552
+
553
+ /**
554
+ * Renders the synthetic launcher activity.
555
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized configuration.
556
+ * @param {string} entryModule - Selected semantic entry module identity.
557
+ * @param {ConfigurationSpan[]} spans - Collected configuration provenance spans.
558
+ * @returns {string} Kotlin launcher activity source.
559
+ */
560
+ function renderActivity(configuration, entryModule, spans) {
561
+ const resourceImport = configuration.namespace == configuration.packageName
562
+ ? []
563
+ : ["import ", field("namespace", configuration.namespace), ".R\n"]
564
+
565
+ return configured(`${projectRoot}/app/src/main/kotlin/${configuration.packageName.replaceAll(".", "/")}/${configuration.activityClassName}.kt`, spans, [
566
+ "package ", field("packageName", configuration.packageName), `
567
+
568
+ import android.app.Activity
569
+ import android.annotation.SuppressLint
570
+ import android.os.Bundle
571
+ import android.widget.TextView
572
+ `, ...resourceImport, `import `, field("packageName", configuration.packageName),
573
+ `.semantic.SemantifoldModule${moduleName(entryModule)}
574
+
575
+ class `, field("activityClassName", configuration.activityClassName), ` : Activity() {
576
+ @SuppressLint("SetTextI18n")
577
+ override fun onCreate(savedInstanceState: Bundle?) {
578
+ super.onCreate(savedInstanceState)
579
+ setContentView(R.layout.activity_main)
580
+ val lines = SemantifoldModule${moduleName(entryModule)}.semantifoldEntry()
581
+ val output = java.lang.StringBuilder()
582
+ var index = 0
583
+ while (index < lines.size) {
584
+ if (index > 0) output.append('\\n')
585
+ output.append(lines[index])
586
+ index += 1
587
+ }
588
+ findViewById<TextView>(R.id.semantifold_output).text = output.toString()
589
+ }
590
+ }
591
+ `
592
+ ])
593
+ }
594
+
595
+ /**
596
+ * Renders the single accessibility-important native text view.
597
+ * @returns {string} Android layout XML.
598
+ */
599
+ function renderLayout() {
600
+ return `<?xml version="1.0" encoding="utf-8"?>
601
+ <TextView xmlns:android="http://schemas.android.com/apk/res/android"
602
+ android:id="@+id/semantifold_output"
603
+ android:layout_width="match_parent"
604
+ android:layout_height="match_parent"
605
+ android:gravity="center"
606
+ android:importantForAccessibility="yes"
607
+ android:textIsSelectable="true" />
608
+ `
609
+ }
610
+
611
+ /**
612
+ * Renders configuration-derived string resources.
613
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized configuration.
614
+ * @param {ConfigurationSpan[]} spans - Collected configuration provenance spans.
615
+ * @returns {string} Android string resource XML.
616
+ */
617
+ function renderStrings(configuration, spans) {
618
+ return configured(`${projectRoot}/app/src/main/res/values/strings.xml`, spans, [
619
+ `<?xml version="1.0" encoding="utf-8"?>\n<resources>\n <string name="app_name">`,
620
+ field("displayName", xmlEscape(configuration.displayName)), `</string>\n</resources>\n`
621
+ ])
622
+ }
623
+
624
+ /**
625
+ * Renders the fixed platform-native baseline theme.
626
+ * @returns {string} Android theme resource XML.
627
+ */
628
+ function renderTheme() {
629
+ return `<?xml version="1.0" encoding="utf-8"?>
630
+ <resources>
631
+ <style name="Theme.Semantifold" parent="android:style/Theme.Material.Light.NoActionBar">
632
+ <item name="android:fontFamily">sans</item>
633
+ <item name="android:windowActionModeOverlay">true</item>
634
+ </style>
635
+ </resources>
636
+ `
637
+ }
638
+
639
+ /**
640
+ * Renders the sole JUnit test-scope semantic assertion.
641
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized configuration.
642
+ * @param {string} entryModule - Selected semantic entry module identity.
643
+ * @param {ConfigurationSpan[]} spans - Collected configuration provenance spans.
644
+ * @returns {string} Kotlin unit test source.
645
+ */
646
+ function renderUnitTest(configuration, entryModule, spans) {
647
+ return configured(`${projectRoot}/app/src/test/kotlin/${configuration.packageName.replaceAll(".", "/")}/SemantifoldEntryTest.kt`, spans, [
648
+ "package ", field("packageName", configuration.packageName), `
649
+
650
+ import `, field("packageName", configuration.packageName), `.semantic.SemantifoldModule${moduleName(entryModule)}
651
+ import org.junit.Test
652
+
653
+ class SemantifoldEntryTest {
654
+ @Test
655
+ fun entryIsDeterministicAcrossFreshRuns() {
656
+ val firstOutput = SemantifoldModule${moduleName(entryModule)}.semantifoldEntry()
657
+ val secondOutput = SemantifoldModule${moduleName(entryModule)}.semantifoldEntry()
658
+ if (!firstOutput.equals(secondOutput)) {
659
+ throw AssertionError("Semantic entry output differs across fresh runs.")
660
+ }
661
+ }
662
+ }
663
+ `
664
+ ])
665
+ }
666
+
667
+ /**
668
+ * Renders the framework-only instrumentation runner.
669
+ * @param {import("../semantic/types.js").AndroidApplicationConfiguration} configuration - Normalized configuration.
670
+ * @param {ConfigurationSpan[]} spans - Collected configuration provenance spans.
671
+ * @returns {string} Kotlin instrumentation source.
672
+ */
673
+ function renderInstrumentation(configuration, spans) {
674
+ const resourceImport = configuration.namespace == configuration.packageName
675
+ ? []
676
+ : ["import ", field("namespace", configuration.namespace), ".R\n"]
677
+
678
+ return configured(`${projectRoot}/app/src/androidTest/kotlin/${configuration.packageName.replaceAll(".", "/")}/SemantifoldUiInstrumentation.kt`, spans, [
679
+ "package ", field("packageName", configuration.packageName), `
680
+
681
+ import android.app.Activity
682
+ import android.app.Instrumentation
683
+ import android.content.Intent
684
+ import android.os.Bundle
685
+ import android.util.Base64
686
+ import android.view.View
687
+ import android.widget.TextView
688
+ import java.nio.charset.StandardCharsets
689
+ `, ...resourceImport, `
690
+ class SemantifoldUiInstrumentation : Instrumentation() {
691
+ private var inputArguments: Bundle? = null
692
+
693
+ override fun onCreate(arguments: Bundle?) {
694
+ inputArguments = arguments
695
+ start()
696
+ }
697
+
698
+ override fun onStart() {
699
+ try {
700
+ val providedArguments = inputArguments
701
+ ?: throw IllegalStateException("missing instrumentation arguments")
702
+ val encoded = providedArguments.getString("expected_output_base64")
703
+ ?: throw IllegalStateException("missing expected_output_base64 instrumentation argument")
704
+ val expected = String(Base64.decode(encoded, Base64.NO_WRAP), StandardCharsets.UTF_8)
705
+ val launch = targetContext.packageManager.getLaunchIntentForPackage(targetContext.packageName)
706
+ ?: throw IllegalStateException("generated application has no launcher intent")
707
+ launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
708
+ val activity = startActivitySync(launch) as `, field("activityClassName", configuration.activityClassName), `
709
+ assertOutput(activity, expected)
710
+ val monitor = addMonitor(`, field("activityClassName", configuration.activityClassName), `::class.java.name, null, false)
711
+ runOnMainSync { activity.recreate() }
712
+ waitForIdleSync()
713
+ val recreated = waitForMonitorWithTimeout(monitor, 10_000)
714
+ ?: throw IllegalStateException("activity was not recreated")
715
+ assertOutput(recreated, expected)
716
+ finish(Activity.RESULT_OK, Bundle())
717
+ } catch (error: Throwable) {
718
+ val result = Bundle()
719
+ result.putString("semantifold_failure", android.util.Log.getStackTraceString(error))
720
+ finish(Activity.RESULT_CANCELED, result)
721
+ }
722
+ }
723
+
724
+ private fun assertOutput(activity: Activity, expected: String) {
725
+ waitForIdleSync()
726
+ val actual = activity.findViewById<TextView>(R.id.semantifold_output)
727
+ ?: throw IllegalStateException("missing semantifold_output view")
728
+ if (actual.visibility != View.VISIBLE) throw IllegalStateException("output view is not visible")
729
+ if (actual.javaClass != TextView::class.java) throw IllegalStateException("output view is not a native TextView")
730
+ if (!actual.text.toString().equals(expected)) throw IllegalStateException("output text mismatch")
731
+ if (actual.contentDescription != null) throw IllegalStateException("output content description must stay absent")
732
+ if (actual.importantForAccessibility != View.IMPORTANT_FOR_ACCESSIBILITY_YES) {
733
+ throw IllegalStateException("output view is not accessibility-important")
734
+ }
735
+ }
736
+ }
737
+ `
738
+ ])
739
+ }
740
+
741
+ /**
742
+ * Renders the ownership, toolchain, and provenance manifest.
743
+ * @param {ReturnType<typeof preflightAndroidApplication>} prepared - Prepared application.
744
+ * @param {import("../semantic/types.js").GeneratedSetArtifact[]} artifacts - Owned project artifacts.
745
+ * @param {string} manifestPath - Ownership manifest path.
746
+ * @param {ConfigurationSpan[]} spans - Configuration provenance spans.
747
+ * @returns {string} Canonical project manifest JSON.
748
+ */
749
+ function renderProjectManifest(prepared, artifacts, manifestPath, spans) {
750
+ const inputPaths = new Set([...prepared.resources, ...prepared.assets].map(({fullPath}) => fullPath))
751
+ const semanticArtifacts = artifacts.filter(artifact => artifact.provenance.kind == "text")
752
+ const ownedPaths = [...artifacts.map(({path}) => path), manifestPath]
753
+
754
+ return stringifyCanonicalJson({
755
+ configuration: prepared.configuration,
756
+ generator: {name: "semantifold", version: generatorVersion},
757
+ ownership: {
758
+ excludedPathPatterns,
759
+ ownedPaths,
760
+ ownedRoot: projectRoot,
761
+ sha256: artifacts.map(artifact => ({path: artifact.path, sha256: hashContent(artifact.content)}))
762
+ },
763
+ provenance: {
764
+ configuration: Object.keys(prepared.configuration).sort().map(fieldName => ({
765
+ field: fieldName,
766
+ manifestPointer: `/configuration/${fieldName}`,
767
+ outputs: configurationOutputs(spans, fieldName)
768
+ })),
769
+ inputs: [...prepared.resources, ...prepared.assets].map(item => ({
770
+ contentKind: item.contentKind,
771
+ mediaType: item.mediaType,
772
+ path: item.fullPath,
773
+ sha256: item.sha256
774
+ })),
775
+ semanticSources: prepared.sources.map(source => ({
776
+ artifacts: semanticArtifacts.filter(artifact => artifact.provenance.kind == "text" &&
777
+ artifact.provenance.mapping.spans.some(({origin}) => originReferencesFilename(origin, source.filename)))
778
+ .map(({path}) => path),
779
+ filename: source.filename,
780
+ ...(source.language === undefined ? {} : {language: source.language}),
781
+ sha256: createHash("sha256").update(source.content).digest("hex")
782
+ })),
783
+ syntheticScaffolding: [...artifacts.filter(artifact => artifact.provenance.kind == "synthetic" &&
784
+ !inputPaths.has(artifact.path)).map(({path}) => path), manifestPath]
785
+ },
786
+ schema: "SemantifoldAndroidProject",
787
+ semanticProgram: semanticProgramManifest(prepared),
788
+ target: "android",
789
+ toolchain,
790
+ version: 1
791
+ })
792
+ }
793
+
794
+ /**
795
+ * Renders semantic module and source identities for the project manifest.
796
+ * @param {ReturnType<typeof preflightAndroidApplication>} prepared - Prepared application.
797
+ * @returns {Record<string, unknown>} Semantic program manifest.
798
+ */
799
+ function semanticProgramManifest(prepared) {
800
+ const sourceContent = new Map(prepared.sources.map(source => [source.filename, source.content]))
801
+ const order = new Map(prepared.program.modules.map((module, index) => [module.id, index]))
802
+ const modules = prepared.program.modules.map(module => {
803
+ const source = prepared.program.sources.find(candidate => candidate.filename == module.sourceFilename)
804
+ const content = sourceContent.get(module.sourceFilename)
805
+
806
+ if (!source || content === undefined) throw new TypeError(`Preflighted Android module '${module.id}' lost source ownership.`)
807
+
808
+ return {
809
+ dependencies: [...new Set(module.imports.map(({moduleId}) => moduleId))]
810
+ .sort((left, right) => /** @type {number} */ (order.get(left)) - /** @type {number} */ (order.get(right))),
811
+ generatedArtifacts: [prepared.modulePaths.get(module.id)],
812
+ id: module.id,
813
+ namespace: `SemantifoldModule${moduleName(module.id)}`,
814
+ source: {filename: source.filename, id: source.id, sha256: createHash("sha256").update(content).digest("hex")}
815
+ }
816
+ })
817
+ const entryId = prepared.program.entryModule
818
+
819
+ return {
820
+ entry: {
821
+ activityArtifact: `${projectRoot}/app/src/main/kotlin/${prepared.packagePath}/${prepared.configuration.activityClassName}.kt`,
822
+ generatedArtifact: prepared.modulePaths.get(entryId),
823
+ generatedFunctionIdentity: `SemantifoldModule${moduleName(entryId)}.semantifoldEntry`,
824
+ moduleId: entryId
825
+ },
826
+ modules,
827
+ schema: "SemantifoldAndroidSemanticProgram",
828
+ sources: prepared.program.sources.map(source => ({
829
+ filename: source.filename,
830
+ id: source.id,
831
+ language: source.language,
832
+ moduleIds: modules.filter(module => module.source.filename == source.filename).map(({id}) => id),
833
+ ownership: source.ownership ?? "application",
834
+ sha256: createHash("sha256").update(/** @type {string} */ (sourceContent.get(source.filename))).digest("hex")
835
+ })),
836
+ version: 1
837
+ }
838
+ }
839
+
840
+ /**
841
+ * Validates and normalizes the closed Android configuration.
842
+ * @param {unknown} candidate - Candidate configuration.
843
+ * @returns {import("../semantic/types.js").AndroidApplicationConfiguration} Normalized configuration.
844
+ */
845
+ function normalizeConfiguration(candidate) {
846
+ if (candidate === undefined) candidate = {}
847
+ if (!isPlainObject(candidate) || Object.keys(candidate).some(key => !configurationFields.has(key))) {
848
+ invalidConfiguration("Android application configuration must be a closed plain object.")
849
+ }
850
+ const configuration = /** @type {import("../semantic/types.js").AndroidApplicationConfigurationInput} */ (candidate)
851
+ const applicationId = configuration.applicationId ?? "dev.semantifold.generated"
852
+ const namespace = configuration.namespace ?? applicationId
853
+ const packageName = configuration.packageName ?? namespace
854
+ const activityClassName = configuration.activityClassName ?? "MainActivity"
855
+ const productName = configuration.productName ?? "Semantifold"
856
+ const displayName = configuration.displayName ?? "Semantifold"
857
+ const minimumSdk = configuration.minimumSdk ?? 23
858
+ const targetSdk = configuration.targetSdk ?? toolchain.targetSdk
859
+ const compileSdk = configuration.compileSdk ?? toolchain.compileSdk
860
+ const buildToolsVersion = configuration.buildToolsVersion ?? toolchain.buildTools
861
+ const versionCode = configuration.versionCode ?? 1
862
+ const versionName = configuration.versionName ?? "1.0.0"
863
+ const orientation = configuration.orientation ?? "unspecified"
864
+ const theme = configuration.theme ?? "system"
865
+
866
+ for (const [label, value] of [["Application ID", applicationId], ["Namespace", namespace], ["Package name", packageName]]) {
867
+ if (typeof value != "string" || !isPackageName(value)) invalidConfiguration(`${label} must be a lowercase Java package identity.`)
868
+ }
869
+ if (typeof activityClassName != "string" || !/^[A-Z][A-Za-z0-9]*$/u.test(activityClassName) || kotlinKeywords.has(activityClassName)) {
870
+ invalidConfiguration("Activity class name must be an ASCII upper-camel Kotlin identifier.")
871
+ }
872
+ for (const [label, value] of [["Product name", productName], ["Display name", displayName]]) {
873
+ if (typeof value != "string" || value.length == 0 || value.trim() != value || [...value].length > 64 ||
874
+ !hasOnlyUnicodeScalars(value) || /[\p{Cc}\p{Zl}\p{Zp}\uFFFE\uFFFF]/u.test(value)) {
875
+ invalidConfiguration(`${label} must be a non-empty single-line Unicode scalar string of at most 64 characters.`)
876
+ }
877
+ }
878
+ if (/[\\/:<>"?*|]/u.test(productName)) {
879
+ invalidConfiguration("Product name contains a character forbidden by the Gradle project-name contract.")
880
+ }
881
+ if (minimumSdk !== 23 || targetSdk !== 35 || compileSdk !== 35 || minimumSdk > targetSdk || targetSdk > compileSdk) {
882
+ invalidConfiguration("SDK levels must use the qualified minimum 23, target 35, and compile 35 matrix.")
883
+ }
884
+ if (buildToolsVersion != toolchain.buildTools) invalidConfiguration("Build Tools version must be exactly '35.0.0'.")
885
+ if (!Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2_100_000_000) {
886
+ invalidConfiguration("Version code must be a positive Android-safe integer.")
887
+ }
888
+ if (typeof versionName != "string" || !semanticVersionPattern.test(versionName)) {
889
+ invalidConfiguration("Version name must be a canonical non-empty semantic version string.")
890
+ }
891
+ if (orientation != "unspecified") invalidConfiguration("The baseline orientation is exactly 'unspecified'.")
892
+ if (theme != "system") invalidConfiguration("The baseline theme is exactly 'system'.")
893
+ if (configuration.permissions !== undefined &&
894
+ (!isDenseArray(configuration.permissions) || configuration.permissions.length != 0)) {
895
+ invalidConfiguration("The baseline Android permission set must be empty.")
896
+ }
897
+
898
+ return {
899
+ activityClassName,
900
+ applicationId,
901
+ buildToolsVersion,
902
+ compileSdk,
903
+ displayName,
904
+ minimumSdk,
905
+ namespace,
906
+ orientation,
907
+ packageName,
908
+ productName,
909
+ targetSdk,
910
+ theme,
911
+ versionCode,
912
+ versionName
913
+ }
914
+ }
915
+
916
+ /**
917
+ * Validates, snapshots, and orders caller-provided inputs.
918
+ * @param {unknown} candidate - Candidate resource or asset list.
919
+ * @param {"asset" | "resource"} kind - Android input kind.
920
+ * @returns {AndroidInputFile[]} Normalized inputs.
921
+ */
922
+ function normalizeInputFiles(candidate, kind) {
923
+ if (candidate === undefined) return []
924
+ if (!isDenseArray(candidate)) invalidResource(`Application ${kind}s must be a dense array.`)
925
+ /** @type {AndroidInputFile[]} */
926
+ const files = []
927
+
928
+ for (let index = 0; index < candidate.length; index += 1) {
929
+ const item = candidate[index]
930
+
931
+ if (!isPlainObject(item) || Object.keys(item).length != resourceFields.size ||
932
+ Object.keys(item).some(key => !resourceFields.has(key))) invalidResource(`${capitalize(kind)} ${index} must use the closed input schema.`)
933
+ const path = item.path
934
+ const content = item.content
935
+ const mediaType = item.mediaType
936
+ const sha256 = item.sha256
937
+
938
+ if (!isSafeArtifactPath(path) || kind == "resource" && !isAllowedResourcePath(path)) {
939
+ invalidResource(`${capitalize(kind)} ${index} has an unsafe or unsupported path.`)
940
+ }
941
+ if (typeof content != "string" && !(content instanceof Uint8Array) || typeof content == "string" &&
942
+ (content.length == 0 || !hasOnlyUnicodeScalars(content)) || content instanceof Uint8Array && content.byteLength == 0) {
943
+ invalidResource(`${capitalize(kind)} '${path}' requires non-empty exact content.`)
944
+ }
945
+ if (typeof mediaType != "string" || !mediaTypePattern.test(mediaType) || /[\r\n]/u.test(mediaType)) {
946
+ invalidResource(`${capitalize(kind)} '${path}' requires an explicit valid media type.`)
947
+ }
948
+ if (kind == "resource" && path.startsWith("drawable-") && !["image/png", "image/webp"].includes(mediaType)) {
949
+ invalidResource(`Drawable resource '${path}' must use PNG or WebP media type.`)
950
+ }
951
+ const snapshot = typeof content == "string" ? content : new Uint8Array(content)
952
+ const actualHash = createHash("sha256").update(snapshot).digest("hex")
953
+
954
+ if (typeof sha256 != "string" || !/^[0-9a-f]{64}$/u.test(sha256) || sha256 != actualHash) {
955
+ invalidResource(`${capitalize(kind)} '${path}' SHA-256 does not match its exact content.`)
956
+ }
957
+ const fullPath = `${projectRoot}/app/src/main/${kind == "resource" ? "res" : "assets"}/${path}`
958
+
959
+ files.push({content: snapshot, contentKind: typeof snapshot == "string" ? "text" : "binary", fullPath, mediaType, path, sha256})
960
+ }
961
+ files.sort((left, right) => left.fullPath < right.fullPath ? -1 : left.fullPath > right.fullPath ? 1 : 0)
962
+ const conflict = findPortableArtifactPathConflict(files.map(({fullPath}) => fullPath))
963
+
964
+ if (conflict) invalidResource(`${capitalize(kind)} path '${conflict.path}' has a ${conflict.kind} conflict.`)
965
+ if (kind == "resource") validateResourceIdentifiers(files)
966
+
967
+ return files
968
+ }
969
+
970
+ /**
971
+ * Rejects two files that Android would compile to the same resource identifier in one configuration.
972
+ * @param {AndroidInputFile[]} files - Validated resource inputs.
973
+ */
974
+ function validateResourceIdentifiers(files) {
975
+ const identifiers = new Map()
976
+
977
+ for (const file of files) {
978
+ const separator = file.path.indexOf("/")
979
+ const directory = file.path.slice(0, separator)
980
+ const filename = file.path.slice(separator + 1)
981
+ const extension = filename.indexOf(".")
982
+ const name = extension == -1 ? filename : filename.slice(0, extension)
983
+ const resourceType = directory.split("-", 1)[0]
984
+ const key = `${directory}/${name}`
985
+ const previous = identifiers.get(key)
986
+
987
+ if (previous) {
988
+ invalidResource(`Android resource identifier '${resourceType}/${name}' collides between '${previous}' and '${file.path}' in configuration '${directory}'.`)
989
+ }
990
+ identifiers.set(key, file.path)
991
+ }
992
+ }
993
+
994
+ /**
995
+ * Tests whether a caller resource uses the narrow allowlisted resource grammar.
996
+ * @param {string} value - Resource path relative to res.
997
+ * @returns {boolean} Whether the path is supported.
998
+ */
999
+ function isAllowedResourcePath(value) {
1000
+ if (/^raw\/[a-z][a-z0-9_]*(?:\.[a-z0-9]+)?$/u.test(value)) return true
1001
+
1002
+ return /^drawable-(?:mdpi|hdpi|xhdpi|xxhdpi|xxxhdpi)\/[a-z][a-z0-9_]*\.(?:png|webp)$/u.test(value)
1003
+ }
1004
+
1005
+ /**
1006
+ * Converts a validated input snapshot into an owned artifact.
1007
+ * @param {AndroidInputFile} item - Validated caller input.
1008
+ * @returns {import("../semantic/types.js").GeneratedSetArtifact} Owned input artifact.
1009
+ */
1010
+ function inputFileArtifact(item) {
1011
+ const reason = `Exact caller-provided Android input (sha256:${item.sha256}).`
1012
+
1013
+ if (typeof item.content == "string") {
1014
+ return {
1015
+ ...syntheticText(item.fullPath, "resource", item.content, reason),
1016
+ mediaType: item.mediaType
1017
+ }
1018
+ }
1019
+ const content = new Uint8Array(item.content)
1020
+
1021
+ return {
1022
+ content,
1023
+ contentKind: "binary",
1024
+ mediaType: item.mediaType,
1025
+ ownership: "generated",
1026
+ path: item.fullPath,
1027
+ provenance: {kind: "bytes", mapping: createByteMapping({
1028
+ byteLength: content.byteLength,
1029
+ path: item.fullPath,
1030
+ ranges: [{generated: {end: content.byteLength, start: 0},
1031
+ origin: {kind: "synthetic", reason, relatedOrigins: []}, role: "resource"}]
1032
+ })},
1033
+ role: "resource"
1034
+ }
1035
+ }
1036
+
1037
+ /**
1038
+ * Creates a deterministic synthetic text artifact.
1039
+ * @param {string} path - Artifact path.
1040
+ * @param {import("../semantic/types.js").GeneratedArtifactRole} role - Artifact role.
1041
+ * @param {string} content - UTF-8 artifact content.
1042
+ * @param {string} reason - Synthetic provenance reason.
1043
+ * @returns {import("../semantic/types.js").GeneratedSetArtifact} Text artifact.
1044
+ */
1045
+ function syntheticText(path, role, content, reason) {
1046
+ return {content, contentKind: "text", mediaType: mediaTypeFor(path), ownership: "generated", path,
1047
+ provenance: {kind: "synthetic", reason, relatedOrigins: []}, role}
1048
+ }
1049
+
1050
+ /**
1051
+ * Creates a configuration-derived text artifact.
1052
+ * @param {string} path - Artifact path.
1053
+ * @param {import("../semantic/types.js").GeneratedArtifactRole} role - Artifact role.
1054
+ * @param {string} content - UTF-8 artifact content.
1055
+ * @param {string} reason - Synthetic provenance reason.
1056
+ * @returns {import("../semantic/types.js").GeneratedSetArtifact} Text artifact.
1057
+ */
1058
+ function configuredText(path, role, content, reason) {
1059
+ return syntheticText(path, role, content, reason)
1060
+ }
1061
+
1062
+ /**
1063
+ * Derives the media type for a generated text path.
1064
+ * @param {string} path - Generated artifact path.
1065
+ * @returns {string} Artifact media type.
1066
+ */
1067
+ function mediaTypeFor(path) {
1068
+ if (path.endsWith(".json")) return "application/json"
1069
+ if (path.endsWith(".xml")) return "application/xml"
1070
+ if (path.endsWith(".kt") || path.endsWith(".kts")) return "text/x-kotlin"
1071
+
1072
+ return "text/plain"
1073
+ }
1074
+
1075
+ /**
1076
+ * Marks one rendered value as configuration-derived.
1077
+ * @param {string} fieldName - Configuration field name.
1078
+ * @param {string} value - Rendered field value.
1079
+ * @returns {{field: string, value: string}} Marked configuration value.
1080
+ */
1081
+ function field(fieldName, value) {
1082
+ return {field: fieldName, value}
1083
+ }
1084
+
1085
+ /**
1086
+ * Joins rendered parts while recording configuration causality.
1087
+ * @param {string} path - Output artifact path.
1088
+ * @param {ConfigurationSpan[]} spans - Mutable provenance span collector.
1089
+ * @param {(string | {field: string, value: string})[]} parts - Synthetic and marked parts.
1090
+ * @returns {string} Rendered text.
1091
+ */
1092
+ function configured(path, spans, parts) {
1093
+ let content = ""
1094
+
1095
+ for (const part of parts) {
1096
+ if (typeof part == "string") content += part
1097
+ else {
1098
+ const start = content.length
1099
+
1100
+ content += part.value
1101
+ spans.push({end: content.length, field: part.field, path, start})
1102
+ }
1103
+ }
1104
+
1105
+ return content
1106
+ }
1107
+
1108
+ /**
1109
+ * Groups configuration spans by artifact.
1110
+ * @param {ConfigurationSpan[]} spans - All rendered configuration spans.
1111
+ * @param {string} fieldName - Requested configuration field.
1112
+ * @returns {{path: string, ranges: {end: number, start: number}[]}[]} Output ranges.
1113
+ */
1114
+ function configurationOutputs(spans, fieldName) {
1115
+ const grouped = new Map()
1116
+
1117
+ for (const span of spans) {
1118
+ if (span.field != fieldName) continue
1119
+ const ranges = grouped.get(span.path) ?? []
1120
+
1121
+ ranges.push({end: span.end, start: span.start})
1122
+ grouped.set(span.path, ranges)
1123
+ }
1124
+
1125
+ return [...grouped].map(([path, ranges]) => ({path, ranges}))
1126
+ }
1127
+
1128
+ /**
1129
+ * Tests whether a mapping origin refers to one semantic source file.
1130
+ * @param {import("../semantic/types.js").SemanticOrigin} origin - Mapping origin.
1131
+ * @param {string} filename - Semantic source filename.
1132
+ * @returns {boolean} Whether the origin owns the filename.
1133
+ */
1134
+ function originReferencesFilename(origin, filename) {
1135
+ if (origin.kind == "source") return origin.location.filename == filename
1136
+ if (origin.kind == "derived") return origin.origins.some(({location}) => location.filename == filename)
1137
+
1138
+ return origin.relatedOrigins.some(({location}) => location.filename == filename)
1139
+ }
1140
+
1141
+ /**
1142
+ * Hashes exact text or bytes for ownership records.
1143
+ * @param {string | Uint8Array} content - Exact artifact content.
1144
+ * @returns {string} Lowercase SHA-256 digest.
1145
+ */
1146
+ function hashContent(content) {
1147
+ return createHash("sha256").update(content).digest("hex")
1148
+ }
1149
+
1150
+ /**
1151
+ * Serializes JSON with recursively stable object-key ordering.
1152
+ * @param {unknown} value - JSON-compatible value.
1153
+ * @returns {string} Canonical indented JSON with trailing newline.
1154
+ */
1155
+ function stringifyCanonicalJson(value) {
1156
+ return `${JSON.stringify(sortJson(value), null, 2)}\n`
1157
+ }
1158
+
1159
+ /**
1160
+ * Recursively sorts object keys without changing array order.
1161
+ * @param {unknown} value - JSON-compatible value.
1162
+ * @returns {unknown} Canonically ordered value.
1163
+ */
1164
+ function sortJson(value) {
1165
+ if (Array.isArray(value)) return value.map(sortJson)
1166
+ if (!isPlainObject(value)) return value
1167
+
1168
+ return Object.fromEntries(Object.keys(value).sort().map(key => [key, sortJson(value[key])]))
1169
+ }
1170
+
1171
+ /**
1172
+ * Tests a lowercase Kotlin-compatible package identity.
1173
+ * @param {string} value - Candidate package name.
1174
+ * @returns {boolean} Whether the name is accepted.
1175
+ */
1176
+ function isPackageName(value) {
1177
+ const parts = value.split(".")
1178
+
1179
+ return parts.length >= 2 && parts.every(part => /^[a-z][a-z0-9_]*$/u.test(part) && !kotlinKeywords.has(part))
1180
+ }
1181
+
1182
+ /**
1183
+ * Rejects launcher names that shadow an unqualified symbol in generated same-package Kotlin.
1184
+ * @param {string} activityClassName - Validated launcher class name.
1185
+ * @param {string} entryModule - Selected semantic entry module.
1186
+ */
1187
+ function validateActivityClassName(activityClassName, entryModule) {
1188
+ const semanticEntrySymbol = `SemantifoldModule${moduleName(entryModule)}`
1189
+
1190
+ if (androidScaffoldSymbols.has(activityClassName) || activityClassName == semanticEntrySymbol) {
1191
+ invalidConfiguration(`Activity class name '${activityClassName}' collides with generated Kotlin symbol '${activityClassName}'.`)
1192
+ }
1193
+ }
1194
+
1195
+ /**
1196
+ * Allocates one module-wide output sink that cannot shadow a source binding.
1197
+ * @param {import("../semantic/types.js").SemanticModule} module - Prepared semantic module.
1198
+ * @returns {string} Collision-safe Kotlin binding.
1199
+ */
1200
+ function outputSinkName(module) {
1201
+ const bindings = new Set(module.functions.flatMap(declaration => declaration.parameters.map(({name}) => name)))
1202
+
1203
+ for (const declaration of module.functions) collectBlockBindingNames(declaration.body, bindings)
1204
+ collectBlockBindingNames(module.entryPoint.body, bindings)
1205
+ let candidate = "semantifoldOutput"
1206
+
1207
+ while (bindings.has(candidate)) candidate += "_"
1208
+
1209
+ return candidate
1210
+ }
1211
+
1212
+ /**
1213
+ * Collects local bindings recursively from the supported semantic block subset.
1214
+ * @param {import("../semantic/types.js").Block} block - Semantic block.
1215
+ * @param {Set<string>} bindings - Mutable binding-name set.
1216
+ */
1217
+ function collectBlockBindingNames(block, bindings) {
1218
+ for (const statement of block.statements) {
1219
+ if (statement.kind == "LocalDeclaration") bindings.add(statement.name)
1220
+ else if (statement.kind == "IfStatement") {
1221
+ collectBlockBindingNames(statement.consequent, bindings)
1222
+ if (statement.alternate) collectBlockBindingNames(statement.alternate, bindings)
1223
+ }
1224
+ }
1225
+ }
1226
+
1227
+ /**
1228
+ * Maps a preflighted module identity to a stable Kotlin suffix.
1229
+ * @param {string} id - Semantic module identity.
1230
+ * @returns {string} Kotlin object-name suffix.
1231
+ */
1232
+ function moduleName(id) {
1233
+ return id.split(/[._-]+/u).map(part => `${part[0].toUpperCase()}${part.slice(1)}`).join("")
1234
+ }
1235
+
1236
+ /**
1237
+ * Wraps legacy single-module input in the canonical program contract.
1238
+ * @param {import("../semantic/types.js").SemanticModule | undefined} candidate - Candidate semantic module.
1239
+ * @returns {import("../semantic/types.js").SemanticProgram} Canonical single-module program.
1240
+ */
1241
+ function normalizeSingleModule(candidate) {
1242
+ if (!isPlainObject(candidate)) unsupportedCapability("android", "missing or invalid module", undefined)
1243
+ const module = structuredClone(candidate)
1244
+ const sources = module.provenance?.sources
1245
+
1246
+ if (!Array.isArray(sources) || sources.length != 1 || sources[0].content === null ||
1247
+ sources[0].filename != module.location?.filename || typeof sources[0].language != "string") {
1248
+ unsupportedCapability("android", "single-module source ownership", module.location)
1249
+ }
1250
+ rekeyModuleDeclarations(module, "main")
1251
+ const exports = [
1252
+ ...(module.records ?? []).map(declaration => applicationExport(declaration, "record")),
1253
+ ...(module.classes ?? []).map(declaration => applicationExport(declaration, "class")),
1254
+ ...(module.errors ?? []).map(declaration => applicationExport(declaration, "error")),
1255
+ ...module.functions.map(declaration => applicationExport(declaration, "function"))
1256
+ ]
1257
+ const programModule = {...module, exports, id: "main", imports: [], sourceFilename: sources[0].filename}
1258
+
1259
+ return {entryModule: "main", kind: "Program", modules: [programModule], sources: structuredClone(sources)}
1260
+ }
1261
+
1262
+ /**
1263
+ * Creates an application-local export record for single-module compatibility.
1264
+ * @param {{id?: string, location: import("../semantic/types.js").SourceLocation, name: string}} declaration - Semantic declaration.
1265
+ * @param {import("../semantic/types.js").SemanticDeclarationKind} symbolKind - Declaration kind.
1266
+ * @returns {import("../semantic/types.js").SemanticExport} Semantic export.
1267
+ */
1268
+ function applicationExport(declaration, symbolKind) {
1269
+ return {declarationId: /** @type {string} */ (declaration.id), exportedName: declaration.name,
1270
+ kind: /** @type {const} */ ("Export"), location: declaration.location, symbolKind}
1271
+ }
1272
+
1273
+ /**
1274
+ * Qualifies single-module declaration identities before program preflight.
1275
+ * @param {import("../semantic/types.js").SemanticModule} module - Cloned semantic module.
1276
+ * @param {string} moduleId - Canonical module identity.
1277
+ */
1278
+ function rekeyModuleDeclarations(module, moduleId) {
1279
+ const declarations = [...module.records ?? [], ...module.classes ?? [], ...module.errors ?? [], ...module.functions]
1280
+ const replacements = new Map(declarations.filter(declaration => typeof declaration.id == "string")
1281
+ .map(declaration => [declaration.id, `${moduleId}#${declaration.id}`]))
1282
+
1283
+ for (const declaration of declarations) if (typeof declaration.id == "string") declaration.id = replacements.get(declaration.id)
1284
+ const seen = new Set()
1285
+ const identityFields = new Set(["classId", "declarationId", "field", "method", "parameterId"])
1286
+ /**
1287
+ * Rewrites declaration references throughout one semantic value graph.
1288
+ * @param {unknown} value - Candidate nested semantic value.
1289
+ */
1290
+ const visit = value => {
1291
+ if (!value || typeof value != "object" || seen.has(value)) return
1292
+ seen.add(value)
1293
+ for (const [key, nested] of Object.entries(value)) {
1294
+ if (!Array.isArray(value) && identityFields.has(key) && typeof nested == "string" && replacements.has(nested)) {
1295
+ Reflect.set(value, key, replacements.get(nested))
1296
+ } else visit(nested)
1297
+ }
1298
+ }
1299
+
1300
+ visit(module)
1301
+ }
1302
+
1303
+ /**
1304
+ * Escapes a configuration value for XML text or attributes.
1305
+ * @param {string} value - Raw configuration value.
1306
+ * @returns {string} XML-safe value.
1307
+ */
1308
+ function xmlEscape(value) {
1309
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")
1310
+ .replaceAll('"', "&quot;").replaceAll("'", "&apos;")
1311
+ }
1312
+
1313
+ /**
1314
+ * Escapes a configuration value for a Kotlin string literal body.
1315
+ * @param {string} value - Raw configuration value.
1316
+ * @returns {string} Kotlin-safe value.
1317
+ */
1318
+ function kotlinEscape(value) {
1319
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("$", "\\$")
1320
+ }
1321
+
1322
+ /**
1323
+ * Uppercases the initial ASCII character of a known non-empty word.
1324
+ * @param {string} value - Non-empty word.
1325
+ * @returns {string} Capitalized word.
1326
+ */
1327
+ function capitalize(value) {
1328
+ return `${value[0].toUpperCase()}${value.slice(1)}`
1329
+ }
1330
+
1331
+ /**
1332
+ * Tests whether a value is a plain data object.
1333
+ * @param {unknown} value - Candidate value.
1334
+ * @returns {value is Record<string, unknown>} Whether the value is a plain object.
1335
+ */
1336
+ function isPlainObject(value) {
1337
+ return Boolean(value) && typeof value == "object" && !Array.isArray(value) && Object.getPrototypeOf(value) == Object.prototype
1338
+ }
1339
+
1340
+ /**
1341
+ * Throws a stable invalid-request diagnostic.
1342
+ * @param {string} message - Diagnostic message.
1343
+ * @returns {never} Never returns.
1344
+ */
1345
+ function invalidInput(message) {
1346
+ throw new SemantifoldDiagnostic({code: "INVALID_APPLICATION_INPUT", language: "android", message})
1347
+ }
1348
+
1349
+ /**
1350
+ * Throws a stable invalid-configuration diagnostic.
1351
+ * @param {string} message - Diagnostic message.
1352
+ * @returns {never} Never returns.
1353
+ */
1354
+ function invalidConfiguration(message) {
1355
+ throw new SemantifoldDiagnostic({code: "INVALID_APPLICATION_CONFIGURATION", language: "android", message})
1356
+ }
1357
+
1358
+ /**
1359
+ * Throws a stable invalid-resource diagnostic.
1360
+ * @param {string} message - Diagnostic message.
1361
+ * @returns {never} Never returns.
1362
+ */
1363
+ function invalidResource(message) {
1364
+ throw new SemantifoldDiagnostic({code: "INVALID_APPLICATION_RESOURCE", language: "android", message})
1365
+ }
1366
+
1367
+ /**
1368
+ * Throws a stable invalid-path diagnostic.
1369
+ * @param {string} message - Diagnostic message.
1370
+ * @returns {never} Never returns.
1371
+ */
1372
+ function invalidPath(message) {
1373
+ throw new SemantifoldDiagnostic({code: "INVALID_APPLICATION_PATH", language: "android", message})
1374
+ }