JavaScriptCore Rust demo

This commit is contained in:
SheetJS 2025-03-30 02:31:40 -04:00
parent 55c1649e47
commit 50c4139bb4
7 changed files with 517 additions and 45 deletions

@ -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>

@ -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/)
:::

@ -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}>&nbsp;</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}>&nbsp;</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}>&nbsp;</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>

@ -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:

@ -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

4
docz/static/jsc/build.rs Normal file

@ -0,0 +1,4 @@
#[cfg(target_os = "macos")]
fn main() {
println!("cargo::rustc-link-lib=framework=JavaScriptCore");
}

228
docz/static/jsc/main.rs Normal file

@ -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);
}