diff --git a/docz/data/engines.xls b/docz/data/engines.xls index 4727094..dd4ff14 100644 --- a/docz/data/engines.xls +++ b/docz/data/engines.xls @@ -244,7 +244,7 @@ </WorksheetOptions> </Worksheet> <Worksheet ss:Name="Bindings"> - <Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="16" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16"> + <Table ss:ExpandedColumnCount="8" ss:ExpandedRowCount="17" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="65" ss:DefaultRowHeight="16"> <Column ss:Index="3" ss:Width="24"/> <Column ss:Width="31"/> <Column ss:Width="24"/> @@ -357,6 +357,16 @@ <Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell> <Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell> </Row> + <Row> + <Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/jsc#rust"><Data ss:Type="String">JSC</Data></Cell> + <Cell><Data ss:Type="String">Rust</Data></Cell> + <Cell ss:StyleID="s16"/> + <Cell ss:StyleID="s16"><Data ss:Type="String">✔</Data></Cell> + <Cell ss:StyleID="s16"/> + <Cell ss:StyleID="s16"/> + <Cell ss:StyleID="s16"/> + <Cell ss:StyleID="s16"/> + </Row> <Row> <Cell ss:StyleID="s20" ss:HRef="/docs/demos/engines/rb#complete-example"><Data ss:Type="String">ExecJS</Data></Cell> <Cell><Data ss:Type="String">Ruby</Data></Cell> diff --git a/docz/docs/02-getting-started/02-examples/06-loader.md b/docz/docs/02-getting-started/02-examples/06-loader.md index 25ae310..ec8a955 100644 --- a/docz/docs/02-getting-started/02-examples/06-loader.md +++ b/docz/docs/02-getting-started/02-examples/06-loader.md @@ -49,35 +49,31 @@ SheetJS users have verified this demo in other configurations: <details> <summary><b>Other tested configurations</b> (click to show)</summary> -| Platform | Architecture | Demo | -|:------------------------------------------------------------------|:-------------|:------------| -| NVIDIA L40 (48 GB VRAM) + i9-13900K (32 GB RAM) | `linux-x64` | LangChainJS | -| NVIDIA RTX 4080 SUPER (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS | -| NVIDIA RTX 4070 Ti (12 GB VRAM) + Ryzen 7 5800x (64 GB RAM) | `win11-x64` | LangChainJS | -| NVIDIA RTX 4060 (8 GB VRAM) + Ryzen 7 5700g (32 GB RAM) | `win11-x64` | LangChainJS | -| NVIDIA RTX 3090 (24 GB VRAM) + Ryzen 9 3900XT (128 GB RAM) | `win11-x64` | LangChainJS | -| NVIDIA RTX 3080 (12 GB VRAM) + Ryzen 7 5800X (32 GB RAM) | `win11-x64` | LangChainJS | -| NVIDIA RTX 3070 (8 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS | -| NVIDIA RTX 3060 (12 GB VRAM) + i5-11400 (32 GB RAM) | `win10-x64` | LangChainJS | -| NVIDIA RTX 2080 (12 GB VRAM) + i7-9700K (16 GB RAM) | `win10-x64` | LangChainJS | -| NVIDIA RTX 2070 (8 GB VRAM) + Ryzen 7 3700x (80 GB RAM) | `linux-x64` | LangChainJS | -| NVIDIA RTX 2060 (6 GB VRAM) + Ryzen 5 3600 (32 GB RAM) | `win10-x64` | LangChainJS | -| NVIDIA GTX 1080 (8 GB VRAM) + Ryzen 7 5800x (64 GB RAM) | `win10-x64` | LangChainJS | -| NVIDIA GTX 1070 (8 GB VRAM) + Ryzen 7 7700x (32 GB RAM) | `win11-x64` | LangChainJS | -| AMD RX 6800 XT (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS | -| Apple M4 10-Core CPU + 10-Core GPU (24 GB unified memory) | `darwin-arm` | LangChainJS | +| Platform | Architecture | Demo | +|:---------------------------------------------------------------------|:-------------|:------------| +| NVIDIA L40 (48 GB VRAM) + i9-13900K (32 GB RAM) | `linux-x64` | LangChainJS | +| NVIDIA RTX 4080 SUPER (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS | +| NVIDIA RTX 4070 Ti SUPER (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS | +| NVIDIA RTX 4070 Ti (12 GB VRAM) + Ryzen 7 5800x (64 GB RAM) | `win11-x64` | LangChainJS | +| NVIDIA RTX 4060 (8 GB VRAM) + Ryzen 7 5700g (32 GB RAM) | `win11-x64` | LangChainJS | +| NVIDIA RTX 3090 (24 GB VRAM) + Ryzen 9 3900XT (128 GB RAM) | `win11-x64` | LangChainJS | +| NVIDIA RTX 3080 (12 GB VRAM) + Ryzen 7 5800X (32 GB RAM) | `win11-x64` | LangChainJS | +| NVIDIA RTX 3070 (8 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS | +| NVIDIA RTX 3060 (12 GB VRAM) + i5-11400 (32 GB RAM) | `win10-x64` | LangChainJS | +| NVIDIA RTX 2080 (12 GB VRAM) + i7-9700K (16 GB RAM) | `win10-x64` | LangChainJS | +| NVIDIA RTX 2070 (8 GB VRAM) + Ryzen 7 3700x (80 GB RAM) | `linux-x64` | LangChainJS | +| NVIDIA RTX 2060 (6 GB VRAM) + Ryzen 5 3600 (32 GB RAM) | `win10-x64` | LangChainJS | +| NVIDIA GTX 1080 (8 GB VRAM) + Ryzen 7 5800x (64 GB RAM) | `win10-x64` | LangChainJS | +| NVIDIA GTX 1070 (8 GB VRAM) + Ryzen 7 7700x (32 GB RAM) | `win11-x64` | LangChainJS | +| AMD RX 6800 XT (16 GB VRAM) + Ryzen Z1 Extreme (16 GB RAM) | `win11-x64` | LangChainJS | +| Apple M4 10-Core CPU + 10-Core GPU (24 GB unified memory) | `darwin-arm` | LangChainJS | </details> -Special thanks to: +Special thanks to the following users for testing with multiple configurations: - [Asadbek Karimov](https://asadk.dev/) -- [Nathan Ayers](https://infocommww.org/) - [Rasmus Tengstedt](https://tengstedt.dev/) -- [Triston Armstrong](https://tristonarmstrong.com/) -- [Navid Nami](https://navidnami.com/) -- [Andreas Karydopoulos](https://smor.dev/) -- [Tim Brugman](https://timbrugman.com/) ::: diff --git a/docz/docs/03-demos/01-math/01-summary.md b/docz/docs/03-demos/01-math/01-summary.md index a6d302c..96e49bb 100644 --- a/docz/docs/03-demos/01-math/01-summary.md +++ b/docz/docs/03-demos/01-math/01-summary.md @@ -514,10 +514,11 @@ The van Reeken array mean can be implemented in one line of JavaScript code: for(var n = 1, mean = 0; n <= x.length; ++n) mean += (x[n-1] - mean)/n; ``` -<details> - <summary><b>Math details</b> (click to show)</summary> +<details open> + <summary><b>Math details</b> (click to hide)</summary> -Let $M[x;m] = \frac{1}{m}\sum_{i=1}^{m}x_m$ be the mean of the first $m$ elements. Then: +Let $M[x;m] = \frac{1}{m}\sum_{i=1}^{m}x_i$ be the mean of the first $m$ elements +(where $m > 0$). The mean of the first $m+1$ elements follows a simple relation: <table style={bs}> <tbody style={bs}> @@ -537,6 +538,14 @@ $= \frac{1}{m+1}\sum_{i=1}^{m+1} x_i$ <td style={bs}> </td> <td style={bs}> +$= \frac{1}{m+1}((\sum_{i=1}^{m} x_i) + x_{m+1})$ + + </td> +</tr> +<tr style={bs}> + <td style={bs}> </td> + <td style={bs}> + $= \frac{1}{m+1}\sum_{i=1}^{m} x_i + \frac{x_{m+1}}{m+1}$ </td> @@ -574,39 +583,114 @@ $= M[x;m] + \frac{x_{m+1}}{m+1} - \frac{1}{m+1}M[x;m]$ </td> </tr> <tr style={bs}> - <td style={bs}> </td> + <td style={bs}> + +$M[x;m+1]$ + + </td> <td style={bs}> $= M[x;m] + \frac{1}{m+1}(x_{m+1}-M[x;m])$ </td> </tr> -<tr style={bs}> - <td style={bs}> - -$new\_mean$ - - </td> - <td style={bs}> - -$= old\_mean + (x_{m+1}-old\_mean) / (m+1)$ - - </td> -</tr> </tbody> </table> -Switching to zero-based indexing, the relation matches the following expression: +--- + +The mean of the first element is $M[x;1] = \frac{1}{1}\sum_{i=1}^{1}x_i = x_1$. +By defining $M[x;0]=0$, the recurrence relation also holds for $m = 0$: + +<table style={bs}> + <tbody style={bs}> +<tr style={bs}> + <td style={bs}> + +$x_1 = 0 + (x_1 - 0)$ + + </td> + <td style={bs}> + +$= 0 + \frac{1}{1}(x_1 - 0)$ + + </td> +</tr> +<tr style={bs}> + <td style={bs}> + +$M[x;1]$ + + </td> + <td style={bs}> + +$= M[x;0] + \frac{1}{1}(x_{1} - M[x;0])$ + + </td> +</tr> +<tr style={bs}> + <td style={bs}> + +$M[x;m+1]$ + + </td> + <td style={bs}> + +$= M[x;m] + \frac{1}{m+1}(x_{m+1} - M[x;m])$ + + </td> +</tr> + + </tbody> +</table> + +$m = 0, M[x;0] = 0$ will serve as the initial condition for the recurrence. + +--- + +JavaScript is zero-indexed: $x_1$ is `x[0]` and generally $x_{m}$ is `x[m-1]`. + +Adjusting for zero-based indexing, the following code calculates the recurrence: ```js -new_mean = old_mean + (x[m] - old_mean) / (m + 1); +/* data array */ +var x = [ /* ... data ... */ ]; + +/* initial condition M[x;0] = 0 */ +var mean = 0; + +/* loop through each value in the array */ +for(var m = 0; m < x.length; ++m) { + /* store the old mean M[x;m] */ + var mean_m = mean; + /* get the next data point x_{m+1} */ + var x_m1 = x[m]; + /* calculate the new mean M[x;m+1] */ + var mean_m1 = mean_m + (x_m1 - mean_m) / (m + 1); + /* update the mean */ + mean = mean_m1; +} ``` -This update can be succinctly implemented in JavaScript: +This can be succinctly implemented by assigning to `mean` directly: ```js -mean += (x[m] - mean) / (m + 1); +for(var m = 0, mean = 0; m < x.length; ++m) { + mean = mean + (x[m] - mean) / (m + 1); +} +``` + +This can be condensed further by using the addition assignment (`+=`) operator: + +```js +for(var m = 0, mean = 0; m < x.length; ++m) mean += (x[m] - mean) / (m + 1); +``` + +Replacing `n = m+1` yields the final code expression: + +```js +for(var n = 1, mean = 0; n <= x.length; ++n) mean += (x[n-1] - mean) / n; ``` </details> diff --git a/docz/docs/03-demos/17-mobile/07-lynx.md b/docz/docs/03-demos/17-mobile/07-lynx.md index 2c6feb2..889d9f7 100644 --- a/docz/docs/03-demos/17-mobile/07-lynx.md +++ b/docz/docs/03-demos/17-mobile/07-lynx.md @@ -257,7 +257,7 @@ curl -o ./src/App.tsx https://docs.sheetjs.com/lynx/App.tsx ```bash curl -o ./src/App.css https://docs.sheetjs.com/lynx/App.css ``` -<a id="step5"></a> +<a name="step5"></a> 5) Start the development server: diff --git a/docz/docs/03-demos/42-engines/04-jsc.md b/docz/docs/03-demos/42-engines/04-jsc.md index de4003a..07cd0b2 100644 --- a/docz/docs/03-demos/42-engines/04-jsc.md +++ b/docz/docs/03-demos/42-engines/04-jsc.md @@ -748,6 +748,155 @@ swiftc -Xcc -I$(pwd) -Xlinker -L../WebKit-WebKit-7620.2.4.111.7/WebKitBuild/JSCO If successful, a CSV will be printed to console. The program also tries to write to `SheetJSwift.xlsx`, which can be opened in a spreadsheet editor. +## Bindings + +It is straightforward to load the platform-native (macOS) or compiled libraries +in programs built in other programming languages. + +### Rust + +Writing bindings is fairly mechanical. For example, the following C code +creates a string within the engine from a UTF8 string: + +```c +JSStringRef script = JSStringCreateWithUTF8CString("SheetJS"); +``` + +**Rust Types** + +`JSStringRef` is a pointer to an opaque data type. The spiritual equivalent +according to the Rustonomicon is a pointer to an opaque struct[^9]: + +```rust +#[repr(C)] +pub struct JSString { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +// highlight-next-line +type JSStringRef = *mut JSContext; +``` + +**Function Declaration** + +The `JSStringCreateWithUTF8CString` function in declared in C as follows: + +```c +JSStringRef JSStringCreateWithUTF8CString(const char * string); +``` + +The equivalent Rust declaration must be defined in an `extern "C"` block: + +```rust +unsafe extern "C" { + pub unsafe fn JSStringCreateWithUTF8CString(string: *const u8) -> JSStringRef; +} +``` + +**Rust Interchange** + +The `std::ffi` module includes a number of helpers for passing data between Rust +code and C libraries. + +Rust string literals are commonly represented as `&str` types: + +```rust +let script: &str = "'Sheet' + 'JS'"; // 'Sheet' + 'JS' +``` + +The following unsafe waltz creates a compatible pointer: + +A) Convert the `&str` to a byte slice + +B) Create a `std::ffi::CString` instance from the bytes + +C) Use the `as_ptr` method to generate a pointer + +```rust title="Generate a JSString from a Rust string literal" +/* start with a string literal */ +let script: &str = "'Sheet' + 'JS'"; + +/* generate CString */ +let cstr: std::ffi::CString = std::ffi::CString::new(script.as_bytes()).unwrap(); + +/* call JSStringCreateWithUTF8CString */ +let ref: JSStringRef = JSStringCreateWithUTF8CString(cstr.as_ptr() as *const u8); +``` + +The demo makes a safe wrapper to perform the unsafe waltz in one line: + +```rust +pub struct JSC; +impl JSC { + pub fn JSStringCreateWithUTF8CString(str: &str) -> JSStringRef { unsafe { + // highlight-next-line + JSStringCreateWithUTF8CString(std::ffi::CString::new(str.as_bytes()).unwrap().as_ptr() as *const u8) + } } +} +``` + +#### Rust Example + +:::note Tested Deployments + +This demo was last tested in the following deployments: + +| Architecture | Date | +|:-------------|:-----------| +| `darwin-arm` | 2025-03-30 | + +::: + +1) Create a new project: + +```bash +cargo new sheetjs-jsc +cd sheetjs-jsc +cargo run +``` + +2) Download the SheetJS Standalone script to the `src` folder in the project: + +<ul> +<li><a href={`https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`}>xlsx.full.min.js</a></li> +</ul> + +<CodeBlock language="bash">{`\ +curl -L -o src/xlsx.full.min.js https://cdn.sheetjs.com/xlsx-${current}/package/dist/xlsx.full.min.js`} +</CodeBlock> + +3) Download the test file to the project directory: + +<ul> +<li><a href="https://docs.sheetjs.com/pres.numbers">pres.numbers</a></li> +</ul> + +<CodeBlock language="bash">{`\ +curl -LO https://docs.sheetjs.com/pres.numbers`} +</CodeBlock> + +4) Download [`main.rs`](pathname:///jsc/main.rs) and replace `src/main.rs`: + +```bash +curl -L -o src/main.rs https://docs.sheetjs.com/jsc/main.rs +``` + +5) Download [`build.rs`](pathname:///jsc/build.rs) to the project folder: + +```bash +curl -LO https://docs.sheetjs.com/jsc/build.rs +``` + +6) Build and run the app: + +```bash +cargo run pres.numbers +``` + +If the program succeeded, the CSV contents will be printed to console and the +file `sheetjsw.xlsb` will be created. That file can be opened with a spreadsheet +editor that supports XLSB spreadsheets. + [^1]: See [`read` in "Reading Files"](/docs/api/parse-options) [^2]: See [`writeFile` in "Writing Files"](/docs/api/write-options) [^3]: See [`JSObjectMakeTypedArrayWithBytesNoCopy`](https://developer.apple.com/documentation/javascriptcore/jsobjectmaketypedarraywithbytesnocopy(_:_:_:_:_:_:_:)/) in the JavaScriptCore documentation. @@ -757,3 +906,4 @@ https://developer.apple.com/documentation/javascriptcore/jsobjectgettypedarrayby [^6]: See [`read` in "Reading Files"](/docs/api/parse-options) [^7]: See [`writeFile` in "Writing Files"](/docs/api/write-options) [^8]: See ["Install Swift"](https://www.swift.org/install) in the Swift website. +[^9]: See ["Representing opaque structs"](https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs) in the Rustonomicon \ No newline at end of file diff --git a/docz/static/jsc/build.rs b/docz/static/jsc/build.rs new file mode 100644 index 0000000..74a61ed --- /dev/null +++ b/docz/static/jsc/build.rs @@ -0,0 +1,4 @@ +#[cfg(target_os = "macos")] +fn main() { + println!("cargo::rustc-link-lib=framework=JavaScriptCore"); +} diff --git a/docz/static/jsc/main.rs b/docz/static/jsc/main.rs new file mode 100644 index 0000000..5f10ec6 --- /dev/null +++ b/docz/static/jsc/main.rs @@ -0,0 +1,228 @@ +/* See https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs */ +#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] + +#[repr(C)] +pub struct JSContext { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +type JSContextRef = *mut JSContext; +type JSGlobalContextRef = JSContextRef; + +#[repr(C)] +pub struct JSClass { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +type JSClassRef = *mut JSClass; + +#[repr(C)] +pub struct JSValue { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +type JSValueRef = *mut JSValue; +/* NOTE: JSEvaluateScript takes a pointer-to-a-pointer, hence RefRef */ +type JSValueRefRef = *mut JSValueRef; + +#[repr(C)] +pub struct JSObject { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +type JSObjectRef = *mut JSObject; + +#[repr(C)] +pub struct JSString { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +type JSStringRef = *mut JSContext; + +#[repr(C)] +pub struct BytesDeallocator { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +type JSTypedArrayBytesDeallocator = *mut BytesDeallocator; + +#[repr(C)] +pub struct BytesDeallocatorContext { + _data: [u8; 0], + _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, +} +type JSTypedArrayBytesDeallocatorContext = *mut BytesDeallocatorContext; + +/* NOTE: this is technically an enum */ +type JSTypedArrayType = u32; +const kJSTypedArrayTypeUint8Array: u32 = 3; + +/* NOTE: this is technically "unsigned int" */ +type JSPropertyAttributes = u32; + +unsafe extern "C" { + pub unsafe fn JSEvaluateScript(ctx: JSContextRef, script: JSStringRef, thisObject: JSObjectRef, sourceURL: JSStringRef, startingLineNumber: i32, exception: JSValueRefRef) -> JSValueRef; + + pub unsafe fn JSGlobalContextCreate(string: JSClassRef) -> JSGlobalContextRef; + pub unsafe fn JSGlobalContextRelease(ctx: JSGlobalContextRef); + + pub unsafe fn JSStringCreateWithUTF8CString(string: *const u8) -> JSStringRef; + pub unsafe fn JSStringGetUTF8CString(string: JSStringRef, buffer: *const u8, bufferSize: usize) -> usize; + pub unsafe fn JSStringGetMaximumUTF8CStringSize(string: JSStringRef) -> usize; + pub unsafe fn JSStringRelease(string: JSStringRef); + + pub unsafe fn JSValueIsString(ctx: JSContextRef, value: JSValueRef) -> bool; + pub unsafe fn JSValueToStringCopy(ctx: JSContextRef, value: JSValueRef, exception: JSValueRefRef) -> JSStringRef; + pub unsafe fn JSValueToObject(ctx: JSContextRef, value: JSValueRef, exception: JSValueRefRef) -> JSObjectRef; + + pub unsafe fn JSObjectGetTypedArrayLength(ctx: JSContextRef, object: JSObjectRef, exception: JSValueRefRef) -> usize; + pub unsafe fn JSObjectGetTypedArrayBytesPtr(ctx: JSContextRef, object: JSObjectRef, exception: JSValueRefRef) -> *const u8; + pub unsafe fn JSObjectSetProperty(ctx: JSContextRef, object: JSObjectRef, propertyName: JSStringRef, value: JSValueRef, attributes: JSPropertyAttributes, exception: JSValueRefRef); + pub unsafe fn JSObjectMakeTypedArrayWithBytesNoCopy(ctx: JSContextRef, arrayType: JSTypedArrayType, bytes: *const u8, byteLength: usize, bytesDeallocator: JSTypedArrayBytesDeallocator, deallocatorContext: JSTypedArrayBytesDeallocatorContext, exception: JSValueRefRef) -> JSObjectRef; + + pub unsafe fn JSContextGetGlobalObject(ctx: JSContextRef) -> JSObjectRef; +} + +macro_rules! NULL { () => { std::ptr::null_mut() }; } + +pub struct JSC; +impl JSC { + pub fn JSEval(ctx: JSContextRef, script: &str) -> JSValueRef { unsafe { + let script: JSStringRef = JSStringCreateWithUTF8CString(std::ffi::CString::new(script.as_bytes()).unwrap().as_ptr() as *const u8); + let result: JSValueRef = JSEvaluateScript(ctx, script, NULL!(), NULL!(), 0, NULL!()); + JSStringRelease(script); + result + } } + pub fn JSEvaluateScript(ctx: JSContextRef, script: JSStringRef, thisObject: JSObjectRef, sourceURL: JSStringRef, startingLineNumber: i32, exception: JSValueRefRef) -> JSValueRef { unsafe { + JSEvaluateScript(ctx, script, thisObject, sourceURL, startingLineNumber, exception) + } } + + pub fn JSGlobalContextCreate(globalObjectClass: JSClassRef) -> JSGlobalContextRef { unsafe { + JSGlobalContextCreate(globalObjectClass) + } } + pub fn JSGlobalContextRelease(globalObjectClass: JSGlobalContextRef) { unsafe { + JSGlobalContextRelease(globalObjectClass) + } } + + pub fn JSStringCreateWithUTF8CString(str: &str) -> JSStringRef { unsafe { + JSStringCreateWithUTF8CString(std::ffi::CString::new(str.as_bytes()).unwrap().as_ptr() as *const u8) + } } + pub fn JSStringRelease(string: JSStringRef) { unsafe { + JSStringRelease(string) + } } + pub fn JSCastStr(ctx: JSContextRef, value: JSValueRef) -> Result<String, String> { unsafe { + match JSValueIsString(ctx, value) { + true => { + /* TODO: this leaks memory -- `buf` must be freed and `str` must be JSStringRelease-d */ + let str: JSStringRef = JSValueToStringCopy(ctx, value, NULL!()); + let sz: usize = JSStringGetMaximumUTF8CStringSize(str); + let layout = std::alloc::Layout::array::<u8>(sz).unwrap(); + let buf = std::alloc::alloc(layout) as *mut u8; + match buf.is_null() { + true => Err(format!("Unable to allocate {} bytes", sz)), + false => { + let size = JSStringGetUTF8CString(str, buf, sz); + let slice = std::slice::from_raw_parts(buf, size); + String::from_utf8(slice.to_vec()).map_err(|e| format!("from_utf8 error {}", e)) + } + } + }, + false => Err("Value is not a string".to_string()) + } + } } + + pub fn JSObjectSetProperty(ctx: JSContextRef, object: JSObjectRef, propertyName: JSStringRef, value: JSValueRef, attributes: JSPropertyAttributes, exception: JSValueRefRef) { unsafe { + JSObjectSetProperty(ctx, object, propertyName, value, attributes, exception) + } } + pub fn JSObjectMakeUint8Array(ctx: JSContextRef, data: Vec<u8>) -> JSObjectRef { unsafe { + JSObjectMakeTypedArrayWithBytesNoCopy(ctx, kJSTypedArrayTypeUint8Array, data.as_ptr(), data.len(), NULL!(), NULL!(), NULL!()) + } } + + pub fn JSContextGetGlobalObject(ctx: JSContextRef) -> JSObjectRef { unsafe { + JSContextGetGlobalObject(ctx) + } } + pub fn JSContextSetGlobalProperty(ctx: JSContextRef, key: &str, val: JSValueRef) { + let global: JSObjectRef = JSC::JSContextGetGlobalObject(ctx); + let k: JSStringRef = JSC::JSStringCreateWithUTF8CString(key); + JSC::JSObjectSetProperty(ctx, global, k, val, 0, NULL!()); + JSC::JSStringRelease(k); + + } + + pub fn JSCastObjectToValue(obj: JSObjectRef) -> JSValueRef { obj as JSValueRef } + pub fn JSEvalAB(ctx: JSContextRef, script: &str) -> Vec<u8> { unsafe { + let result = JSC::JSEval(ctx, script); + + /* pull Uint8Array data back to Rust */ + let u8: JSObjectRef = JSValueToObject(ctx, result, NULL!()); + let sz: usize = JSObjectGetTypedArrayLength(ctx, u8, NULL!()); + let buf: *const u8 = JSObjectGetTypedArrayBytesPtr(ctx, u8, NULL!()); + + std::slice::from_raw_parts(buf, sz).to_vec() + } } + pub fn JSEvalStr(ctx: JSContextRef, script: &str) -> Result<String, String> { + let result = JSC::JSEval(ctx, script); + JSC::JSCastStr(ctx, result) + } +} + + +fn main() { + + /* initialize */ + let ctx: JSGlobalContextRef = JSC::JSGlobalContextCreate(NULL!()); + + /* JSC does not expose a standard "global" by default */ + JSC::JSEval(ctx, "var global = (function(){ return this; }).call(null);"); + + /* load library */ + JSC::JSEval(ctx, include_str!("xlsx.full.min.js")); + + /* get version string */ + match JSC::JSEvalStr(ctx, "XLSX.version") { + Ok(s) => { println!("SheetJS library version {}", s); }, + Err(e) => { + eprintln!("Could not get SheetJS version: {}", e); + JSC::JSGlobalContextRelease(ctx); + std::process::exit(1) + } + } + + /* read file */ + { + let mut iter = std::env::args(); + let path: String = iter.nth(1).expect("must specify a file name"); + + let file: Vec<u8> = std::fs::read(path.clone()).unwrap(); + + /* push data to JSC */ + let u8: JSObjectRef = JSC::JSObjectMakeUint8Array(ctx, file); + + /* assign to `global.buf` */ + JSC::JSContextSetGlobalProperty(ctx, "buf", JSC::JSCastObjectToValue(u8)); + } + + /* parse workbook and print CSV */ + match JSC::JSEvalStr(ctx, " + var wb = XLSX.read(global.buf); + var ws = wb.Sheets[wb.SheetNames[0]]; + XLSX.utils.sheet_to_csv(ws) + ") { + Ok(s) => { println!("{}", s); }, + Err(e) => { + eprintln!("Could not generate CSV: {}", e); + JSC::JSGlobalContextRelease(ctx); + std::process::exit(2) + } + } + + /* write file */ + { + let result = JSC::JSEvalAB(ctx, "XLSX.write(wb, {type:'buffer', bookType:'xlsb'});"); + + /* save file */ + std::fs::write("sheetjsw.xlsb", result).unwrap(); + } + + JSC::JSGlobalContextRelease(ctx); +}