use std::fmt::Write;
use crate::intrinsics::RenderIntrinsicsArgs;
use crate::uwriteln;
use crate::{intrinsics::Intrinsic, source::Source};
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum StringIntrinsic {
Utf16Decoder,
Utf16Encode,
Utf16EncodeAsync,
GlobalTextDecoderUtf8,
GlobalTextEncoderUtf8,
Utf8Encode,
Utf8EncodeAsync,
ValidateGuestChar,
ValidateHostChar,
}
impl StringIntrinsic {
pub fn get_global_names() -> impl IntoIterator<Item = &'static str> {
[
Self::Utf16Decoder.name(),
Self::Utf16Encode.name(),
Self::Utf16EncodeAsync.name(),
Self::GlobalTextDecoderUtf8.name(),
Self::GlobalTextEncoderUtf8.name(),
Self::Utf8Encode.name(),
Self::Utf8EncodeAsync.name(),
Self::ValidateGuestChar.name(),
Self::ValidateHostChar.name(),
]
}
pub fn name(&self) -> &'static str {
match self {
Self::Utf16Decoder => "utf16Decoder",
Self::Utf16Encode => "_utf16AllocateAndEncode",
Self::Utf16EncodeAsync => "_utf16AllocateAndEncodeAsync",
Self::GlobalTextDecoderUtf8 => "TEXT_DECODER_UTF8",
Self::GlobalTextEncoderUtf8 => "TEXT_ENCODER_UTF8",
Self::Utf8Encode => "_utf8AllocateAndEncode",
Self::Utf8EncodeAsync => "_utf8AllocateAndEncodeAsync",
Self::ValidateGuestChar => "validateGuestChar",
Self::ValidateHostChar => "validateHostChar",
}
}
pub fn render(&self, output: &mut Source, render_args: &RenderIntrinsicsArgs<'_>) {
let name = self.name();
match self {
Self::Utf16Decoder => uwriteln!(output, "const {name} = new TextDecoder('utf-16');"),
Self::Utf16Encode | Self::Utf16EncodeAsync => {
let is_le = render_args.require_intrinsic(Intrinsic::IsLE);
let (fn_preamble, realloc_call) = match self {
Self::Utf16Encode => ("", "realloc"),
Self::Utf16EncodeAsync => ("async ", "await realloc"),
_ => unreachable!("unexpected intrinsic"),
};
uwriteln!(
output,
r#"
{fn_preamble}function {name}(str, realloc, memory) {{
const len = str.length;
const ptr = {realloc_call}(0, 0, 2, len * 2);
const out = new Uint16Array(memory.buffer, ptr, len);
let i = 0;
if ({is_le}) {{
while (i < len) {{ out[i] = str.charCodeAt(i++); }}
}} else {{
while (i < len) {{
const ch = str.charCodeAt(i);
out[i++] = (ch & 0xff) << 8 | ch >>> 8;
}}
}}
return {{ ptr, len, codepoints: [...str].length }};
}}
"#
);
}
Self::GlobalTextDecoderUtf8 => uwriteln!(output, "const {name} = new TextDecoder();"),
Self::GlobalTextEncoderUtf8 => uwriteln!(output, "const {name} = new TextEncoder();"),
Self::Utf8Encode | Self::Utf8EncodeAsync => {
let encoder = render_args.require_intrinsic(Self::GlobalTextEncoderUtf8);
let (fn_preamble, realloc_call) = match self {
Self::Utf8Encode => ("", "realloc"),
Self::Utf8EncodeAsync => ("async ", "await realloc"),
_ => unreachable!("unexpected intrinsic"),
};
uwriteln!(
output,
r#"
{fn_preamble}function {name}(s, realloc, memory) {{
if (typeof s !== 'string') {{
throw new TypeError('expected a string, received [' + typeof s + ']');
}}
if (s.length === 0) {{ return {{ ptr: 1, len: 0 }}; }}
// Compute the exact allocation size up front. Some older preview1
// adapters only support an initial allocation, not a subsequent shrink.
let len = 0;
let codepoints = 0;
for (let i = 0; i < s.length; i++) {{
const ch = s.charCodeAt(i);
codepoints++;
if (ch < 0x80) {{ len += 1; }}
else if (ch < 0x800) {{ len += 2; }}
else if (ch >= 0xd800 && ch <= 0xdbff &&
i + 1 < s.length &&
(s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {{
len += 4;
i++;
}} else {{ len += 3; }}
}}
const ptr = {realloc_call}(0, 0, 1, len);
const {{ read, written }} = {encoder}.encodeInto(
s,
new Uint8Array(memory.buffer, ptr, len),
);
if (read !== s.length || written !== len) {{
throw new Error('failed to encode whole string');
}}
const res = {{ ptr, len, codepoints }};
return res;
}}
"#
);
}
Self::ValidateGuestChar => uwriteln!(
output,
r#"
function {name}(i) {{
if ((i > 0x10ffff) || (i >= 0xd800 && i <= 0xdfff)) {{ throw new TypeError(`not a valid char`); }}
return String.fromCodePoint(i);
}}
"#,
),
Self::ValidateHostChar => uwriteln!(
output,
r#"
function {name}(s) {{
if (typeof s !== 'string') {{ throw new TypeError(`must be a string`); }}
return s.codePointAt(0);
}}
"#
),
}
}
}