mirror of https://github.com/boa-dev/boa.git
HalidOdat
5 years ago
committed by
GitHub
15 changed files with 482 additions and 424 deletions
@ -1,61 +1,82 @@ |
|||||||
use crate::builtins::{console::formatter, value::Value}; |
use crate::{ |
||||||
|
builtins::{console::formatter, value::Value}, |
||||||
|
exec::Interpreter, |
||||||
|
realm::Realm, |
||||||
|
}; |
||||||
|
|
||||||
#[test] |
#[test] |
||||||
fn formatter_no_args_is_empty_string() { |
fn formatter_no_args_is_empty_string() { |
||||||
assert_eq!(formatter(&[]), "") |
let realm = Realm::create(); |
||||||
|
let mut engine = Interpreter::new(realm); |
||||||
|
assert_eq!(formatter(&[], &mut engine).unwrap(), ""); |
||||||
} |
} |
||||||
|
|
||||||
#[test] |
#[test] |
||||||
fn formatter_empty_format_string_is_empty_string() { |
fn formatter_empty_format_string_is_empty_string() { |
||||||
|
let realm = Realm::create(); |
||||||
|
let mut engine = Interpreter::new(realm); |
||||||
let val = Value::string("".to_string()); |
let val = Value::string("".to_string()); |
||||||
let res = formatter(&[val]); |
assert_eq!(formatter(&[val], &mut engine).unwrap(), ""); |
||||||
assert_eq!(res, ""); |
|
||||||
} |
} |
||||||
|
|
||||||
#[test] |
#[test] |
||||||
fn formatter_format_without_args_renders_verbatim() { |
fn formatter_format_without_args_renders_verbatim() { |
||||||
let val = [Value::string("%d %s %% %f".to_string())]; |
let realm = Realm::create(); |
||||||
let res = formatter(&val); |
let mut engine = Interpreter::new(realm); |
||||||
|
let val = [Value::string("%d %s %% %f")]; |
||||||
|
let res = formatter(&val, &mut engine).unwrap(); |
||||||
assert_eq!(res, "%d %s %% %f"); |
assert_eq!(res, "%d %s %% %f"); |
||||||
} |
} |
||||||
|
|
||||||
#[test] |
#[test] |
||||||
fn formatter_empty_format_string_concatenates_rest_of_args() { |
fn formatter_empty_format_string_concatenates_rest_of_args() { |
||||||
|
let realm = Realm::create(); |
||||||
|
let mut engine = Interpreter::new(realm); |
||||||
|
|
||||||
let val = [ |
let val = [ |
||||||
Value::string("".to_string()), |
Value::string(""), |
||||||
Value::string("to powinno zostać".to_string()), |
Value::string("to powinno zostać"), |
||||||
Value::string("połączone".to_string()), |
Value::string("połączone"), |
||||||
]; |
]; |
||||||
let res = formatter(&val); |
let res = formatter(&val, &mut engine).unwrap(); |
||||||
assert_eq!(res, " to powinno zostać połączone"); |
assert_eq!(res, " to powinno zostać połączone"); |
||||||
} |
} |
||||||
|
|
||||||
#[test] |
#[test] |
||||||
fn formatter_utf_8_checks() { |
fn formatter_utf_8_checks() { |
||||||
|
let realm = Realm::create(); |
||||||
|
let mut engine = Interpreter::new(realm); |
||||||
|
|
||||||
let val = [ |
let val = [ |
||||||
Value::string("Są takie chwile %dą %są tu%sów %привет%ź".to_string()), |
Value::string("Są takie chwile %dą %są tu%sów %привет%ź".to_string()), |
||||||
Value::integer(123), |
Value::integer(123), |
||||||
Value::rational(1.23), |
Value::rational(1.23), |
||||||
Value::string("ł".to_string()), |
Value::string("ł".to_string()), |
||||||
]; |
]; |
||||||
let res = formatter(&val); |
let res = formatter(&val, &mut engine).unwrap(); |
||||||
assert_eq!(res, "Są takie chwile 123ą 1.23ą tułów %привет%ź"); |
assert_eq!(res, "Są takie chwile 123ą 1.23ą tułów %привет%ź"); |
||||||
} |
} |
||||||
|
|
||||||
#[test] |
#[test] |
||||||
fn formatter_trailing_format_leader_renders() { |
fn formatter_trailing_format_leader_renders() { |
||||||
|
let realm = Realm::create(); |
||||||
|
let mut engine = Interpreter::new(realm); |
||||||
|
|
||||||
let val = [ |
let val = [ |
||||||
Value::string("%%%%%".to_string()), |
Value::string("%%%%%".to_string()), |
||||||
Value::string("|".to_string()), |
Value::string("|".to_string()), |
||||||
]; |
]; |
||||||
let res = formatter(&val); |
let res = formatter(&val, &mut engine).unwrap(); |
||||||
assert_eq!(res, "%%% |") |
assert_eq!(res, "%%% |"); |
||||||
} |
} |
||||||
|
|
||||||
#[test] |
#[test] |
||||||
#[allow(clippy::approx_constant)] |
#[allow(clippy::approx_constant)] |
||||||
fn formatter_float_format_works() { |
fn formatter_float_format_works() { |
||||||
|
let realm = Realm::create(); |
||||||
|
let mut engine = Interpreter::new(realm); |
||||||
|
|
||||||
let val = [Value::string("%f".to_string()), Value::rational(3.1415)]; |
let val = [Value::string("%f".to_string()), Value::rational(3.1415)]; |
||||||
let res = formatter(&val); |
let res = formatter(&val, &mut engine).unwrap(); |
||||||
assert_eq!(res, "3.141500") |
assert_eq!(res, "3.141500"); |
||||||
} |
} |
||||||
|
@ -0,0 +1,222 @@ |
|||||||
|
use super::*; |
||||||
|
|
||||||
|
impl Display for Value { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||||
|
Display::fmt(&self.data(), f) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A helper macro for printing objects
|
||||||
|
/// Can be used to print both properties and internal slots
|
||||||
|
/// All of the overloads take:
|
||||||
|
/// - The object to be printed
|
||||||
|
/// - The function with which to print
|
||||||
|
/// - The indentation for the current level (for nested objects)
|
||||||
|
/// - A HashSet with the addresses of the already printed objects for the current branch
|
||||||
|
/// (used to avoid infinite loops when there are cyclic deps)
|
||||||
|
macro_rules! print_obj_value { |
||||||
|
(all of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => { |
||||||
|
{ |
||||||
|
let mut internals = print_obj_value!(internals of $obj, $display_fn, $indent, $encounters); |
||||||
|
let mut props = print_obj_value!(props of $obj, $display_fn, $indent, $encounters, true); |
||||||
|
|
||||||
|
props.reserve(internals.len()); |
||||||
|
props.append(&mut internals); |
||||||
|
|
||||||
|
props |
||||||
|
} |
||||||
|
}; |
||||||
|
(internals of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => { |
||||||
|
print_obj_value!(impl internal_slots, $obj, |(key, val)| { |
||||||
|
format!( |
||||||
|
"{:>width$}: {}", |
||||||
|
key, |
||||||
|
$display_fn(&val, $encounters, $indent.wrapping_add(4), true), |
||||||
|
width = $indent, |
||||||
|
) |
||||||
|
}) |
||||||
|
}; |
||||||
|
(props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => { |
||||||
|
print_obj_value!(impl properties, $obj, |(key, val)| { |
||||||
|
let v = &val |
||||||
|
.value |
||||||
|
.as_ref() |
||||||
|
.expect("Could not get the property's value"); |
||||||
|
|
||||||
|
format!( |
||||||
|
"{:>width$}: {}", |
||||||
|
key, |
||||||
|
$display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals), |
||||||
|
width = $indent, |
||||||
|
) |
||||||
|
}) |
||||||
|
}; |
||||||
|
|
||||||
|
// A private overload of the macro
|
||||||
|
// DO NOT use directly
|
||||||
|
(impl $field:ident, $v:expr, $f:expr) => { |
||||||
|
$v |
||||||
|
.borrow() |
||||||
|
.$field |
||||||
|
.iter() |
||||||
|
.map($f) |
||||||
|
.collect::<Vec<String>>() |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { |
||||||
|
match x { |
||||||
|
// We don't want to print private (compiler) or prototype properties
|
||||||
|
ValueData::Object(ref v) => { |
||||||
|
// Can use the private "type" field of an Object to match on
|
||||||
|
// which type of Object it represents for special printing
|
||||||
|
match v.borrow().kind { |
||||||
|
ObjectKind::String => match v |
||||||
|
.borrow() |
||||||
|
.internal_slots |
||||||
|
.get("StringData") |
||||||
|
.expect("Cannot get primitive value from String") |
||||||
|
.data() |
||||||
|
{ |
||||||
|
ValueData::String(ref string) => format!("\"{}\"", string), |
||||||
|
_ => unreachable!("[[StringData]] should always contain String"), |
||||||
|
}, |
||||||
|
ObjectKind::Boolean => { |
||||||
|
let bool_data = v.borrow().get_internal_slot("BooleanData").to_string(); |
||||||
|
|
||||||
|
format!("Boolean {{ {} }}", bool_data) |
||||||
|
} |
||||||
|
ObjectKind::Array => { |
||||||
|
let len = i32::from( |
||||||
|
&v.borrow() |
||||||
|
.properties |
||||||
|
.get("length") |
||||||
|
.unwrap() |
||||||
|
.value |
||||||
|
.clone() |
||||||
|
.expect("Could not borrow value"), |
||||||
|
); |
||||||
|
|
||||||
|
if len == 0 { |
||||||
|
return String::from("[]"); |
||||||
|
} |
||||||
|
|
||||||
|
let arr = (0..len) |
||||||
|
.map(|i| { |
||||||
|
// Introduce recursive call to stringify any objects
|
||||||
|
// which are part of the Array
|
||||||
|
log_string_from( |
||||||
|
&v.borrow() |
||||||
|
.properties |
||||||
|
.get(&i.to_string()) |
||||||
|
.unwrap() |
||||||
|
.value |
||||||
|
.clone() |
||||||
|
.expect("Could not borrow value"), |
||||||
|
print_internals, |
||||||
|
) |
||||||
|
}) |
||||||
|
.collect::<Vec<String>>() |
||||||
|
.join(", "); |
||||||
|
|
||||||
|
format!("[ {} ]", arr) |
||||||
|
} |
||||||
|
_ => display_obj(&x, print_internals), |
||||||
|
} |
||||||
|
} |
||||||
|
ValueData::Symbol(ref sym) => { |
||||||
|
let desc: Value = sym.borrow().get_internal_slot("Description"); |
||||||
|
match *desc { |
||||||
|
ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()), |
||||||
|
_ => String::from("Symbol()"), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_ => format!("{}", x), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// A helper function for specifically printing object values
|
||||||
|
pub(crate) fn display_obj(v: &ValueData, print_internals: bool) -> String { |
||||||
|
// A simple helper for getting the address of a value
|
||||||
|
// TODO: Find a more general place for this, as it can be used in other situations as well
|
||||||
|
fn address_of<T>(t: &T) -> usize { |
||||||
|
let my_ptr: *const T = t; |
||||||
|
my_ptr as usize |
||||||
|
} |
||||||
|
|
||||||
|
// We keep track of which objects we have encountered by keeping their
|
||||||
|
// in-memory address in this set
|
||||||
|
let mut encounters = HashSet::new(); |
||||||
|
|
||||||
|
fn display_obj_internal( |
||||||
|
data: &ValueData, |
||||||
|
encounters: &mut HashSet<usize>, |
||||||
|
indent: usize, |
||||||
|
print_internals: bool, |
||||||
|
) -> String { |
||||||
|
if let ValueData::Object(ref v) = *data { |
||||||
|
// The in-memory address of the current object
|
||||||
|
let addr = address_of(v.borrow().deref()); |
||||||
|
|
||||||
|
// We need not continue if this object has already been
|
||||||
|
// printed up the current chain
|
||||||
|
if encounters.contains(&addr) { |
||||||
|
return String::from("[Cycle]"); |
||||||
|
} |
||||||
|
|
||||||
|
// Mark the current object as encountered
|
||||||
|
encounters.insert(addr); |
||||||
|
|
||||||
|
let result = if print_internals { |
||||||
|
print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n") |
||||||
|
} else { |
||||||
|
print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals) |
||||||
|
.join(",\n") |
||||||
|
}; |
||||||
|
|
||||||
|
// If the current object is referenced in a different branch,
|
||||||
|
// it will not cause an infinte printing loop, so it is safe to be printed again
|
||||||
|
encounters.remove(&addr); |
||||||
|
|
||||||
|
let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)]) |
||||||
|
.expect("Could not create the closing brace's indentation string"); |
||||||
|
|
||||||
|
format!("{{\n{}\n{}}}", result, closing_indent) |
||||||
|
} else { |
||||||
|
// Every other type of data is printed as is
|
||||||
|
format!("{}", data) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
display_obj_internal(v, &mut encounters, 4, print_internals) |
||||||
|
} |
||||||
|
|
||||||
|
impl Display for ValueData { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||||
|
match self { |
||||||
|
Self::Null => write!(f, "null"), |
||||||
|
Self::Undefined => write!(f, "undefined"), |
||||||
|
Self::Boolean(v) => write!(f, "{}", v), |
||||||
|
Self::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { |
||||||
|
// If a description exists use it
|
||||||
|
Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), |
||||||
|
_ => write!(f, "Symbol()"), |
||||||
|
}, |
||||||
|
Self::String(ref v) => write!(f, "{}", v), |
||||||
|
Self::Rational(v) => write!( |
||||||
|
f, |
||||||
|
"{}", |
||||||
|
match v { |
||||||
|
_ if v.is_nan() => "NaN".to_string(), |
||||||
|
_ if v.is_infinite() && v.is_sign_negative() => "-Infinity".to_string(), |
||||||
|
_ if v.is_infinite() => "Infinity".to_string(), |
||||||
|
_ => v.to_string(), |
||||||
|
} |
||||||
|
), |
||||||
|
Self::Object(_) => write!(f, "{}", log_string_from(self, true)), |
||||||
|
Self::Integer(v) => write!(f, "{}", v), |
||||||
|
Self::BigInt(ref num) => write!(f, "{}n", num), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue