diff --git a/inputs/passing/stringMethods/fromCodePoint.ts b/inputs/passing/stringMethods/fromCodePoint.ts new file mode 100644 index 0000000..717bf5c --- /dev/null +++ b/inputs/passing/stringMethods/fromCodePoint.ts @@ -0,0 +1,5 @@ +// test_output! "Hi 👋" + +export default function () { + return String.fromCodePoint(72, 105, 32, 128075); +} diff --git a/valuescript_compiler/src/assembler.rs b/valuescript_compiler/src/assembler.rs index ed8aabc..b2228a0 100644 --- a/valuescript_compiler/src/assembler.rs +++ b/valuescript_compiler/src/assembler.rs @@ -329,6 +329,7 @@ impl Assembler { let builtin_code = match builtin.name.as_str() { "Math" => 0, "Debug" => 1, + "String" => 2, _ => panic!("Unknown builtin: {}", builtin.name), }; diff --git a/valuescript_compiler/src/assembly_parser.rs b/valuescript_compiler/src/assembly_parser.rs index b80a7f8..d9fe5aa 100644 --- a/valuescript_compiler/src/assembly_parser.rs +++ b/valuescript_compiler/src/assembly_parser.rs @@ -747,13 +747,16 @@ impl<'a> AssemblyParser<'a> { } fn assemble_builtin(&mut self) -> Builtin { - match self.parse_one_of(&["$Math", "$Debug"]).as_str() { + match self.parse_one_of(&["$Math", "$Debug", "$String"]).as_str() { "$Math" => Builtin { name: "Math".to_string(), }, "$Debug" => Builtin { name: "Debug".to_string(), }, + "$String" => Builtin { + name: "String".to_string(), + }, _ => panic!("Shouldn't happen"), } } diff --git a/valuescript_compiler/src/scope.rs b/valuescript_compiler/src/scope.rs index f76c9e0..adeabdc 100644 --- a/valuescript_compiler/src/scope.rs +++ b/valuescript_compiler/src/scope.rs @@ -95,6 +95,12 @@ pub fn init_std_scope() -> Scope { name: "Debug".to_string(), }), ), + ( + "String".to_string(), + MappedName::Builtin(Builtin { + name: "String".to_string(), + }), + ), ]), parent: None, })), diff --git a/valuescript_compiler/src/scope_analysis.rs b/valuescript_compiler/src/scope_analysis.rs index e2cb44c..d3105b0 100644 --- a/valuescript_compiler/src/scope_analysis.rs +++ b/valuescript_compiler/src/scope_analysis.rs @@ -57,7 +57,7 @@ impl ScopeAnalysis { let mut sa = ScopeAnalysis::default(); let scope = init_std_scope(); - for builtin_name in vec!["Debug", "Math"] { + for builtin_name in vec!["Debug", "Math", "String"] { let builtin = Builtin { name: builtin_name.to_string(), }; @@ -1629,6 +1629,12 @@ fn init_std_scope() -> XScope { name: "Debug".to_string(), }), ), + ( + swc_atoms::JsWord::from("String"), + NameId::Builtin(Builtin { + name: "String".to_string(), + }), + ), ]), parent: None, })) diff --git a/valuescript_vm/src/builtins.rs b/valuescript_vm/src/builtins.rs index e95e544..4fb257f 100644 --- a/valuescript_vm/src/builtins.rs +++ b/valuescript_vm/src/builtins.rs @@ -1,6 +1,8 @@ -use super::vs_value::ValTrait; -use super::math::MATH; +use crate::string_builtin::STRING_BUILTIN; + use super::debug::DEBUG; +use super::math::MATH; +use super::vs_value::ValTrait; // TODO: Investigate whether a static array can be used for this and why rust // seems to not like it when I try. @@ -8,6 +10,7 @@ pub fn get_builtin(index: usize) -> &'static dyn ValTrait { return match index { 0 => &MATH, 1 => &DEBUG, + 2 => &STRING_BUILTIN, _ => std::panic!(""), - } + }; } diff --git a/valuescript_vm/src/lib.rs b/valuescript_vm/src/lib.rs index a76b4eb..97d095c 100644 --- a/valuescript_vm/src/lib.rs +++ b/valuescript_vm/src/lib.rs @@ -11,6 +11,7 @@ mod native_frame_function; mod native_function; mod operations; mod stack_frame; +mod string_builtin; mod string_methods; mod virtual_machine; mod vs_array; diff --git a/valuescript_vm/src/string_builtin.rs b/valuescript_vm/src/string_builtin.rs new file mode 100644 index 0000000..5a6971f --- /dev/null +++ b/valuescript_vm/src/string_builtin.rs @@ -0,0 +1,95 @@ +use std::rc::Rc; + +use crate::{ + native_function::NativeFunction, + vs_array::VsArray, + vs_class::VsClass, + vs_object::VsObject, + vs_value::{LoadFunctionResult, Val, VsType}, + ValTrait, +}; + +pub struct StringBuiltin {} + +pub static STRING_BUILTIN: StringBuiltin = StringBuiltin {}; + +impl ValTrait for StringBuiltin { + fn typeof_(&self) -> VsType { + VsType::Object + } + fn val_to_string(&self) -> String { + "function String() { [native code] }".to_string() + } + fn to_number(&self) -> f64 { + core::f64::NAN + } + fn to_index(&self) -> Option { + None + } + fn is_primitive(&self) -> bool { + false + } + fn to_primitive(&self) -> Val { + Val::String(Rc::new("function String() { [native code] }".to_string())) + } + fn is_truthy(&self) -> bool { + true + } + fn is_nullish(&self) -> bool { + false + } + fn bind(&self, _params: Vec) -> Option { + None + } + fn as_array_data(&self) -> Option> { + None + } + fn as_object_data(&self) -> Option> { + None + } + fn as_class_data(&self) -> Option> { + None + } + + fn load_function(&self) -> LoadFunctionResult { + LoadFunctionResult::NotAFunction // TODO: Converts input to string + } + + fn sub(&self, key: Val) -> Val { + match key.val_to_string().as_str() { + "fromCodePoint" => Val::Static(&FROM_CODE_POINT), + _ => Val::Undefined, + } + } + + fn submov(&mut self, _key: Val, _value: Val) { + std::panic!("Not implemented: exceptions"); + } + + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\x1b[36m[String]\x1b[39m") + } + + fn codify(&self) -> String { + "String".into() + } +} + +static FROM_CODE_POINT: NativeFunction = NativeFunction { + fn_: |_this: &mut Val, params: Vec| -> Val { + let mut result = String::new(); + + for param in params { + let code_point = param.to_number() as u32; // TODO: Check overflow behavior + + let char = match std::char::from_u32(code_point) { + Some(c) => c, + None => panic!("Not implemented: exceptions (RangeError: Invalid code point)"), + }; + + result.push(char); + } + + Val::String(Rc::new(result)) + }, +}; diff --git a/valuescript_vm/src/string_methods.rs b/valuescript_vm/src/string_methods.rs index 81d19ec..72840a0 100644 --- a/valuescript_vm/src/string_methods.rs +++ b/valuescript_vm/src/string_methods.rs @@ -29,26 +29,25 @@ pub fn op_sub_string(string_data: &Rc, subscript: &Val) -> Val { } pub fn get_string_method(method: &str) -> Val { + // Not supported: charAt, charCodeAt, fromCharCode. + // + // These methods are inherently about utf16, which is not how strings work in ValueScript. They + // also have some particularly strange behavior, like: + // + // "foo".charAt(NaN) // "f" + // + // Usually we include JavaScript behavior as much as possible, but since ValueScript strings are + // utf8, there's more license to reinterpret strings and leave out things like this which aren't + // desirable. + match method { "at" => Val::Static(&AT), - // - // Not supported: charAt, charCodeAt. - // - // These methods are inherently about utf16, which is not how strings work in ValueScript. They - // also have some particularly strange behavior, like: - // - // "foo".charAt(NaN) // "f" - // - // Usually we include JavaScript behavior as much as possible, but since strings are utf8, - // there's more license to reinterpret strings and leave out things like this which aren't - // desirable. - // // "charAt" => Val::Static(&CHAR_AT), // "charCodeAt" => Val::Static(&CHAR_CODE_AT), - // "codePointAt" => Val::Static(&CODE_POINT_AT), "concat" => Val::Static(&CONCAT), "endsWith" => Val::Static(&ENDS_WITH), + // "fromCharCode" => Val::Static(&FROM_CHAR_CODE), _ => Val::Undefined, } } @@ -128,6 +127,7 @@ static ENDS_WITH: NativeFunction = NativeFunction { let end_pos = match params.get(1) { Some(p) => match p.to_index() { + // FIXME: Using to_index for end_pos is not quite right (eg -1 should be 0) None => return Val::Bool(false), Some(i) => std::cmp::min(i, string_bytes.len()), },