///|
priv struct NameAlphabet {
first : String
rest : String
}
///|
const NAME_FIRST : String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$"
///|
const NAME_REST : String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$0123456789"
///|
const ASCII_CHARACTER_COUNT : Int32 = 128
///|
priv struct NameGenerator {
alphabet : NameAlphabet
reserved : @hashset.HashSet[String]
mut index : Int32
}
///|
fn NameGenerator::new(
alphabet : NameAlphabet,
reserved : @hashset.HashSet[String],
) -> NameGenerator {
{ alphabet, reserved, index: 0, }
}
///|
fn NameGenerator::next_name(self : NameGenerator) -> String {
for index = self.index {
let name = self.alphabet.short_name(index)
guard !self.reserved.contains(name) else { continue index + 1 }
self.index = index + 1
break name
}
}
///|
/// Count source characters directly; alphabet selection filters identifier chars.
fn count_characters(
source : String,
start : Int32,
end : Int32,
counts : FixedArray[Int32],
delta : Int32,
) -> Unit {
for index in start.. NameAlphabet {
let counts = program.unchanged_character_counts()
alphabet_by_frequency(counts)
}
///|
fn Program::unchanged_character_counts(program : Program) -> FixedArray[Int32] {
let counts = program.character_counts.copy()
// A source token may represent both a shorthand key and a reference.
// Decide removals first, then count each remaining deferred spelling once.
let accounted_positions = FixedArray::make(
program.source.length() / 32 + 1,
0U,
)
let remove_name = (start, end) => {
guard mark_position(accounted_positions, start) else { return }
if !deferred_identifier_span(program.source, start, end) {
count_characters(program.source, start, end, counts, -1)
}
}
for reference in program.references {
if reference.symbol >= 0 &&
(program.symbols[reference.symbol].flags & SYMBOL_PRESERVED) == 0 {
remove_name(reference.name_start, reference.name_end)
}
}
for id, _ in program.property_names {
let node = program.nodes[program.property_leaf(id)]
remove_name(node.a, node.b)
}
for id, _ in program.member_names {
let node = program.nodes[id]
remove_name(node.b, node.c)
}
let retain_name = (start, end) => {
guard mark_position(accounted_positions, start) &&
deferred_identifier_span(program.source, start, end) else {
return
}
count_characters(program.source, start, end, counts, 1)
}
for node in program.nodes {
match node.kind {
Labeled | Break | Continue if node.a >= 0 => remove_name(node.a, node.b)
Member => retain_name(node.b, node.c)
StringLiteral => retain_name(node.a, node.b)
_ => ()
}
}
for reference in program.references {
retain_name(reference.name_start, reference.name_end)
}
counts
}
///|
fn deferred_identifier_span(
source : String,
start : Int32,
end : Int32,
) -> Bool {
guard start < end else { return false }
match source[start] {
'\'' | '"' | '`' | '#' | '.' | '0'..<='9' => false
_ =>
keyword_of(source.view(start_offset=start, end_offset=end)) is NotKeyword
}
}
///|
fn mark_position(positions : FixedArray[UInt32], start : Int32) -> Bool {
let word_index = start / 32
let position_mask = 1U << (start % 32)
let word = positions[word_index]
guard (word & position_mask) == 0U else { return false }
positions[word_index] = word | position_mask
true
}
///|
fn alphabet_by_frequency(counts : FixedArray[Int32]) -> NameAlphabet {
let order = Array::makei(NAME_REST.length(), index => index)
order.sort_by((left, right) => {
let difference = counts[NAME_REST[right].to_int()] -
counts[NAME_REST[left].to_int()]
guard difference == 0 else { return difference }
left - right
})
let first = StringBuilder()
let rest = StringBuilder()
for index in order {
let character = NAME_REST.get_char(index).unwrap()
rest.write_char(character)
guard index < NAME_FIRST.length() else { continue }
first.write_char(character)
}
{ first: first.to_string(), rest: rest.to_string(), }
}