Skip to content

feat(ts): translate the rest of str's searches and text iterators - #461

Merged
moq-bot[bot] merged 2 commits into
mainfrom
quest/m3/ts-str-searches
Nov 2, 2026
Merged

moq-bot[bot] merged 2 commits into
mainfrom
quest/m3/ts-str-searches

Conversation

@kixelated

@kixelated kixelated commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

rust2-ts refused most of str's pattern searches and text iterators as unsupported calls: find, strip_prefix, strip_suffix, trim_matches, replace, rsplit, rsplit_terminator, splitn, rsplitn, split_inclusive, matches, rmatches, rmatch_indices, char_indices, lines, and split_whitespace (quest, found by #434). No consumer calls them yet. They are planned as part of std.

Approach

Each maps onto the runtime's Searcher and Split in rt/str.ts, by every pattern std takes: a char, a &str or &String, a &[char], a [char; N], and a closure.

  • Split gains std's SplitInternal next_inclusive and next_back_inclusive (for split_inclusive), and SplitNInternal's count (for splitn).
  • The r forms (rsplit, rsplit_terminator, rsplitn, rmatches, rmatch_indices) are rt.iterRev of the forward iterator, as std's are. Their clones map to { i: { f } }.
  • matches is a new Matches over the Searcher.
  • find, strip_prefix, strip_suffix, replace, and replacen are each one search. replace is replacen with no limit, and replacen asks for no match past its count, as std's take does.
  • trim_matches calls a closure from the front up to the char it rejects, then from the back down to just after that char, as std's next_reject and next_reject_back do.
  • lines is split_inclusive('\n') mapped through std's LinesMap. split_whitespace is split(char::is_whitespace) filtered to non-empty parts.
  • char_indices is a new CharIndices over Chars. It counts the bytes from the back only once that end first moves.
  • Fixed a latent bug: an empty &str needle searched from the back never moved past its first match, so it would loop. It now steps back one char between matches, as std's EmptyNeedle does.

Choices I made without asking:

  • I also mapped trim, trim_start, and trim_end (std's trim_matches(char::is_whitespace) and its one-sided forms), replacen, and char::is_whitespace. All of them use the same machinery, and split_whitespace needs rt.charIsWhitespace anyway.
  • I left out split_ascii_whitespace, because std's size hint for it comes from a byte-slice split rather than str's. I also left out the deprecated trim_left_matches, trim_right_matches, and lines_any, and the unstable remainder. Each of these still refuses loudly.

Tests: a new searches module in the lang fixture runs differentially against native wasm32:

  • searches::searches runs every search by each of the five pattern kinds. It reads each iterator from the front, from the back, or from both ends as the input bytes pick, then clones it after the first item and reads the clone from the back. The closure logs every char it is called with into a RefCell, so the output includes which chars std tests and in what order.
  • searches::texts covers char_indices, lines, split_whitespace, and the three trims over line ends and whitespace of every UTF-8 width. It also includes \u{200b} and \u{feff}, which are not White_Space (JS's \s counts \u{feff} as whitespace).
  • Named cases in caller.ts cover:
    • empty text;
    • empty needles and needles of one to three chars;
    • a separator alone, doubled, and at either end;
    • runs of a that a two-char needle overlaps in;
    • chars of every UTF-8 width;
    • \r\n, \n\n, and a \r with no \n;
    • Unicode whitespace;
    • counts from 0 to 4.

I mutated the runtime by hand 16 times. Mutations included: an inclusive part's end from either side, the count from either end, the clone's count, the trim_matches floor, strip_suffix testing the first char, lines keeping \r, dropping U+0085 from whitespace or adding U+FEFF, char_indices' back offset, replacen one past its count, find giving the match's end, the trailing-empty check, and the empty needle's back step. The named cases caught every one, with 2 to 760 differences each.

Impact

  • The translator's output: these calls translate where they were refused.
  • The runtime gains rt.strFind, strStripPrefix, strStripSuffix, strTrimMatches, strReplace, strRsplit, strRsplitTerminator, strSplitn, strRsplitn, strSplitInclusive, strMatches, strRmatches, strRmatchIndices, strCharIndices, strLines, strSplitWhitespace, and charIsWhitespace. Nothing breaks.

Alternatives

  • Translating std's own bodies of these instead of mapping them. That would need the Pattern/Searcher traits and get_unchecked by byte offsets, which the runtime's UTF-16 strings don't have.
  • One class per std iterator (RSplit, RSplitN, ...). rt.iterRev gives the same calls in the same order with less code.

Follow-ups

None found.

Review

Codex gpt-6.1-sol, medium effort, via quest review --base origin/main, round 1 on head 7be4c40efea473ede7bfce3bcd94e7d7adcc7996: no findings.

🤖 Generated with Claude Code

kixelated and others added 2 commits November 2, 2026 15:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title The rest of str's searches feat(ts): translate the rest of str's searches and text iterators Nov 2, 2026
@kixelated
kixelated marked this pull request as ready for review November 2, 2026 23:33
@coderabbitai

coderabbitai Bot commented Nov 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 196ab926-d8c1-47a1-af9e-df57b9245613
Commits

Reviewing files that changed from the base of the PR and between e7b4293 and 7be4c40.

Files selected for processing: 11
  • quest/m3/README.md
  • quest/m3/ts-str-searches.md
  • ts/fixtures/lang/caller.ts
  • ts/fixtures/lang/expected/harness.ts
  • ts/fixtures/lang/expected/searches.ts
  • ts/fixtures/lang/src/harness.rs
  • ts/fixtures/lang/src/lib.rs
  • ts/fixtures/lang/src/searches.rs
  • ts/runtime/rt/str.ts
  • ts/src/lower/iter.rs
  • ts/src/lower/library.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@kixelated
kixelated enabled auto-merge (squash) November 2, 2026 23:39
@moq-bot
moq-bot Bot merged commit 8a74099 into main Nov 2, 2026
5 checks passed
@moq-bot
moq-bot Bot deleted the quest/m3/ts-str-searches branch November 2, 2026 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant