JavaScriptCore Rust demo
This commit is contained in:
parent
55c1649e47
commit
50c4139bb4
@ -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}> </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>
|
||||
|
@ -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
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
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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user