Skip to content

Commit

Permalink
Added Format Specifier Support to Console
Browse files Browse the repository at this point in the history
  • Loading branch information
Redfire75369 committed Feb 13, 2024
1 parent 7d20b31 commit 6425540
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 83 deletions.
2 changes: 1 addition & 1 deletion ion/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ impl From<Error> for ErrorReport {

impl<E: error::Error> From<E> for Error {
fn from(error: E) -> Error {
Error::new(&error.to_string(), None)
Error::new(error.to_string(), None)
}
}

Expand Down
2 changes: 1 addition & 1 deletion ion/src/exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Exception {
let kind = ErrorKind::from_proto_key(IdentifyStandardInstance(handle.get()));
let error = Error {
kind,
message,
message: message.into(),
location: Some(location),
object: Some(handle.get()),
};
Expand Down
17 changes: 10 additions & 7 deletions ion/src/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@ pub fn __handle_native_constructor_result(
}

fn handle_unwind_error(cx: &Context, unwind_error: Box<dyn Any + Send>) -> bool {
if let Some(unwind) = unwind_error.downcast_ref::<String>() {
Error::new(unwind, None).throw(cx);
} else if let Some(unwind) = unwind_error.downcast_ref::<&str>() {
Error::new(unwind, None).throw(cx);
} else {
Error::new("Unknown Panic Occurred", None).throw(cx);
forget(unwind_error);
match unwind_error.downcast::<String>() {
Ok(unwind) => Error::new(*unwind, None).throw(cx),
Err(unwind_error) => {
if let Some(unwind) = unwind_error.downcast_ref::<&'static str>() {
Error::new(*unwind, None).throw(cx);
} else {
Error::new("Unknown Panic Occurred", None).throw(cx);
forget(unwind_error);
}
}
}
false
}
Expand Down
1 change: 1 addition & 0 deletions ion/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extern crate mozjs;

use std::result;

pub use bigint::BigInt;
pub use class::ClassDefinition;
pub use context::{Context, ContextInner};
pub use error::{Error, ErrorKind};
Expand Down
2 changes: 1 addition & 1 deletion modules/src/assert/assert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn assert_internal(message: Option<String>) -> Result<()> {
Some(msg) => format!("Assertion Failed: {}", msg),
None => String::from("Assertion Failed"),
};
Err(Error::new(&error, None))
Err(Error::new(error, None))
}

#[js_fn]
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/event_loop/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl FutureQueue {
match item {
Ok(item) => results.push(item),
Err(error) => {
Error::new(&error.to_string(), ErrorKind::Normal).throw(cx);
Error::new(error.to_string(), ErrorKind::Normal).throw(cx);
return Err(None);
}
}
Expand Down
195 changes: 195 additions & 0 deletions runtime/src/globals/console/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

use std::fmt;
use std::fmt::{Display, Formatter, Write};

use mozjs::conversions::ConversionBehavior;

use ion::{BigInt, Context, Local, Result, Value};
use ion::conversions::FromValue;
use ion::format::{format_value, ValueDisplay};
use ion::format::Config as FormatConfig;

use crate::config::{Config, LogLevel};
use crate::globals::console::INDENTS;

pub(crate) enum FormatArg<'cx> {
String(String),
Value { value: ValueDisplay<'cx>, spaced: bool },
}

impl FormatArg<'_> {
pub(crate) fn spaced(&self) -> bool {
match self {
FormatArg::String(_) => false,
FormatArg::Value { spaced, .. } => *spaced,
}
}
}

impl Display for FormatArg<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
FormatArg::String(string) => string.fmt(f),
FormatArg::Value { value, .. } => value.fmt(f),
}
}
}

pub(crate) fn format_args<'cx>(cx: &'cx Context, args: &'cx [Value<'cx>]) -> Vec<FormatArg<'cx>> {
fn inner<'cx>(cx: &'cx Context, args: &'cx [Value<'cx>]) -> Result<Vec<FormatArg<'cx>>> {
if args.len() <= 1 || !args[0].get().is_string() {
return Ok(format_value_args(cx, args.iter()).collect());
}

let format = String::from_value(cx, &args[0], true, ())?;

if format.is_empty() {
return Ok(format_value_args(cx, args.iter()).collect());
}

let mut outputs = Vec::new();
let mut output = String::with_capacity(format.len());

let mut args = args.iter().skip(1).peekable();
let mut index = 0;

for (base, _) in format.match_indices('%') {
if base < index {
continue;
}

output.push_str(&format[index..base]);
index = base + 1;

match get_ascii_at(&format, index) {
next @ (Some(b'%') | None) => {
if next.is_some() || index == format.len() {
output.push('%');
}
index += 1;
}
Some(b'0'..=b'9') | Some(b'.') | Some(b'd') | Some(b'i') | Some(b'f') => {
let arg = args.next().unwrap();

let (w_len, width) = parse_maximum(&format[index..]).unzip();
index += w_len.unwrap_or(0);
let (p_len, precision) = get_ascii_at(&format, index)
.filter(|b| *b == b'.')
.and_then(|_| parse_maximum(&format[index + 1..]))
.unzip();
index += p_len.map(|len| len + 1).unwrap_or(0);

match get_ascii_at(&format, index) {
Some(b'd') | Some(b'i') => {
if arg.get().is_symbol() {
output.push_str("NaN");
} else if arg.get().is_bigint() {
let bigint = BigInt::from(unsafe { Local::from_marked(&arg.get().to_bigint()) });
output.push_str(&bigint.to_string(cx, 10).unwrap().to_owned(cx).unwrap());
} else {
write_printf(
&mut output,
width,
precision,
i32::from_value(cx, arg, false, ConversionBehavior::Default)?,
)
.unwrap();
}
index += 1;
}
Some(b'f') => {
if arg.get().is_symbol() {
output.push_str("NaN");
} else {
write_printf(&mut output, width, precision, f64::from_value(cx, arg, false, ())?)
.unwrap();
}
index += 1;
}
_ => output.push_str(&format[base..index]),
}
}
Some(next @ (b's' | b'o' | b'O')) => {
let arg = args.next().unwrap();
index += 1;

match next {
b's' => output.push_str(&String::from_value(cx, arg, false, ())?),
b'o' | b'O' => {
outputs.push(FormatArg::String(output));
output = String::with_capacity(format.len() - index);

outputs.push(FormatArg::Value {
value: format_value(cx, FormatConfig::default().indentation(INDENTS.get()), arg),
spaced: false,
});
}
_ => unreachable!(),
}
}
Some(b'c') => {
index += 1;
}
Some(b) => {
output.push('%');
output.push(char::from(b));
index += 1;
}
};

if args.peek().is_none() {
output.push_str(&format[index..]);
break;
}
}

outputs.push(FormatArg::String(output));
outputs.extend(format_value_args(cx, args));
Ok(outputs)
}

inner(cx, args).unwrap_or_else(|error| {
if Config::global().log_level >= LogLevel::Warn {
eprintln!("{}", error.format());
}
Vec::new()
})
}

pub(crate) fn format_value_args<'cx>(
cx: &'cx Context, args: impl Iterator<Item = &'cx Value<'cx>>,
) -> impl Iterator<Item = FormatArg<'cx>> {
args.map(|arg| FormatArg::Value {
value: format_value(cx, FormatConfig::default().indentation(INDENTS.get()), arg),
spaced: true,
})
}

fn get_ascii_at(str: &str, index: usize) -> Option<u8> {
str.as_bytes().get(index).copied().filter(|b| b.is_ascii())
}

fn parse_maximum(str: &str) -> Option<(usize, usize)> {
if str.is_empty() || !str.as_bytes()[0].is_ascii_digit() {
return None;
}

let end = str.bytes().position(|b| !b.is_ascii_digit()).unwrap_or(str.len());
Some((end, str[..end].parse().unwrap()))
}

fn write_printf<D: Display>(
output: &mut String, width: Option<usize>, precision: Option<usize>, display: D,
) -> fmt::Result {
match (width, precision) {
(Some(width), Some(precision)) => write!(output, "{:1$.2$}", display, width, precision),
(Some(width), None) => write!(output, "{:1$}", display, width),
(None, Some(precision)) => write!(output, "{:.1$}", display, precision),
(None, None) => write!(output, "{}", display),
}
}
Loading

0 comments on commit 6425540

Please sign in to comment.