#![allow(clippy::print_stdout)]
#[cfg(test)]
mod tests;
use crate::{
builtins::function::make_builtin_fn,
value::{display_obj, RcString, Value},
BoaProfiler, Context, Result,
};
use rustc_hash::FxHashMap;
use std::time::SystemTime;
#[derive(Debug)]
pub enum LogMessage {
Log(String),
Info(String),
Warn(String),
Error(String),
}
fn get_arg_at_index<'a, T>(args: &'a [Value], index: usize) -> Option<T>
where
T: From<&'a Value> + Default,
{
args.get(index).map(|s| T::from(s))
}
pub(crate) fn logger(msg: LogMessage, console_state: &Console) {
let indent = 2 * console_state.groups.len();
match msg {
LogMessage::Error(msg) => {
eprintln!("{:>width$}", msg, width = indent);
}
LogMessage::Log(msg) | LogMessage::Info(msg) | LogMessage::Warn(msg) => {
println!("{:>width$}", msg, width = indent);
}
}
}
pub fn formatter(data: &[Value], ctx: &mut Context) -> Result<String> {
let target = data.get(0).cloned().unwrap_or_default().to_string(ctx)?;
match data.len() {
0 => Ok(String::new()),
1 => Ok(target.to_string()),
_ => {
let mut formatted = String::new();
let mut arg_index = 1;
let mut chars = target.chars();
while let Some(c) = chars.next() {
if c == '%' {
let fmt = chars.next().unwrap_or('%');
match fmt {
'd' | 'i' => {
let arg = data
.get(arg_index)
.cloned()
.unwrap_or_default()
.to_integer(ctx)?;
formatted.push_str(&format!("{}", arg));
arg_index += 1;
}
'f' => {
let arg = data
.get(arg_index)
.cloned()
.unwrap_or_default()
.to_number(ctx)?;
formatted.push_str(&format!("{number:.prec$}", number = arg, prec = 6));
arg_index += 1
}
'o' | 'O' => {
let arg = data.get(arg_index).cloned().unwrap_or_default();
formatted.push_str(&format!("{}", arg.display()));
arg_index += 1
}
's' => {
let arg = data
.get(arg_index)
.cloned()
.unwrap_or_default()
.to_string(ctx)?;
formatted.push_str(&arg);
arg_index += 1
}
'%' => formatted.push('%'),
c => {
formatted.push('%');
formatted.push(c);
}
}
} else {
formatted.push(c);
};
}
for rest in data.iter().skip(arg_index) {
formatted.push_str(&format!(" {}", rest.to_string(ctx)?))
}
Ok(formatted)
}
}
}
#[derive(Debug, Default)]
pub(crate) struct Console {
count_map: FxHashMap<RcString, u32>,
timer_map: FxHashMap<RcString, u128>,
groups: Vec<String>,
}
impl Console {
pub(crate) const NAME: &'static str = "console";
pub(crate) fn assert(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let assertion = get_arg_at_index::<bool>(args, 0).unwrap_or_default();
if !assertion {
let mut args: Vec<Value> = args.iter().skip(1).cloned().collect();
let message = "Assertion failed".to_string();
if args.is_empty() {
args.push(Value::from(message));
} else if !args[0].is_string() {
args.insert(0, Value::from(message));
} else {
let concat = format!("{}: {}", message, args[0].display());
args[0] = Value::from(concat);
}
logger(LogMessage::Error(formatter(&args, ctx)?), ctx.console());
}
Ok(Value::undefined())
}
pub(crate) fn clear(_: &Value, _: &[Value], ctx: &mut Context) -> Result<Value> {
ctx.console_mut().groups.clear();
Ok(Value::undefined())
}
pub(crate) fn debug(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
logger(LogMessage::Log(formatter(args, ctx)?), ctx.console());
Ok(Value::undefined())
}
pub(crate) fn error(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
logger(LogMessage::Error(formatter(args, ctx)?), ctx.console());
Ok(Value::undefined())
}
pub(crate) fn info(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
logger(LogMessage::Info(formatter(args, ctx)?), ctx.console());
Ok(Value::undefined())
}
pub(crate) fn log(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
logger(LogMessage::Log(formatter(args, ctx)?), ctx.console());
Ok(Value::undefined())
}
pub(crate) fn trace(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
if !args.is_empty() {
logger(LogMessage::Log(formatter(args, ctx)?), ctx.console());
logger(
LogMessage::Log("Not implemented: <stack trace>".to_string()),
ctx.console(),
)
}
Ok(Value::undefined())
}
pub(crate) fn warn(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
logger(LogMessage::Warn(formatter(args, ctx)?), ctx.console());
Ok(Value::undefined())
}
pub(crate) fn count(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let label = match args.get(0) {
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
let msg = format!("count {}:", &label);
let c = ctx.console_mut().count_map.entry(label).or_insert(0);
*c += 1;
logger(LogMessage::Info(format!("{} {}", msg, c)), ctx.console());
Ok(Value::undefined())
}
pub(crate) fn count_reset(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let label = match args.get(0) {
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
ctx.console_mut().count_map.remove(&label);
logger(
LogMessage::Warn(format!("countReset {}", label)),
ctx.console(),
);
Ok(Value::undefined())
}
fn system_time_in_ms() -> u128 {
let now = SystemTime::now();
now.duration_since(SystemTime::UNIX_EPOCH)
.expect("negative duration")
.as_millis()
}
pub(crate) fn time(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let label = match args.get(0) {
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
if ctx.console().timer_map.get(&label).is_some() {
logger(
LogMessage::Warn(format!("Timer '{}' already exist", label)),
ctx.console(),
);
} else {
let time = Self::system_time_in_ms();
ctx.console_mut().timer_map.insert(label, time);
}
Ok(Value::undefined())
}
pub(crate) fn time_log(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let label = match args.get(0) {
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
if let Some(t) = ctx.console().timer_map.get(&label) {
let time = Self::system_time_in_ms();
let mut concat = format!("{}: {} ms", label, time - t);
for msg in args.iter().skip(1) {
concat = concat + " " + &msg.display().to_string();
}
logger(LogMessage::Log(concat), ctx.console());
} else {
logger(
LogMessage::Warn(format!("Timer '{}' doesn't exist", label)),
ctx.console(),
);
}
Ok(Value::undefined())
}
pub(crate) fn time_end(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let label = match args.get(0) {
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
if let Some(t) = ctx.console_mut().timer_map.remove(label.as_str()) {
let time = Self::system_time_in_ms();
logger(
LogMessage::Info(format!("{}: {} ms - timer removed", label, time - t)),
ctx.console(),
);
} else {
logger(
LogMessage::Warn(format!("Timer '{}' doesn't exist", label)),
ctx.console(),
);
}
Ok(Value::undefined())
}
pub(crate) fn group(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let group_label = formatter(args, ctx)?;
logger(
LogMessage::Info(format!("group: {}", &group_label)),
ctx.console(),
);
ctx.console_mut().groups.push(group_label);
Ok(Value::undefined())
}
pub(crate) fn group_end(_: &Value, _: &[Value], ctx: &mut Context) -> Result<Value> {
ctx.console_mut().groups.pop();
Ok(Value::undefined())
}
pub(crate) fn dir(_: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let undefined = Value::undefined();
logger(
LogMessage::Info(display_obj(args.get(0).unwrap_or(&undefined), true)),
ctx.console(),
);
Ok(Value::undefined())
}
#[inline]
pub(crate) fn init(interpreter: &mut Context) -> (&'static str, Value) {
let global = interpreter.global_object();
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
let console = Value::new_object(Some(global));
make_builtin_fn(Self::assert, "assert", &console, 0, interpreter);
make_builtin_fn(Self::clear, "clear", &console, 0, interpreter);
make_builtin_fn(Self::debug, "debug", &console, 0, interpreter);
make_builtin_fn(Self::error, "error", &console, 0, interpreter);
make_builtin_fn(Self::info, "info", &console, 0, interpreter);
make_builtin_fn(Self::log, "log", &console, 0, interpreter);
make_builtin_fn(Self::trace, "trace", &console, 0, interpreter);
make_builtin_fn(Self::warn, "warn", &console, 0, interpreter);
make_builtin_fn(Self::error, "exception", &console, 0, interpreter);
make_builtin_fn(Self::count, "count", &console, 0, interpreter);
make_builtin_fn(Self::count_reset, "countReset", &console, 0, interpreter);
make_builtin_fn(Self::group, "group", &console, 0, interpreter);
make_builtin_fn(Self::group, "groupCollapsed", &console, 0, interpreter);
make_builtin_fn(Self::group_end, "groupEnd", &console, 0, interpreter);
make_builtin_fn(Self::time, "time", &console, 0, interpreter);
make_builtin_fn(Self::time_log, "timeLog", &console, 0, interpreter);
make_builtin_fn(Self::time_end, "timeEnd", &console, 0, interpreter);
make_builtin_fn(Self::dir, "dir", &console, 0, interpreter);
make_builtin_fn(Self::dir, "dirxml", &console, 0, interpreter);
(Self::NAME, console)
}
}