-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
856 additions
and
111 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
libs/shinkai-tools-runner/src/tools/quickjs_runtime/context_globals/text_decoder.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use rquickjs::{function::Opt, Class, Ctx, Object, Result, Value}; | ||
|
||
use super::utils::{bytes::get_bytes, encoding::Encoder, object::ObjectExt, result::ResultExt}; | ||
|
||
#[rquickjs::class] | ||
#[derive(rquickjs::class::Trace)] | ||
pub struct TextDecoder { | ||
#[qjs(skip_trace)] | ||
encoder: Encoder, | ||
fatal: bool, | ||
ignore_bom: bool, | ||
} | ||
|
||
#[rquickjs::methods] | ||
impl<'js> TextDecoder { | ||
#[qjs(constructor)] | ||
pub fn new(ctx: Ctx<'js>, label: Opt<String>, options: Opt<Object<'js>>) -> Result<Self> { | ||
let encoding = label | ||
.0 | ||
.filter(|lbl| !lbl.is_empty()) | ||
.unwrap_or_else(|| String::from("utf-8")); | ||
let mut fatal = false; | ||
let mut ignore_bom = false; | ||
|
||
let encoder = Encoder::from_str(&encoding).or_throw_range(&ctx, None)?; | ||
|
||
if let Some(options) = options.0 { | ||
if let Some(opt) = options.get_optional("fatal")? { | ||
fatal = opt; | ||
} | ||
if let Some(opt) = options.get_optional("ignoreBOM")? { | ||
ignore_bom = opt; | ||
} | ||
} | ||
|
||
Ok(TextDecoder { | ||
encoder, | ||
fatal, | ||
ignore_bom, | ||
}) | ||
} | ||
|
||
#[qjs(get)] | ||
fn encoding(&self) -> &str { | ||
self.encoder.as_label() | ||
} | ||
|
||
#[qjs(get)] | ||
fn fatal(&self) -> bool { | ||
self.fatal | ||
} | ||
|
||
#[qjs(get, rename = "ignoreBOM")] | ||
fn ignore_bom(&self) -> bool { | ||
self.ignore_bom | ||
} | ||
|
||
pub fn decode(&self, ctx: Ctx<'js>, buffer: Value<'js>) -> Result<String> { | ||
let bytes = get_bytes(&ctx, buffer)?; | ||
let start_pos = if !self.ignore_bom { | ||
match bytes.get(..3) { | ||
Some([0xFF, 0xFE, ..]) | Some([0xFE, 0xFF, ..]) => 2, | ||
Some([0xEF, 0xBB, 0xBF]) => 3, | ||
_ => 0, | ||
} | ||
} else { | ||
0 | ||
}; | ||
|
||
self.encoder | ||
.encode_to_string(&bytes[start_pos..], !self.fatal) | ||
.or_throw_type(&ctx, None) | ||
} | ||
} | ||
|
||
pub fn init(ctx: &Ctx<'_>) -> Result<()> { | ||
let globals = ctx.globals(); | ||
Class::<TextDecoder>::define(&globals)?; | ||
Ok(()) | ||
} |
128 changes: 128 additions & 0 deletions
128
libs/shinkai-tools-runner/src/tools/quickjs_runtime/context_globals/text_encoder.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// https://github.com/awslabs/llrt/blob/main/llrt_core/src/modules/ | ||
|
||
use rquickjs::{class::Trace, Array, Class, Ctx, Result, Value}; | ||
use rquickjs::{function::Opt, Exception, Object, TypedArray}; | ||
|
||
use super::utils::result::ResultExt; | ||
|
||
#[derive(Trace)] | ||
#[rquickjs::class] | ||
pub struct TextEncoder {} | ||
|
||
#[rquickjs::methods(rename_all = "camelCase")] | ||
impl TextEncoder { | ||
#[qjs(constructor)] | ||
pub fn new() -> Self { | ||
Self {} | ||
} | ||
|
||
#[qjs(get)] | ||
fn encoding(&self) -> &str { | ||
"utf-8" | ||
} | ||
|
||
pub fn encode<'js>(&self, ctx: Ctx<'js>, string: Opt<Value<'js>>) -> Result<Value<'js>> { | ||
if let Some(string) = string.0 { | ||
if let Some(string) = string.as_string() { | ||
let string = string.to_string()?; | ||
eprintln!("String to encode: {}", string); | ||
return TypedArray::new(ctx.clone(), string.as_bytes()) | ||
.map(|m: TypedArray<'_, u8>| m.into_value()); | ||
} else if !string.is_undefined() { | ||
eprintln!("The \"string\" argument must be a string."); | ||
return Err(Exception::throw_message( | ||
&ctx, | ||
"The \"string\" argument must be a string.", | ||
)); | ||
} | ||
} | ||
|
||
eprintln!("Encoding empty string"); | ||
TypedArray::new(ctx.clone(), []).map(|m: TypedArray<'_, u8>| m.into_value()) | ||
} | ||
|
||
pub fn encode_into<'js>( | ||
&self, | ||
ctx: Ctx<'js>, | ||
src: String, | ||
dst: Value<'js>, | ||
) -> Result<Object<'js>> { | ||
if let Ok(typed_array) = TypedArray::<u8>::from_value(dst) { | ||
let dst_length = typed_array.len(); | ||
let dst_offset: usize = typed_array.get("byteOffset")?; | ||
let array_buffer = typed_array.arraybuffer()?; | ||
let raw = array_buffer | ||
.as_raw() | ||
.ok_or("ArrayBuffer is detached") | ||
.or_throw(&ctx)?; | ||
|
||
let dst = unsafe { | ||
std::slice::from_raw_parts_mut(raw.ptr.as_ptr().add(dst_offset), dst_length) | ||
}; | ||
|
||
let mut written = 0; | ||
let dst_len = dst.len(); | ||
for ch in src.chars() { | ||
let len = ch.len_utf8(); | ||
if written + len > dst_len { | ||
break; | ||
} | ||
written += len; | ||
} | ||
dst[..written].copy_from_slice(&src.as_bytes()[..written]); | ||
let read: usize = src[..written].chars().map(char::len_utf16).sum(); | ||
|
||
let obj = Object::new(ctx)?; | ||
obj.set("read", read)?; | ||
obj.set("written", written)?; | ||
Ok(obj) | ||
} else { | ||
Err(Exception::throw_type( | ||
&ctx, | ||
"The \"dest\" argument must be an instance of Uint8Array.", | ||
)) | ||
} | ||
} | ||
} | ||
|
||
pub fn init(ctx: &Ctx<'_>) -> Result<()> { | ||
let globals = ctx.globals(); | ||
Class::<TextEncoder>::define(&globals)?; | ||
Ok(()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use rquickjs::{Context, Runtime, Value}; | ||
use crate::tools::quickjs_runtime::context_globals::text_decoder; | ||
use std::error::Error; | ||
|
||
#[test] | ||
fn test_text_encoder_decoder() -> std::result::Result<(), Box<dyn Error>> { | ||
let runtime = Runtime::new()?; | ||
let ctx = Context::full(&runtime)?; | ||
|
||
ctx.with(|ctx| { | ||
// Initialize the TextEncoder and TextDecoder classes | ||
init(&ctx)?; | ||
text_decoder::init(&ctx)?; | ||
|
||
let result: Value<'_> = ctx.eval( | ||
r#" | ||
let encoder = new TextEncoder(); | ||
let decoder = new TextDecoder(); | ||
let buffer = encoder.encode('hello'); | ||
decoder.decode(buffer) == 'hello'; | ||
"#, | ||
)?; | ||
|
||
assert!(result.as_bool().unwrap()); | ||
Ok::<(), Box<dyn Error>>(()) | ||
})?; | ||
Ok(()) | ||
} | ||
} |
Oops, something went wrong.