@statsim/compiler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@statsim/compiler",
3
+ "version": "0.1.0",
4
+ "description": "StatSim language compiler — parse .sm models and compile to stoch, PyMC, Stan, Z3",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src/",
12
+ "wasm/"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "test": "node --experimental-vm-modules test/test.js"
19
+ },
20
+ "keywords": ["statsim", "probabilistic-programming", "bayesian", "compiler"],
21
+ "author": "Anton Zemlyansky",
22
+ "license": "MIT"
23
+ }
package/src/index.js ADDED
@@ -0,0 +1,83 @@
1
+ import { loadWasm, getWasm } from './wasm.js'
2
+ import { TfpjsRunner } from './runners/tfpjs.js'
3
+
4
+ // Ensure WASM is loaded before any operations
5
+ let initialized = false
6
+
7
+ async function ensureInit () {
8
+ if (!initialized) {
9
+ await loadWasm()
10
+ initialized = true
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Parse .sm source into an opaque block handle.
16
+ * @param {string} source - StatSim model source
17
+ * @returns {Promise<number>} block handle
18
+ */
19
+ export async function parse (source) {
20
+ await ensureInit()
21
+ const wasm = getWasm()
22
+ const h1 = wasm.parse(source)
23
+ const h2 = wasm.expand(h1)
24
+ wasm.free(h1)
25
+ return h2
26
+ }
27
+
28
+ /**
29
+ * Compile blocks to target code (script mode).
30
+ * @param {number} handle - block handle from parse()
31
+ * @param {object} opts - { target: 'tfpjs'|'pymc'|'stan'|'z3' }
32
+ * @returns {string} generated code
33
+ */
34
+ export function compileScript (handle, opts) {
35
+ const wasm = getWasm()
36
+ return wasm.compileScript(handle, opts.target)
37
+ }
38
+
39
+ /**
40
+ * Compile blocks to target code (module mode) with metadata.
41
+ * @param {number} handle - block handle from parse()
42
+ * @param {object} opts - { target: 'tfpjs'|'pymc'|'stan'|'z3' }
43
+ * @returns {{ code: string, dataNames: string[], paramNames: string[], inferenceMode: string }}
44
+ */
45
+ export function compile (handle, opts) {
46
+ const wasm = getWasm()
47
+ return wasm.compileModule(handle, opts.target)
48
+ }
49
+
50
+ /**
51
+ * Free a block handle when no longer needed.
52
+ * @param {number} handle
53
+ */
54
+ export function free (handle) {
55
+ const wasm = getWasm()
56
+ wasm.free(handle)
57
+ }
58
+
59
+ /**
60
+ * High-level API: parse + compile + return a runner.
61
+ * @param {string} source - StatSim model source
62
+ * @param {object} opts - { target: 'tfpjs'|'pymc'|'stan'|'z3' }
63
+ * @returns {Promise<TfpjsRunner>} runner with .run(), .build(), .code
64
+ */
65
+ export async function model (source, opts = { target: 'tfpjs' }) {
66
+ const handle = await parse(source)
67
+ const result = compile(handle, opts)
68
+ free(handle)
69
+
70
+ if (opts.target === 'tfpjs') {
71
+ return new TfpjsRunner(result)
72
+ }
73
+
74
+ // Generic runner for other targets — code + metadata only
75
+ return {
76
+ code: result.code,
77
+ dataNames: result.dataNames,
78
+ paramNames: result.paramNames,
79
+ inferenceMode: result.inferenceMode
80
+ }
81
+ }
82
+
83
+ export { loadWasm }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * TFP.js runner — executes compiled module code in the host environment.
3
+ *
4
+ * Supports three modes:
5
+ * runner.run(data, options) — run inference, return results
6
+ * runner.build(data) — return model object for custom inference
7
+ * runner.code — raw code string
8
+ * runner.exports — lazy build() with no data
9
+ *
10
+ * Host functions are passed alongside data — they're auto-separated:
11
+ * runner.run({ images, labels, encode: myEncoder })
12
+ */
13
+ export class TfpjsRunner {
14
+ /**
15
+ * @param {{ code: string, dataNames: string[], paramNames: string[], inferenceMode: string }} result
16
+ */
17
+ constructor (result) {
18
+ this.code = result.code
19
+ this.dataNames = result.dataNames
20
+ this.paramNames = result.paramNames
21
+ this.inferenceMode = result.inferenceMode
22
+ this._module = null
23
+ this._exports = null
24
+ }
25
+
26
+ /**
27
+ * Lazily load the generated module code.
28
+ * @returns {Promise<object>} the evaluated module exports
29
+ */
30
+ async _loadModule () {
31
+ if (this._module) return this._module
32
+ const blob = new Blob([this.code], { type: 'text/javascript' })
33
+ const url = URL.createObjectURL(blob)
34
+ try {
35
+ this._module = await import(url)
36
+ } finally {
37
+ URL.revokeObjectURL(url)
38
+ }
39
+ return this._module
40
+ }
41
+
42
+ /**
43
+ * Separate data from host functions in a merged input object.
44
+ * Functions are extracted into ext, everything else stays as data.
45
+ */
46
+ _separate (input) {
47
+ const data = {}
48
+ const ext = {}
49
+ for (const [k, v] of Object.entries(input || {})) {
50
+ if (typeof v === 'function') {
51
+ ext[k] = v
52
+ } else {
53
+ data[k] = v
54
+ }
55
+ }
56
+ return { data, ext }
57
+ }
58
+
59
+ /**
60
+ * Run inference.
61
+ * @param {object} [input={}] - data + host functions (auto-separated)
62
+ * @param {object} [options={}] - inference options (numResults, numBurninSteps, etc.)
63
+ * @returns {Promise<object>} inference results keyed by parameter name
64
+ */
65
+ async run (input = {}, options = {}) {
66
+ const mod = await this._loadModule()
67
+ const { data, ext } = this._separate(input)
68
+ if (this.inferenceMode === 'optimize') {
69
+ return mod.runOptimization(data, ext, options)
70
+ }
71
+ return mod.runInference(data, ext, options)
72
+ }
73
+
74
+ /**
75
+ * Build model without running inference.
76
+ * Returns the model object for custom extension.
77
+ * @param {object} [input={}] - data + host functions (auto-separated)
78
+ * @returns {Promise<object>} model object with targetLogProb, bijectors, etc.
79
+ */
80
+ async build (input = {}) {
81
+ const mod = await this._loadModule()
82
+ const { data, ext } = this._separate(input)
83
+ if (this.inferenceMode === 'optimize') {
84
+ return mod.createOptimizer(data, ext)
85
+ }
86
+ return mod.createModel(data, ext)
87
+ }
88
+
89
+ /**
90
+ * Lazy exports — calls build() with empty data.
91
+ * @returns {Promise<object>} model object
92
+ */
93
+ get exports () {
94
+ if (!this._exports) {
95
+ this._exports = this.build()
96
+ }
97
+ return this._exports
98
+ }
99
+ }
package/src/wasm.js ADDED
@@ -0,0 +1,16 @@
1
+ // WASM loader — loads the statsim compiler WASM module
2
+ let wasmModule = null
3
+
4
+ export async function loadWasm () {
5
+ if (wasmModule) return wasmModule
6
+
7
+ // Dynamic import of the emscripten-generated glue code
8
+ const { default: createStatsim } = await import('../wasm/statsim.js')
9
+ wasmModule = await createStatsim()
10
+ return wasmModule
11
+ }
12
+
13
+ export function getWasm () {
14
+ if (!wasmModule) throw new Error('WASM not loaded — call loadWasm() first')
15
+ return wasmModule
16
+ }