···49495050 ([Giacomo Cavalieri](https://github.com/giacomocavalieri))
51515252+- The compiler now emits more efficient code when matching on single-character
5353+ string prefixes on the JavaScript target. For example, the `glance` package
5454+ is now nearly 30% faster on the JavaScript target:
5555+5656+ ```
5757+ # before:
5858+ min: 10.8ms, max: 365.82ms, median: 14.74ms, mean: 14.76ms
5959+ warmup: 100/1.5s, total post-warmup: 1000/14.76s
6060+6161+ # after:
6262+ min: 8.96ms, max: 143.76ms, median: 10.72ms, mean: 11.06ms
6363+ warmup: 100/1.24s, total post-warmup: 1000/11.06s
6464+ ```
6565+6666+ ([Surya Rose](https://github.com/GearsDatapacks))
6767+5268### Build tool
53695470- The `gleam hex owner add` command has been added, which allows adding
···408408 assignments.push(let_doc(name, value.to_doc()))
409409 };
410410411411+ // Variable storing the character code for the first character of a string.
412412+ // This is only declared if multiple patterns match on just the first
413413+ // character, as it allows us to avoid calling `.startsWith` multiple times
414414+ // and just call `.charCodeAt(0)` once, which is much faster.
415415+ let first_character_variable = if multiple_single_character_prefix_checks(choices) {
416416+ let name = self.variables.next_local_var(&ASSIGNMENT_VAR.into());
417417+ let string = self.variables.get_value(var);
418418+ let first_character = docvec![string, ".charCodeAt(0)"];
419419+ assignments.push(let_doc(name.clone(), first_character));
420420+ Some(name)
421421+ } else {
422422+ None
423423+ };
424424+411425 let mut if_ = CaseBody::Statements(nil());
412426 for (i, (check, decision)) in choices.iter().enumerate() {
413427 self.variables.record_check_assignments(var, check);
···419433 // referenced by this check
420434 let (check_doc, body, mut segment_assignments) = self.inside_new_scope(|this| {
421435 let segment_assignments = this.variables.bit_array_segment_assignments(check);
422422- let check_doc = this.variables.runtime_check(var, check);
436436+437437+ // If the pattern matches on a single character, use the character
438438+ // code instead of `.startsWith`.
439439+ let check_doc = if let Some(code) = single_character_prefix_code(check) {
440440+ let first_character = if let Some(variable) = &first_character_variable {
441441+ variable.to_doc()
442442+ } else {
443443+ // This is the only single-character match in this `case`
444444+ // expression, so we don't bind it to a variable and just
445445+ // call `.charCodeAt` inline. This is still faster than
446446+ // `.startsWith`.
447447+ let string = this.variables.get_value(var);
448448+ docvec![string, ".charCodeAt(0)"]
449449+ };
450450+451451+ docvec![first_character, " === ", code]
452452+ } else {
453453+ this.variables.runtime_check(var, check)
454454+ };
455455+423456 let body = this.decision(decision);
424457 (check_doc, body, segment_assignments)
425458 });
···700733 ],
701734 )
702735 }
736736+}
737737+738738+/// Returns the character code for the character being matched for patterns matching
739739+/// on single-character string prefixes.
740740+fn single_character_prefix_code(check: &RuntimeCheck) -> Option<u32> {
741741+ match check {
742742+ // On JavaScript, a single "character" is one that can be represented as
743743+ // a single UTF-16 codepoint.
744744+ RuntimeCheck::StringPrefix { prefix, rest } if utf16_no_escape_len(prefix) == 1 => {
745745+ convert_string_escape_chars(prefix)
746746+ .chars()
747747+ .next()
748748+ .map(|first| first as u32)
749749+ }
750750+ RuntimeCheck::Int { .. }
751751+ | RuntimeCheck::Float { .. }
752752+ | RuntimeCheck::String { .. }
753753+ | RuntimeCheck::StringPrefix { .. }
754754+ | RuntimeCheck::Tuple { .. }
755755+ | RuntimeCheck::BitArray { .. }
756756+ | RuntimeCheck::Variant { .. }
757757+ | RuntimeCheck::NonEmptyList { .. }
758758+ | RuntimeCheck::EmptyList => None,
759759+ }
760760+}
761761+762762+/// Returns whether a `case` expression contains multiple patterns matching on
763763+/// the first character of a string.
764764+fn multiple_single_character_prefix_checks(choices: &[(RuntimeCheck, Decision)]) -> bool {
765765+ let mut encountered_check = false;
766766+767767+ for (check, _) in choices.iter() {
768768+ if let RuntimeCheck::StringPrefix { prefix, .. } = check
769769+ && utf16_no_escape_len(prefix) == 1
770770+ {
771771+ if encountered_check {
772772+ return true;
773773+ } else {
774774+ encountered_check = true;
775775+ }
776776+ }
777777+ }
778778+779779+ false
703780}
704781705782pub fn let_<'a>(
···3232export function main() {
3333 let tmp = new Wibble("wibble");
3434 let $ = tmp.wobble;
3535- if ($.startsWith("w")) {
3535+ if ($.charCodeAt(0) === 119) {
3636 let wibble = "w";
3737 let rest = $.slice(1);
3838 return wibble + rest;