teptris 0.0.0-stage → 0.3.4
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/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/teptris.mjs +16 -0
- package/dist/teptris.wasm +0 -0
- package/package.json +32 -4
- package/src/index.d.ts +59 -0
- package/src/index.js +213 -0
- package/src/teptris_wasm.c +165 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 leptris contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
-
#
|
|
1
|
+
# teptris (JavaScript/TypeScript)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TOML for JavaScript at libteptris speed — the engine compiled to
|
|
4
|
+
WebAssembly (emscripten, ES module), no fallback.
|
|
5
|
+
|
|
6
|
+
```js
|
|
7
|
+
import { init } from "teptris";
|
|
8
|
+
|
|
9
|
+
const teptris = await init();
|
|
10
|
+
teptris.loads('a = 1'); // → { a: 1 }
|
|
11
|
+
teptris.dump(obj); // → canonical TOML string
|
|
12
|
+
teptris.dumpJson(obj); // → natural JSON string
|
|
13
|
+
teptris.engineVersion(); // → the libteptris inside
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
TypeScript ships first-class: `src/index.d.ts` types the full surface
|
|
17
|
+
(`init`, `TeptrisApi`, `TomlTable`, `TeptrisError` with
|
|
18
|
+
`line`/`column`) — strict `tsc` consumers are part of the test suite.
|
|
19
|
+
For custom instantiation (bundler-managed wasm loading), `initApi(mod)`
|
|
20
|
+
wraps an already-instantiated emscripten module.
|
|
21
|
+
|
|
22
|
+
**Surface (v1, engine 0.3.0):** `loads` uses the natural-JSON emit —
|
|
23
|
+
real numbers, booleans, RFC 3339 datetime strings, non-finite floats
|
|
24
|
+
as `null`. `dump` emits canonical TOML through the builder; `dumpJson`
|
|
25
|
+
emits the same tree as natural JSON — the string a host hands straight
|
|
26
|
+
to JSON consumers, and the engine's fastest emit mode. Errors carry
|
|
27
|
+
`message`/`line`/`column`.
|
|
28
|
+
|
|
29
|
+
## Performance (JS tier)
|
|
30
|
+
|
|
31
|
+
Best-of-40 on darwin/arm64, node 24, over the engine bench corpus
|
|
32
|
+
(`benchmark/js_tier.mjs` — runs in CI as a report-only lane):
|
|
33
|
+
|
|
34
|
+
- **vs @iarna/toml** (the ecosystem's long-standing standard): 2–4×
|
|
35
|
+
faster on loads — and it rejects two corpus shapes outright (it
|
|
36
|
+
still enforces a pre-1.0 single-type-array rule; the corpus is
|
|
37
|
+
legal TOML 1.0).
|
|
38
|
+
- **vs smol-toml** (the modern fast pure-JS parser): roughly parity
|
|
39
|
+
— teptris wins the datetime-heavy shapes (1.5×) and deep tables;
|
|
40
|
+
smol wins scalar-string-heavy inputs, where the wasm→JS bridge
|
|
41
|
+
(natural-JSON emit + `JSON.parse`) prices every string.
|
|
42
|
+
- **dumpJson vs `JSON.stringify`**: the native baseline is 3–6×
|
|
43
|
+
faster — same verdict as the ruby/python tiers. The API's value is
|
|
44
|
+
the one-pass TOML→JSON contract, not beating the platform's JSON.
|
|
45
|
+
|
|
46
|
+
The JS tier's edge is the engine's correctness: full TOML 1.1 draft
|
|
47
|
+
grammar and the same parse/emit behavior as the ruby, python and
|
|
48
|
+
rust bindings.
|
|
49
|
+
|
|
50
|
+
## Build
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
ENGINESRC=../teptris/src ./build.sh # emscripten required
|
|
54
|
+
npm test
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The bridge copies input bytes INTO the wasm heap; the engine's
|
|
58
|
+
zero-copy buffer-lifetime contract stays internal — the host never
|
|
59
|
+
holds a heap pointer past the call.
|
package/dist/teptris.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
var Module = (() => {
|
|
3
|
+
var _scriptName = import.meta.url;
|
|
4
|
+
|
|
5
|
+
return (
|
|
6
|
+
async function(moduleArg = {}) {
|
|
7
|
+
var moduleRtn;
|
|
8
|
+
|
|
9
|
+
var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof WorkerGlobalScope!="undefined";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string"&&process.type!="renderer";if(ENVIRONMENT_IS_NODE){const{createRequire}=await import("module");let dirname=import.meta.url;if(dirname.startsWith("data:")){dirname="/"}var require=createRequire(dirname)}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");if(!import.meta.url.startsWith("data:")){scriptDirectory=nodePath.dirname(require("url").fileURLToPath(import.meta.url))+"/"}readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?undefined:"utf8");return ret};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAP64,HEAPU64,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b);Module["HEAP64"]=HEAP64=new BigInt64Array(b);Module["HEAPU64"]=HEAPU64=new BigUint64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);var isFileURI=filename=>filename.startsWith("file://");function findWasmBinary(){if(Module["locateFile"]){var f="teptris.wasm";if(!isDataURI(f)){return locateFile(f)}return f}return new URL("teptris.wasm",import.meta.url).href}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){return{a:wasmImports}}async function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["c"];updateMemoryViews();addOnInit(wasmExports["d"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}wasmBinaryFile??=findWasmBinary();try{var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);receiveInstantiationResult(result);return result}catch(e){readyPromiseReject(e);return}}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var noExitRuntime=Module["noExitRuntime"]||true;var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder:undefined;var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead=NaN)=>{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";var ___assert_fail=(condition,filename,line,func)=>abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"]);var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var wasmImports={a:___assert_fail,b:_emscripten_resize_heap};var wasmExports;createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["d"])();var _teptris_wasm_load=Module["_teptris_wasm_load"]=(a0,a1)=>(_teptris_wasm_load=Module["_teptris_wasm_load"]=wasmExports["e"])(a0,a1);var _teptris_wasm_last_error=Module["_teptris_wasm_last_error"]=()=>(_teptris_wasm_last_error=Module["_teptris_wasm_last_error"]=wasmExports["f"])();var _teptris_wasm_last_error_line=Module["_teptris_wasm_last_error_line"]=()=>(_teptris_wasm_last_error_line=Module["_teptris_wasm_last_error_line"]=wasmExports["g"])();var _teptris_wasm_last_error_column=Module["_teptris_wasm_last_error_column"]=()=>(_teptris_wasm_last_error_column=Module["_teptris_wasm_last_error_column"]=wasmExports["h"])();var _teptris_wasm_json=Module["_teptris_wasm_json"]=()=>(_teptris_wasm_json=Module["_teptris_wasm_json"]=wasmExports["i"])();var _teptris_wasm_json_len=Module["_teptris_wasm_json_len"]=()=>(_teptris_wasm_json_len=Module["_teptris_wasm_json_len"]=wasmExports["j"])();var _teptris_wasm_free=Module["_teptris_wasm_free"]=a0=>(_teptris_wasm_free=Module["_teptris_wasm_free"]=wasmExports["k"])(a0);var _teptris_wasm_builder_new=Module["_teptris_wasm_builder_new"]=()=>(_teptris_wasm_builder_new=Module["_teptris_wasm_builder_new"]=wasmExports["l"])();var _teptris_wasm_builder_free=Module["_teptris_wasm_builder_free"]=a0=>(_teptris_wasm_builder_free=Module["_teptris_wasm_builder_free"]=wasmExports["m"])(a0);var _teptris_wasm_builder_put_string=Module["_teptris_wasm_builder_put_string"]=(a0,a1,a2)=>(_teptris_wasm_builder_put_string=Module["_teptris_wasm_builder_put_string"]=wasmExports["n"])(a0,a1,a2);var _teptris_wasm_builder_put_integer=Module["_teptris_wasm_builder_put_integer"]=(a0,a1,a2)=>(_teptris_wasm_builder_put_integer=Module["_teptris_wasm_builder_put_integer"]=wasmExports["o"])(a0,a1,a2);var _teptris_wasm_builder_put_float=Module["_teptris_wasm_builder_put_float"]=(a0,a1,a2)=>(_teptris_wasm_builder_put_float=Module["_teptris_wasm_builder_put_float"]=wasmExports["p"])(a0,a1,a2);var _teptris_wasm_builder_put_boolean=Module["_teptris_wasm_builder_put_boolean"]=(a0,a1,a2)=>(_teptris_wasm_builder_put_boolean=Module["_teptris_wasm_builder_put_boolean"]=wasmExports["q"])(a0,a1,a2);var _teptris_wasm_builder_put_datetime=Module["_teptris_wasm_builder_put_datetime"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)=>(_teptris_wasm_builder_put_datetime=Module["_teptris_wasm_builder_put_datetime"]=wasmExports["r"])(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10);var _teptris_wasm_builder_open_table=Module["_teptris_wasm_builder_open_table"]=(a0,a1)=>(_teptris_wasm_builder_open_table=Module["_teptris_wasm_builder_open_table"]=wasmExports["s"])(a0,a1);var _teptris_wasm_builder_open_array=Module["_teptris_wasm_builder_open_array"]=(a0,a1)=>(_teptris_wasm_builder_open_array=Module["_teptris_wasm_builder_open_array"]=wasmExports["t"])(a0,a1);var _teptris_wasm_builder_close=Module["_teptris_wasm_builder_close"]=a0=>(_teptris_wasm_builder_close=Module["_teptris_wasm_builder_close"]=wasmExports["u"])(a0);var _teptris_wasm_builder_finish=Module["_teptris_wasm_builder_finish"]=a0=>(_teptris_wasm_builder_finish=Module["_teptris_wasm_builder_finish"]=wasmExports["v"])(a0);var _teptris_wasm_builder_finish_json_natural=Module["_teptris_wasm_builder_finish_json_natural"]=a0=>(_teptris_wasm_builder_finish_json_natural=Module["_teptris_wasm_builder_finish_json_natural"]=wasmExports["w"])(a0);var _teptris_wasm_toml=Module["_teptris_wasm_toml"]=()=>(_teptris_wasm_toml=Module["_teptris_wasm_toml"]=wasmExports["x"])();var _teptris_wasm_toml_len=Module["_teptris_wasm_toml_len"]=()=>(_teptris_wasm_toml_len=Module["_teptris_wasm_toml_len"]=wasmExports["y"])();var _malloc=Module["_malloc"]=a0=>(_malloc=Module["_malloc"]=wasmExports["A"])(a0);var _teptris_version_string=Module["_teptris_version_string"]=()=>(_teptris_version_string=Module["_teptris_version_string"]=wasmExports["B"])();Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();moduleRtn=readyPromise;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
return moduleRtn;
|
|
13
|
+
}
|
|
14
|
+
);
|
|
15
|
+
})();
|
|
16
|
+
export default Module;
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teptris",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.4",
|
|
4
|
+
"description": "TOML for JavaScript/TypeScript at libteptris speed (WebAssembly, no fallback) - version tracks the engine ({engine}.{iteration}; npm semver is 3-part)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./src/index.d.ts",
|
|
10
|
+
"default": "./src/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "./build.sh",
|
|
19
|
+
"test": "node --test test/test.mjs test/package.test.mjs"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/leptris/teptris-ts.git"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
29
|
+
"types": "src/index.d.ts",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@iarna/toml": "^2.2.5",
|
|
32
|
+
"smol-toml": "^1.9.1"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* teptris — TOML 1.1 for JavaScript/TypeScript at libteptris
|
|
3
|
+
* speed (WebAssembly, no fallback).
|
|
4
|
+
*
|
|
5
|
+
* const teptris = await init();
|
|
6
|
+
* const doc = teptris.loads('title = "demo"');
|
|
7
|
+
* const toml = teptris.dump(doc);
|
|
8
|
+
* const json = teptris.dumpJson(doc);
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** A parsed TOML value: datetimes are RFC 3339 strings, non-finite
|
|
12
|
+
* floats (nan/inf) are null. */
|
|
13
|
+
export type TomlValue =
|
|
14
|
+
| string
|
|
15
|
+
| number
|
|
16
|
+
| boolean
|
|
17
|
+
| null
|
|
18
|
+
| TomlTable
|
|
19
|
+
| TomlValue[];
|
|
20
|
+
|
|
21
|
+
/** A TOML table: string keys, insertion-ordered. */
|
|
22
|
+
export interface TomlTable {
|
|
23
|
+
[key: string]: TomlValue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Parse errors carry the engine's line/column (1-based). */
|
|
27
|
+
export interface TeptrisError extends Error {
|
|
28
|
+
line: number;
|
|
29
|
+
column: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface TeptrisApi {
|
|
33
|
+
/** Parse TOML into a JS object (natural-JSON view: real numbers,
|
|
34
|
+
* booleans, RFC 3339 datetime strings, non-finite floats as
|
|
35
|
+
* null). Throws TeptrisError on invalid input. */
|
|
36
|
+
loads(toml: string): TomlTable;
|
|
37
|
+
|
|
38
|
+
/** Serialize a table to canonical TOML via the engine builder.
|
|
39
|
+
* RFC 3339 strings re-enter as datetimes. Throws TypeError for
|
|
40
|
+
* non-table roots, Error for unsupported values. */
|
|
41
|
+
dump(value: TomlTable): string;
|
|
42
|
+
|
|
43
|
+
/** Serialize a table to natural JSON (the engine's fastest emit
|
|
44
|
+
* mode): real numbers, booleans, RFC 3339 datetime strings,
|
|
45
|
+
* non-finite floats as null. */
|
|
46
|
+
dumpJson(value: TomlTable): string;
|
|
47
|
+
|
|
48
|
+
/** The libteptris engine version inside this build. */
|
|
49
|
+
engineVersion(): string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Instantiate the WebAssembly engine and return the API. */
|
|
53
|
+
export function init(): Promise<TeptrisApi>;
|
|
54
|
+
|
|
55
|
+
/** Advanced: wrap an already-instantiated engine module (custom
|
|
56
|
+
* instantiation, browser bundlers with special wasm loading). */
|
|
57
|
+
export function initApi(mod: unknown): TeptrisApi;
|
|
58
|
+
|
|
59
|
+
export default init;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// The public surface: natural-JSON loads + builder-driven dumps.
|
|
2
|
+
// Engine FFI through ../dist/teptris.mjs (built by build.sh).
|
|
3
|
+
|
|
4
|
+
const KIND_OFFSET = 4, KIND_LOCAL_DT = 5, KIND_DATE = 6, KIND_TIME = 7;
|
|
5
|
+
|
|
6
|
+
function cstr(mod, s) {
|
|
7
|
+
const bytes = mod.lengthBytesUTF8(s) + 1;
|
|
8
|
+
const ptr = mod._malloc(bytes);
|
|
9
|
+
mod.stringToUTF8(s, ptr, bytes);
|
|
10
|
+
return ptr;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function freeCstr(mod, ptr) { mod._teptris_wasm_free(ptr); }
|
|
14
|
+
|
|
15
|
+
// RFC 3339-ish (the four TOML shapes) back into builder fields
|
|
16
|
+
function parseDateTime(str) {
|
|
17
|
+
let m = str.match(
|
|
18
|
+
/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[+-]\d{2}:\d{2})?$/);
|
|
19
|
+
if (m) {
|
|
20
|
+
const kind = m[8] ? KIND_OFFSET : KIND_LOCAL_DT;
|
|
21
|
+
let offset = 0;
|
|
22
|
+
if (m[8] && m[8] !== "Z") {
|
|
23
|
+
const sign = m[8][0] === "-" ? -1 : 1;
|
|
24
|
+
const hh = parseInt(m[8].slice(1, 3), 10);
|
|
25
|
+
const mm = parseInt(m[8].slice(4, 6), 10);
|
|
26
|
+
offset = sign * (hh * 3600 + mm * 60);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
kind, year: +m[1], month: +m[2], day: +m[3], hour: +m[4],
|
|
30
|
+
minute: +m[5], second: +m[6],
|
|
31
|
+
nanosecond: m[7] ? Math.floor(+("0." + m[7]) * 1e9) : 0,
|
|
32
|
+
offset_seconds: offset,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
m = str.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
36
|
+
if (m) {
|
|
37
|
+
return { kind: KIND_DATE, year: +m[1], month: +m[2], day: +m[3],
|
|
38
|
+
hour: 0, minute: 0, second: 0, nanosecond: 0,
|
|
39
|
+
offset_seconds: 0 };
|
|
40
|
+
}
|
|
41
|
+
m = str.match(/^(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?$/);
|
|
42
|
+
if (m) {
|
|
43
|
+
return { kind: KIND_TIME, year: 0, month: 0, day: 0, hour: +m[1],
|
|
44
|
+
minute: +m[2], second: +m[3],
|
|
45
|
+
nanosecond: m[4] ? Math.floor(+("0." + m[4]) * 1e9) : 0,
|
|
46
|
+
offset_seconds: 0 };
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const RFC3339 =
|
|
52
|
+
/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$|^\d{2}:\d{2}:\d{2}(\.\d+)?$/;
|
|
53
|
+
|
|
54
|
+
function buildValue(mod, b, key, v, errs) {
|
|
55
|
+
const kp = key == null ? 0 : cstr(mod, key);
|
|
56
|
+
try {
|
|
57
|
+
if (v === null || v === undefined) {
|
|
58
|
+
throw new Error("null/undefined cannot round-trip through TOML");
|
|
59
|
+
}
|
|
60
|
+
switch (typeof v) {
|
|
61
|
+
case "number":
|
|
62
|
+
if (Number.isInteger(v)) {
|
|
63
|
+
mod._teptris_wasm_builder_put_integer(b, kp, BigInt(v));
|
|
64
|
+
} else {
|
|
65
|
+
mod._teptris_wasm_builder_put_float(b, kp, v);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
case "boolean":
|
|
69
|
+
mod._teptris_wasm_builder_put_boolean(b, kp, v ? 1 : 0);
|
|
70
|
+
return;
|
|
71
|
+
case "string":
|
|
72
|
+
// RFC 3339 strings from loads() re-enter as datetimes; a
|
|
73
|
+
// plain string stays a string
|
|
74
|
+
if (RFC3339.test(v)) {
|
|
75
|
+
const dt = parseDateTime(v);
|
|
76
|
+
mod._teptris_wasm_builder_put_datetime(
|
|
77
|
+
b, kp, dt.kind, dt.year, dt.month, dt.day, dt.hour,
|
|
78
|
+
dt.minute, dt.second, dt.nanosecond, dt.offset_seconds);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const vp = cstr(mod, v);
|
|
82
|
+
mod._teptris_wasm_builder_put_string(b, kp, vp);
|
|
83
|
+
freeCstr(mod, vp);
|
|
84
|
+
return;
|
|
85
|
+
default:
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(v)) {
|
|
89
|
+
mod._teptris_wasm_builder_open_array(b, kp);
|
|
90
|
+
for (const item of v) buildValue(mod, b, null, item, errs);
|
|
91
|
+
mod._teptris_wasm_builder_close(b);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (typeof v === "object") {
|
|
95
|
+
// open_table with a NULL key = element of the current array
|
|
96
|
+
// (array-of-tables); the root table itself is implicit (see
|
|
97
|
+
// dump - it iterates root entries directly)
|
|
98
|
+
mod._teptris_wasm_builder_open_table(b, kp);
|
|
99
|
+
for (const [k, item] of Object.entries(v)) {
|
|
100
|
+
buildValue(mod, b, k, item, errs);
|
|
101
|
+
}
|
|
102
|
+
mod._teptris_wasm_builder_close(b);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
errs.push(new Error(`unsupported value: ${String(v)}`));
|
|
106
|
+
} finally {
|
|
107
|
+
if (kp) freeCstr(mod, kp);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
import createModule from "../dist/teptris.mjs";
|
|
112
|
+
|
|
113
|
+
export function initApi(mod) {
|
|
114
|
+
const { UTF8ToString, stringToUTF8, lengthBytesUTF8 } = mod;
|
|
115
|
+
|
|
116
|
+
function loads(toml) {
|
|
117
|
+
if (typeof toml !== "string") {
|
|
118
|
+
throw new TypeError("loads expects a string");
|
|
119
|
+
}
|
|
120
|
+
const ptr = cstr(mod, toml);
|
|
121
|
+
const st = mod._teptris_wasm_load(ptr, lengthBytesUTF8(toml));
|
|
122
|
+
let value = null, error = null;
|
|
123
|
+
if (st === 0) {
|
|
124
|
+
const jptr = mod._teptris_wasm_json();
|
|
125
|
+
value = JSON.parse(UTF8ToString(jptr));
|
|
126
|
+
mod._teptris_wasm_free(jptr);
|
|
127
|
+
} else {
|
|
128
|
+
error = {
|
|
129
|
+
message: UTF8ToString(mod._teptris_wasm_last_error()),
|
|
130
|
+
line: mod._teptris_wasm_last_error_line(),
|
|
131
|
+
column: mod._teptris_wasm_last_error_column(),
|
|
132
|
+
status: st,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
freeCstr(mod, ptr);
|
|
136
|
+
if (error) {
|
|
137
|
+
const e = new Error(
|
|
138
|
+
`${error.message} (line ${error.line}, column ${error.column})`);
|
|
139
|
+
e.line = error.line;
|
|
140
|
+
e.column = error.column;
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildTree(obj) {
|
|
147
|
+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
148
|
+
throw new TypeError("dump expects a table object (TOML roots are tables)");
|
|
149
|
+
}
|
|
150
|
+
const b = mod._teptris_wasm_builder_new();
|
|
151
|
+
const errs = [];
|
|
152
|
+
// the root table is implicit: entries attach directly
|
|
153
|
+
for (const [k, item] of Object.entries(obj)) {
|
|
154
|
+
buildValue(mod, b, k, item, errs);
|
|
155
|
+
}
|
|
156
|
+
if (errs.length) {
|
|
157
|
+
mod._teptris_wasm_builder_free(b);
|
|
158
|
+
throw errs[0];
|
|
159
|
+
}
|
|
160
|
+
return b;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function readOut(ptr) {
|
|
164
|
+
const out = UTF8ToString(ptr);
|
|
165
|
+
mod._teptris_wasm_free(ptr);
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function dump(obj) {
|
|
170
|
+
const b = buildTree(obj);
|
|
171
|
+
try {
|
|
172
|
+
const st = mod._teptris_wasm_builder_finish(b);
|
|
173
|
+
if (st !== 0) {
|
|
174
|
+
throw new Error(`dump failed (status ${st})`);
|
|
175
|
+
}
|
|
176
|
+
return readOut(mod._teptris_wasm_toml());
|
|
177
|
+
} finally {
|
|
178
|
+
mod._teptris_wasm_builder_free(b);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Natural JSON (engine 0.3.0): real numbers, booleans, RFC 3339
|
|
183
|
+
// datetime strings; non-finite floats become null. The string a
|
|
184
|
+
// host can hand straight to JSON consumers.
|
|
185
|
+
function dumpJson(obj) {
|
|
186
|
+
const b = buildTree(obj);
|
|
187
|
+
try {
|
|
188
|
+
const st = mod._teptris_wasm_builder_finish_json_natural(b);
|
|
189
|
+
if (st !== 0) {
|
|
190
|
+
throw new Error(`dumpJson failed (status ${st})`);
|
|
191
|
+
}
|
|
192
|
+
return readOut(mod._teptris_wasm_toml());
|
|
193
|
+
} finally {
|
|
194
|
+
mod._teptris_wasm_builder_free(b);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function engineVersion() {
|
|
199
|
+
return UTF8ToString(mod._teptris_version_string());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { loads, dump, dumpJson, engineVersion };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The consumer entry: instantiate the engine once, get the API.
|
|
206
|
+
// const teptris = await init();
|
|
207
|
+
// teptris.loads("a = 1");
|
|
208
|
+
export async function init() {
|
|
209
|
+
const mod = await createModule();
|
|
210
|
+
return initApi(mod);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export default init;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/* The wasm surface: one contained bridge over the zero-copy core.
|
|
2
|
+
* Input bytes are copied INTO the wasm heap; the document's views live
|
|
3
|
+
* inside the heap for the duration of the call; the JSON answer leaves
|
|
4
|
+
* before the document is freed. The host never holds a heap pointer
|
|
5
|
+
* past the call - the C buffer-lifetime contract stays internal. */
|
|
6
|
+
#include <emscripten/emscripten.h>
|
|
7
|
+
#include <stdio.h>
|
|
8
|
+
#include <stdlib.h>
|
|
9
|
+
#include <string.h>
|
|
10
|
+
|
|
11
|
+
#include "teptris/teptris.h"
|
|
12
|
+
|
|
13
|
+
static char *g_json;
|
|
14
|
+
static size_t g_json_len;
|
|
15
|
+
|
|
16
|
+
static char last_error[256];
|
|
17
|
+
static int last_error_line;
|
|
18
|
+
static int last_error_column;
|
|
19
|
+
|
|
20
|
+
EMSCRIPTEN_KEEPALIVE
|
|
21
|
+
int teptris_wasm_load(const char *data, size_t len) {
|
|
22
|
+
teptris_document *doc = NULL;
|
|
23
|
+
teptris_status st = teptris_parse(data, len, NULL, &doc);
|
|
24
|
+
if (st != TEPTRIS_OK || doc == NULL) {
|
|
25
|
+
if (doc != NULL) {
|
|
26
|
+
/* read the error BEFORE the free - it points into the doc */
|
|
27
|
+
const teptris_error *e = teptris_document_error(doc);
|
|
28
|
+
snprintf(last_error, sizeof last_error, "%s", e->message);
|
|
29
|
+
last_error_line = (int)e->line;
|
|
30
|
+
last_error_column = (int)e->column;
|
|
31
|
+
teptris_document_free(doc);
|
|
32
|
+
} else {
|
|
33
|
+
snprintf(last_error, sizeof last_error, "parse failed");
|
|
34
|
+
last_error_line = 0;
|
|
35
|
+
last_error_column = 0;
|
|
36
|
+
}
|
|
37
|
+
return (int)st;
|
|
38
|
+
}
|
|
39
|
+
st = teptris_document_emit_json_natural(doc, &g_json, &g_json_len);
|
|
40
|
+
teptris_document_free(doc);
|
|
41
|
+
return (int)st;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
EMSCRIPTEN_KEEPALIVE
|
|
45
|
+
const char *teptris_wasm_last_error(void) { return last_error; }
|
|
46
|
+
|
|
47
|
+
EMSCRIPTEN_KEEPALIVE
|
|
48
|
+
int teptris_wasm_last_error_line(void) { return last_error_line; }
|
|
49
|
+
|
|
50
|
+
EMSCRIPTEN_KEEPALIVE
|
|
51
|
+
int teptris_wasm_last_error_column(void) { return last_error_column; }
|
|
52
|
+
|
|
53
|
+
EMSCRIPTEN_KEEPALIVE
|
|
54
|
+
const char *teptris_wasm_json(void) { return g_json; }
|
|
55
|
+
|
|
56
|
+
EMSCRIPTEN_KEEPALIVE
|
|
57
|
+
int teptris_wasm_json_len(void) { return (int)g_json_len; }
|
|
58
|
+
|
|
59
|
+
EMSCRIPTEN_KEEPALIVE
|
|
60
|
+
void teptris_wasm_free(void *p) { free(p); }
|
|
61
|
+
|
|
62
|
+
/* ---- builder surface (the dump side) ----------------------------- *
|
|
63
|
+
* 1:1 with teptris_builder_*: the JS wrapper drives the same stack
|
|
64
|
+
* discipline (open/close, NULL key = array element) the C and Rust
|
|
65
|
+
* bindings use, so the emitter stays the single formatting truth. */
|
|
66
|
+
|
|
67
|
+
EMSCRIPTEN_KEEPALIVE
|
|
68
|
+
void *teptris_wasm_builder_new(void) { return teptris_builder_new(); }
|
|
69
|
+
|
|
70
|
+
EMSCRIPTEN_KEEPALIVE
|
|
71
|
+
void teptris_wasm_builder_free(void *b) { teptris_builder_free(b); }
|
|
72
|
+
|
|
73
|
+
EMSCRIPTEN_KEEPALIVE
|
|
74
|
+
int teptris_wasm_builder_put_string(void *b, const char *key,
|
|
75
|
+
const char *val) {
|
|
76
|
+
return teptris_builder_put_string(b, key, key ? strlen(key) : 0,
|
|
77
|
+
val, strlen(val));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
EMSCRIPTEN_KEEPALIVE
|
|
81
|
+
int teptris_wasm_builder_put_integer(void *b, const char *key,
|
|
82
|
+
int64_t v) {
|
|
83
|
+
return teptris_builder_put_integer(b, key, key ? strlen(key) : 0, v);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
EMSCRIPTEN_KEEPALIVE
|
|
87
|
+
int teptris_wasm_builder_put_float(void *b, const char *key, double v) {
|
|
88
|
+
return teptris_builder_put_float(b, key, key ? strlen(key) : 0, v);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
EMSCRIPTEN_KEEPALIVE
|
|
92
|
+
int teptris_wasm_builder_put_boolean(void *b, const char *key, int v) {
|
|
93
|
+
return teptris_builder_put_boolean(b, key, key ? strlen(key) : 0,
|
|
94
|
+
v != 0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
EMSCRIPTEN_KEEPALIVE
|
|
98
|
+
int teptris_wasm_builder_put_datetime(void *b, const char *key, int kind,
|
|
99
|
+
int32_t year, uint8_t month,
|
|
100
|
+
uint8_t day, uint8_t hour,
|
|
101
|
+
uint8_t minute, uint8_t second,
|
|
102
|
+
uint32_t nanosecond,
|
|
103
|
+
int32_t offset_seconds) {
|
|
104
|
+
teptris_datetime dt = {year, month, day, hour, minute, second,
|
|
105
|
+
nanosecond, offset_seconds};
|
|
106
|
+
return teptris_builder_put_datetime(
|
|
107
|
+
b, key, key ? strlen(key) : 0, (teptris_kind)kind, &dt);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
EMSCRIPTEN_KEEPALIVE
|
|
111
|
+
int teptris_wasm_builder_open_table(void *b, const char *key) {
|
|
112
|
+
return teptris_builder_open_table(b, key, key ? strlen(key) : 0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
EMSCRIPTEN_KEEPALIVE
|
|
116
|
+
int teptris_wasm_builder_open_array(void *b, const char *key) {
|
|
117
|
+
return teptris_builder_open_array(b, key, key ? strlen(key) : 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
EMSCRIPTEN_KEEPALIVE
|
|
121
|
+
int teptris_wasm_builder_close(void *b) {
|
|
122
|
+
return teptris_builder_close(b);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* finish -> emit into a global buffer (same accessor pattern as the
|
|
126
|
+
* parse-side JSON): the dump path offers canonical TOML and the
|
|
127
|
+
* host-facing natural JSON (engine 0.3.0). */
|
|
128
|
+
static char *g_toml;
|
|
129
|
+
static size_t g_toml_len;
|
|
130
|
+
|
|
131
|
+
static int finish_with(void *b,
|
|
132
|
+
teptris_status (*emit)(const teptris_document *,
|
|
133
|
+
char **, size_t *)) {
|
|
134
|
+
teptris_document *doc = NULL;
|
|
135
|
+
teptris_status st = teptris_builder_finish(b, &doc);
|
|
136
|
+
if (st != TEPTRIS_OK) {
|
|
137
|
+
return (int)st;
|
|
138
|
+
}
|
|
139
|
+
char *buf = NULL;
|
|
140
|
+
size_t len = 0;
|
|
141
|
+
st = emit(doc, &buf, &len);
|
|
142
|
+
teptris_document_free(doc);
|
|
143
|
+
if (st != TEPTRIS_OK) {
|
|
144
|
+
return (int)st;
|
|
145
|
+
}
|
|
146
|
+
g_toml = buf;
|
|
147
|
+
g_toml_len = len;
|
|
148
|
+
return TEPTRIS_OK;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
EMSCRIPTEN_KEEPALIVE
|
|
152
|
+
int teptris_wasm_builder_finish(void *b) {
|
|
153
|
+
return finish_with(b, teptris_document_emit);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
EMSCRIPTEN_KEEPALIVE
|
|
157
|
+
int teptris_wasm_builder_finish_json_natural(void *b) {
|
|
158
|
+
return finish_with(b, teptris_document_emit_json_natural);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
EMSCRIPTEN_KEEPALIVE
|
|
162
|
+
const char *teptris_wasm_toml(void) { return g_toml; }
|
|
163
|
+
|
|
164
|
+
EMSCRIPTEN_KEEPALIVE
|
|
165
|
+
int teptris_wasm_toml_len(void) { return (int)g_toml_len; }
|