///|
priv struct DecimalSpelling {
mantissa_start : Int32
exponent_marker : Int32
is_integer : Bool
}
///|
fn decimal_spelling(
source : String,
start : Int32,
end : Int32,
) -> DecimalSpelling? {
let spelling = source.view(start_offset=start, end_offset=end)
let exponent_marker = lexmatch spelling with longest {
(re"^[0-9.]*[eE]", after=rest) => rest.start_offset() - 1
re"^[0-9.]*$" => end
_ => return None
}
let mantissa = source.view(start_offset=start, end_offset=exponent_marker)
let can_omit_leading_zero = mantissa =~ (re"^0\.[0-9.]", after=_)
Some({
mantissa_start: if can_omit_leading_zero {
start + 1
} else {
start
},
exponent_marker,
is_integer: mantissa =~ re"^[0-9]*$",
})
}
///|
fn integer_exponent_spelling(
source : String,
start : Int32,
end : Int32,
) -> String? {
let spelling = source.view(start_offset=start, end_offset=end)
// Leading-zero integers may be legacy octal.
guard !(spelling =~ (re"^0", after=_)) else { return None }
let trailing_zeros = lexmatch spelling {
(re"0+$" as zeros, before=_) => zeros.length()
_ => return None
}
let significant_end = end - trailing_zeros
let exponent = trailing_zeros.to_string()
let shortened_length = significant_end - start + 1 + exponent.length()
guard shortened_length < end - start else { return None }
let text = StringBuilder()
text.write_substring(source, start, significant_end - start)
text.write_string("e")
text.write_string(exponent)
Some(text.to_string())
}
///|
fn shortened_exponent_spelling(
source : String,
start : Int32,
end : Int32,
decimal : DecimalSpelling,
) -> String? {
let marker = decimal.exponent_marker
guard marker + 1 < end else { return None }
let exponent = source.view(start_offset=marker + 1, end_offset=end)
let (digits, has_minus) = lexmatch exponent with longest {
(re"^-", after=digits) => (digits, true)
(re"^\+", after=digits) => (digits, false)
_ => (exponent, false)
}
guard digits =~ re"^[0-9]+$" else { return None }
let (significant_start, exponent_is_zero) = lexmatch digits with longest {
(re"^0+", after=rest) =>
if rest.length() == 0 {
(end - 1, true)
} else {
(rest.start_offset(), false)
}
_ => (digits.start_offset(), false)
}
let needs_minus = has_minus && !exponent_is_zero
let mantissa_length = marker - decimal.mantissa_start
let exponent_length = end -
significant_start +
(if needs_minus { 1 } else { 0 })
guard mantissa_length + 1 + exponent_length < end - start else { return None }
let text = StringBuilder()
text.write_substring(source, decimal.mantissa_start, mantissa_length)
text.write_string("e")
if needs_minus {
text.write_string("-")
}
text.write_substring(source, significant_start, end - significant_start)
Some(text.to_string())
}
///|
fn compact_decimal_spelling(text : String) -> String {
let end = text.length()
guard decimal_spelling(text, 0, end) is Some(decimal) else { return text }
if decimal.exponent_marker < end {
return shortened_exponent_spelling(text, 0, end, decimal).unwrap_or(text)
}
if decimal.is_integer {
return integer_exponent_spelling(text, 0, end).unwrap_or(text)
}
text[decimal.mantissa_start:end].to_owned()
}
///|
/// Formatting is restricted to ordinary decimal fractions. A second parse
/// checks the complete binary64 value, including the sign bit of zero.
fn shortened_decimal_value(spelling : StringView) -> String? {
guard spelling =~ re"^(?:(?:0|[1-9][0-9]*)\.[0-9]*|\.[0-9]+)(?:[eE][+\-]?[0-9]+)?$" else {
return None
}
let value = @string.parse_double(spelling) catch { _ => return None }
guard !value.is_inf() && !value.is_nan() else { return None }
let shortened = compact_decimal_spelling(value.to_string())
guard shortened.length() < spelling.length() else { return None }
let reparsed = @string.parse_double(shortened[:]) catch { _ => return None }
guard reparsed.reinterpret_as_uint64() == value.reinterpret_as_uint64() else {
return None
}
Some(shortened)
}
///|
fn Printer::shortened_decimal_value(
self : Printer,
start : Int32,
end : Int32,
) -> String? {
let spelling = self.program.source[start:end]
guard self.decimal_spellings.get(spelling) is Some(cached) else {
let shortened = shortened_decimal_value(spelling)
self.decimal_spellings[spelling] = shortened
return shortened
}
cached
}
///|
/// Only rewrite decimal spellings whose numeric value is unchanged exactly.
fn Printer::number(self : Printer, start : Int32, end : Int32) -> Unit {
// No supported numeric rewrite can shorten a one- or two-character spelling.
guard end - start > 2 else { return self.span(start, end, true) }
let source = self.program.source
guard decimal_spelling(source, start, end) is Some(decimal) else {
self.span(start, end, true)
return
}
// Long fractions commonly repeat in generated drawing code. Cache both
// successful and unsuccessful conversions; short literals keep the fast path.
if end - start >= 16 &&
!decimal.is_integer &&
self.shortened_decimal_value(start, end) is Some(text) {
return self.text(text, true)
}
guard decimal.exponent_marker != end else {
// BigInt and other bases were excluded by the decimal scan.
guard decimal.is_integer &&
integer_exponent_spelling(source, start, end) is Some(text) else {
return self.span(decimal.mantissa_start, end, true)
}
return self.text(text, true)
}
guard shortened_exponent_spelling(source, start, end, decimal) is Some(text) else {
return self.span(start, end, true)
}
self.text(text, true)
}
///|
priv struct QuoteCosts {
unescaped_length : Int32
single_quotes : Int32
double_quotes : Int32
}
///|
fn quote_costs(source : String, start : Int32, end : Int32) -> QuoteCosts {
for index = start + 1, unescaped_length = end - start, single_quotes = 0, double_quotes = 0 {
guard index < end - 1 else {
break { unescaped_length, single_quotes, double_quotes, }
}
match quoted_string_piece(source, index, end) {
Unchanged(next) =>
continue next, unescaped_length, single_quotes, double_quotes
SingleQuote(next) =>
continue next, unescaped_length, single_quotes + 1, double_quotes
DoubleQuote(next) =>
continue next, unescaped_length, single_quotes, double_quotes + 1
EscapedSingleQuote(next) =>
continue next, unescaped_length - 1, single_quotes + 1, double_quotes
EscapedDoubleQuote(next) =>
continue next, unescaped_length - 1, single_quotes, double_quotes + 1
}
}
}
///|
priv enum QuotedStringPiece {
Unchanged(Int32)
SingleQuote(Int32)
DoubleQuote(Int32)
EscapedSingleQuote(Int32)
EscapedDoubleQuote(Int32)
}
///|
fn quoted_string_piece(
source : String,
start : Int32,
end : Int32,
) -> QuotedStringPiece {
lexmatch source.view(start_offset=start, end_offset=end) with longest {
(re"^[^\\'\"]+", after=rest) => Unchanged(rest.start_offset())
(re"^\\'", after=rest) => EscapedSingleQuote(rest.start_offset())
(re"^\\\"", after=rest) => EscapedDoubleQuote(rest.start_offset())
(re"^'", after=rest) => SingleQuote(rest.start_offset())
(re"^\"", after=rest) => DoubleQuote(rest.start_offset())
(re"^\\[\u0000-\u{10FFFF}]", after=rest) => Unchanged(rest.start_offset())
// Regex character classes exclude isolated UTF-16 surrogates.
(re"^\\", after=_) => Unchanged(start + 2)
_ => Unchanged(start + 1)
}
}
///|
fn QuoteCosts::preferred_quote(self : QuoteCosts, original : UInt16) -> UInt16 {
guard self.single_quotes != self.double_quotes else { return original }
if self.single_quotes < self.double_quotes {
'\''
} else {
'"'
}
}
///|
fn QuoteCosts::quoted_length(self : QuoteCosts, quote : UInt16) -> Int32 {
self.unescaped_length +
(if quote == '\'' { self.single_quotes } else { self.double_quotes })
}
///|
/// Change quote escapes only. Other escapes and line continuations retain
/// their source spelling, including escapes in directive-like strings.
fn Printer::string(self : Printer, start : Int32, end : Int32) -> Unit {
let source = self.program.source
guard end > start + 1 else {
self.span(start, end, true)
return
}
let spelling = source.view(start_offset=start, end_offset=end)
guard spelling =~ (re"^['\"]", after=_) else {
// Property keys and module aliases also use StringLiteral arena nodes.
self.span(start, end, true)
return
}
let costs = quote_costs(source, start, end)
let quote = costs.preferred_quote(source[start])
guard costs.quoted_length(quote) < end - start else {
self.span(start, end, false)
return
}
self.text(quoted_text(source, start, end, quote), false)
}
///|
fn quoted_text(
source : String,
start : Int32,
end : Int32,
quote : UInt16,
) -> String {
let text = StringBuilder()
let delimiter = if quote == '\'' { "'" } else { "\"" }
text.write_string(delimiter)
let remaining = for index = start + 1, segment_start = start + 1 {
guard index < end - 1 else { break segment_start }
let (next_index, character, is_escaped) : (Int32, UInt16, Bool) = match
quoted_string_piece(source, index, end) {
Unchanged(next) => continue next, segment_start
SingleQuote(next) => (next, '\'', false)
DoubleQuote(next) => (next, '"', false)
EscapedSingleQuote(next) => (next, '\'', true)
EscapedDoubleQuote(next) => (next, '"', true)
}
guard is_escaped || character == quote else {
continue next_index, segment_start
}
text.write_substring(source, segment_start, index - segment_start)
if character == quote {
text.write_string("\\")
}
text.write_substring(source, next_index - 1, 1)
continue next_index, next_index
}
// Copy contiguous source spans so UTF-16 surrogate pairs are never split.
text.write_substring(source, remaining, end - 1 - remaining)
text.write_string(delimiter)
text.to_string()
}