silk.text.diff.
Line diffs, unified diff text, and reading and applying patches.
When to use#
Use unified to show how a file changed, as diff -u and git diff do, or to make a patch
that a later step applies. Use parse to read a patch from diff -u, git diff, or
git format-patch, and apply to apply one of its files to the original text. Use
diffLines or diffSymbols for the edits themselves, for example to show a side-by-side
view or to compare words instead of lines.
Details#
A line ends after each \n. A last line without \n is a line too, and it differs from the
same line with \n. Lines are compared byte for byte, so a \r before \n is part of the line.
The Algorithm chooses how the shared lines are found:
Algorithm.Histogramanchors the comparison on lines that are rare in both texts, asgit diff --histogramdoes. It is the fastest, and the diffs are easy to read. Use it unless there is a reason to use another algorithm.Algorithm.MyersMinimalalways finds the fewest deleted and inserted lines, asgit diff --minimaldoes. The time grows with the line count times the number of changed lines.Algorithm.Myersstops a long search after about the square root of the line count, asgit diffdoes by default, so very different texts do not take quadratic time. The diff can then have a few more changed lines than the minimum.
The edits are a list of Edit runs. unified groups the changes into hunks with
context unchanged lines around them, and writes \ No newline at end of file after a last
line without \n, so the text reads back with parse and applies with apply.
A Patch holds one FilePatch for each file. Its paths, hunks and lines are Span
ranges of the patch text, so the patch text must stay alive while the patch is in use.
apply puts each hunk where its old lines are, nearest to the line that the hunk header
gives, so a patch still applies after lines were added or removed before a hunk.
The Bytes forms, such as unifiedBytes, take and return bytes that need not be UTF-8, such
as file contents.
Examples#
Make a patch and apply it#
import silk.text.diff
import silk.text.diff { Algorithm, ApplyError, FilePatch, PatchError }
import silk.collections.list { List }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.text.string { String }
effect fn check() -> i32 ! PatchError | ApplyError | OutOfMemoryError ? &mut Allocator {
let old = "red\ngreen\nblue\n"
let new = "red\nyellow\nblue\n"
let text = run diff.unified("a/colors.txt", old, "b/colors.txt", new, 3, Algorithm.Histogram)
let expected = "--- a/colors.txt\n+++ b/colors.txt\n@@ -1,3 +1,3 @@\n red\n-green\n+yellow\n blue\n"
if String.view(&text) != expected {
return 1
}
let patch = run diff.parse(String.view(&text))
let file = &List<FilePatch>.asSlice(&patch.files)[0]
let patched = run diff.apply(old, String.view(&text), file)
if String.view(&patched) != new {
return 1
}
return 0
}
effect fn failed(error: PatchError | ApplyError | OutOfMemoryError) -> i32 {
drop error
return 2
}
pub fn main() -> i32 {
let mut allocator = Allocator.systemAllocatorProvider()
return run Effect.catchAll(check(), failed) |> Effect.provideMut<Allocator>(&mut allocator)
}Import as diff with import silk.text.diff as diff.
Public declarations: 22.
Algorithm#
pub enum AlgorithmThe algorithm that finds the lines two texts share.
When to use#
Use Algorithm.Histogram unless there is a reason to use another algorithm. It is the
fastest, and its diffs are easy to read.
Details#
All three algorithms give correct edits: applied to the old text, they give the new text. They
can differ in which lines they keep when more than one choice has the same cost, and
Algorithm.Myers and Algorithm.Histogram can change a few more lines than the minimum.
Myers#
MyersMyers' algorithm, as git diff uses by default. A search that takes longer than about the
square root of the line count stops at its best point so far, so the time stays low for very
different texts. The diff can then have a few more changed lines than the minimum.
MyersMinimal#
MyersMinimalMyers' algorithm with no limit, as git diff --minimal. The diff has the fewest deleted and
inserted lines. The time grows with the line count times the number of changed lines.
Histogram#
HistogramThe histogram algorithm, as git diff --histogram. It matches the lines that are rare in
both texts first, then the lines around them. It is the fastest of the three, and it keeps
rare lines, such as function headers, as anchors.
EditKind#
pub enum EditKindWhat one Edit does: keep, delete, or insert its lines.
Equal#
EqualThe lines are in both sequences.
Delete#
DeleteThe lines are only in the old sequence.
Insert#
InsertThe lines are only in the new sequence.
Edit#
pub struct EditOne run of lines that are equal, deleted, or inserted.
Details#
The runs of a diff cover both sequences in order, with no gaps: each run starts where the last
one ended. An Equal run covers length lines of each sequence. A Delete run covers
length old lines and starts no new lines, and an Insert run covers length new lines and
starts no old lines. Where lines change, the Delete run comes before the Insert run. Line
indices start from 0.
Field kind#
pub kind: EditKindWhat the run does.
Field oldStart#
pub oldStart: usizeThe index of the first line of the run in the old sequence.
Field newStart#
pub newStart: usizeThe index of the first line of the run in the new sequence.
Field length#
pub length: usizeThe number of lines in the run.
Implementation Copy for Edit#
impl Copy for EditdiffSymbols#
pub effect fn diffSymbols(old: &[u32], new: &[u32], algorithm: Algorithm) -> List<Edit>
! OutOfMemoryError
? &mut AllocatorReturns the edits that turn the sequence old into new, where each number is one symbol.
When to use#
Use this function to compare sequences of things other than lines, such as words or tokens:
give equal things equal numbers, then compare the numbers. For lines of text, use
diffLines.
Details#
The runs cover both sequences in order. Where symbols change, a Delete run comes before the
Insert run. The memory used grows with the sequence lengths and with the largest symbol, so
number the symbols from 0.
Errors#
OutOfMemoryErrorif theAllocatorcannot supply the scratch storage or the result.
Examples#
Compare two sequences of word numbers#
import silk.text.diff
import silk.text.diff { Algorithm, Edit, EditKind }
import silk.collections.list { List }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
effect fn check() -> i32 ! OutOfMemoryError ? &mut Allocator {
// "the quick fox" against "the slow fox", one number per word.
let old: [u32; 3] = [0, 1, 2]
let new: [u32; 3] = [0, 3, 2]
let edits = run diff.diffSymbols(&old, &new, Algorithm.Histogram)
let runs = List<Edit>.asSlice(&edits)
if runs.length != 4 || runs[1].kind != EditKind.Delete || runs[2].kind != EditKind.Insert {
return 1
}
return 0
}
effect fn exhausted(error: OutOfMemoryError) -> i32 {
drop error
return 2
}
pub fn main() -> i32 {
let mut allocator = Allocator.systemAllocatorProvider()
return run Effect.catchAll(check(), exhausted) |> Effect.provideMut<Allocator>(&mut allocator)
}diffLines#
pub effect fn diffLines(old: string, new: string, algorithm: Algorithm) -> List<Edit>
! OutOfMemoryError
? &mut AllocatorReturns the edits that turn the lines of old into the lines of new.
When to use#
Use this function when the edits themselves are needed, for example to show a side-by-side
view or to count changed lines. Use unified for diff text.
Details#
A line ends after each \n. A last line without \n is a line too, and it differs from the
same line with \n. Lines are compared byte for byte. The runs cover both texts in order, with
line indices from 0. Where lines change, a Delete run comes before the Insert run.
Errors#
OutOfMemoryErrorif theAllocatorcannot supply the scratch storage or the result.
Examples#
Count the changed lines#
import silk.text.diff
import silk.text.diff { Algorithm, Edit, EditKind }
import silk.collections.list { List }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
effect fn check() -> i32 ! OutOfMemoryError ? &mut Allocator {
let edits = run diff.diffLines("a\nb\nc\n", "a\nc\nd\n", Algorithm.MyersMinimal)
let runs = List<Edit>.asSlice(&edits)
let mut deleted: usize = 0
let mut inserted: usize = 0
let mut at: usize = 0
while at < runs.length {
if runs[at].kind == EditKind.Delete {
deleted = deleted + runs[at].length
} else if runs[at].kind == EditKind.Insert {
inserted = inserted + runs[at].length
}
at = at + 1
}
if deleted != 1 || inserted != 1 {
return 1
}
return 0
}
effect fn exhausted(error: OutOfMemoryError) -> i32 {
drop error
return 2
}
pub fn main() -> i32 {
let mut allocator = Allocator.systemAllocatorProvider()
return run Effect.catchAll(check(), exhausted) |> Effect.provideMut<Allocator>(&mut allocator)
}diffLinesBytes#
pub effect fn diffLinesBytes(old: &[u8], new: &[u8], algorithm: Algorithm) -> List<Edit>
! OutOfMemoryError
? &mut AllocatorReturns the edits that turn the lines of the bytes old into the lines of new.
When to use#
Use this function for file contents and other bytes that need not be UTF-8. For text, use
diffLines.
Details#
A line ends after each \n byte. A last line without \n is a line too, and it differs from
the same line with \n. Lines are compared byte for byte. The runs cover both inputs in
order, with line indices from 0. Where lines change, a Delete run comes before the Insert
run.
Errors#
OutOfMemoryErrorif theAllocatorcannot supply the scratch storage or the result.
unified#
pub effect fn unified(
oldName: string,
old: string,
newName: string,
new: string,
context: usize,
algorithm: Algorithm,
) -> String
! OutOfMemoryError
? &mut AllocatorReturns the unified diff from old to new, with context unchanged lines around each change.
When to use#
Use this function to show how a text changed, or to make a patch for apply or for tools
such as patch and git apply.
Details#
The diff starts with --- oldName and +++ newName, then has one @@ -start,count +start,count
@@ hunk for each group of changes. Changes with at most 2 * context unchanged lines between
them share a hunk. Each line starts with ' ' for an unchanged line, '-' for a deleted line,
or '+' for an inserted line. A last line without \n is followed by \n\ No newline at end
of file\n. The names are written as given, so pass a/path and b/path for the form that
git apply expects. The usual context is 3.
Equal texts give an empty diff, with no header.
Errors#
OutOfMemoryErrorif theAllocatorcannot supply the scratch storage or the result.
Gotchas#
A name that contains a tab or a newline makes a header that other tools read differently.
Examples#
Write a diff with one line of context#
import silk.text.diff
import silk.text.diff { Algorithm }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.text.string { String }
effect fn check() -> i32 ! OutOfMemoryError ? &mut Allocator {
let old = "one\ntwo\nthree\nfour\n"
let new = "one\n2\nthree\nfour"
let text = run diff.unified("a/n.txt", old, "b/n.txt", new, 1, Algorithm.Histogram)
let expected = "--- a/n.txt\n+++ b/n.txt\n@@ -1,4 +1,4 @@\n one\n-two\n+2\n three\n-four\n+four\n\\ No newline at end of file\n"
if String.view(&text) != expected {
return 1
}
return 0
}
effect fn exhausted(error: OutOfMemoryError) -> i32 {
drop error
return 2
}
pub fn main() -> i32 {
let mut allocator = Allocator.systemAllocatorProvider()
return run Effect.catchAll(check(), exhausted) |> Effect.provideMut<Allocator>(&mut allocator)
}unifiedBytes#
pub effect fn unifiedBytes(
oldName: &[u8],
old: &[u8],
newName: &[u8],
new: &[u8],
context: usize,
algorithm: Algorithm,
) -> Bytes
! OutOfMemoryError
? &mut AllocatorReturns the unified diff from the bytes old to new, with context unchanged lines around
each change.
When to use#
Use this function for file contents and paths that need not be UTF-8. For text, use
unified.
Details#
The diff starts with --- oldName and +++ newName, then has one @@ -start,count +start,count
@@ hunk for each group of changes. Changes with at most 2 * context unchanged lines between
them share a hunk. Each line starts with ' ', '-', or '+'. A last line without \n is
followed by \n\ No newline at end of file\n. Equal inputs give an empty diff, with no
header.
Errors#
OutOfMemoryErrorif theAllocatorcannot supply the scratch storage or the result.
Span#
pub struct SpanA range of bytes in a patch text.
Details#
The parts of a Patch are spans of the text that parse read, so they do not copy it. Use
Span.text or Span.bytes with that same text to get the part.
Field offset#
pub offset: usizeThe offset of the first byte from the start of the source.
Field length#
pub length: usizeThe number of bytes.
Implementation Copy for Span#
impl Copy for SpanImplementation Span#
impl SpanMethod text#
pub fn text<'source>(self: &Self, source: string<'source>) -> string<'source>Returns the part of source that the span covers.
Gotchas#
source must be the text that the span came from. The function traps if the span ends
past the end of source, or does not start and end on character boundaries.
Method bytes#
pub fn bytes<'source>(self: &Self, source: &'source [u8]) -> &'source [u8]Returns the bytes of source that the span covers.
Gotchas#
source must be the bytes that the span came from. The function traps if the span ends past
the end of source.
LineKind#
pub enum LineKindWhat one line of a hunk does: keep, delete, or insert it.
Context#
ContextThe line is in the old and the new file.
Delete#
DeleteThe line is only in the old file.
Insert#
InsertThe line is only in the new file.
PatchLine#
pub struct PatchLineOne line of a hunk: what it does, and its content in the patch text.
Field kind#
pub kind: LineKindWhat the line does.
Field text#
pub text: SpanThe content of the line after its ' ', '-', or '+' prefix, with its \n. A line that
a \ No newline at end of file line follows has no \n. A context line that is empty in
the patch, because a tool removed its space, is the \n alone.
Implementation Copy for PatchLine#
impl Copy for PatchLineHunk#
pub struct HunkOne hunk of a file patch: its @@ header and where its lines are.
Details#
The numbers are as the header gives them. A range with no lines, such as -0,0, gives the line
before the change, so its start is one less than for a range with lines. The lines of the hunk
are lineCount items of FilePatch.lines from firstLine.
Field oldStart#
pub oldStart: usizeThe line number of the first old line, from 1, or of the line before the change for an empty range.
Field oldLength#
pub oldLength: usizeThe number of context and deleted lines.
Field newStart#
pub newStart: usizeThe line number of the first new line, from 1, or of the line before the change for an empty range.
Field newLength#
pub newLength: usizeThe number of context and inserted lines.
Field section#
pub section: SpanThe text after the closing @@, such as a function name. Empty if there is none.
Field firstLine#
pub firstLine: usizeThe index of the first line of the hunk in FilePatch.lines.
Field lineCount#
pub lineCount: usizeThe number of lines of the hunk in FilePatch.lines.
Implementation Copy for Hunk#
impl Copy for HunkFileChange#
pub enum FileChangeWhat a file patch does to its file: change, create, delete, rename, or copy it.
Details#
A git patch says so in its extended header lines, such as new file mode or rename from.
Other patches create a file when the old path is /dev/null, and delete one when the new path
is /dev/null.
Modified#
ModifiedThe file changes in place.
Created#
CreatedThe file is new.
Deleted#
DeletedThe file is removed.
Renamed#
RenamedThe file moves to a new path, and can also change.
Copied#
CopiedA copy of the file is made at a new path, and the copy can also change.
FilePatch#
pub struct FilePatchThe changes to one file: its paths, modes, and hunks.
Details#
The paths and lines are Span ranges of the patch text. In a git patch, the paths lose their
a/ and b/ prefixes, so they are relative to the repository. In other patches they are as
written in the --- and +++ lines, up to a tab. Use apply to apply the hunks to the old
text.
Field change#
pub change: FileChangeWhat the patch does to the file.
Field oldPath#
pub oldPath: Option<Span>The path of the old file, or none for /dev/null or a patch that gives no path.
Field newPath#
pub newPath: Option<Span>The path of the new file, or none for /dev/null or a patch that gives no path.
Field oldMode#
pub oldMode: Option<u32>The old file mode of a git patch, such as 33188 for 100644 in octal.
Field newMode#
pub newMode: Option<u32>The new file mode of a git patch, such as 33261 for 100755 in octal.
Field binary#
pub binary: booltrue if the patch changes binary content that it does not give as hunks.
Field hunks#
pub hunks: List<Hunk>The hunks, in order.
Field lines#
pub lines: List<PatchLine>The lines of all hunks, in order.
Patch#
pub struct PatchThe file patches of one patch text, in order.
Details#
Its parts are Span ranges of the text that parse read, so keep that text alive while the
patch is in use.
Field files#
pub files: List<FilePatch>One item for each file, in the order of the patch.
PatchReason#
pub enum PatchReasonWhy parse cannot read a patch.
InvalidHunkHeader#
InvalidHunkHeaderAn @@ line is not a valid hunk header.
InvalidHunkLine#
InvalidHunkLineA hunk line does not start with ' ', '-', '+' or '\\', or there are more old or new
lines than the hunk header gives.
UnexpectedEnd#
UnexpectedEndThe patch ends before the hunk has all the lines that its header gives.
MissingFileHeader#
MissingFileHeaderA hunk comes before any file header.
InvalidMode#
InvalidModeA file mode is not an octal number.
Implementation Debug for PatchReason#
impl Debug for PatchReasonOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
! WriterError
? &mut WriterWrites the name of the variant, such as InvalidHunkHeader.
PatchError#
pub struct PatchErrorThe error from parse or parseBytes: why the patch cannot be read, and on which line.
Field reason#
pub reason: PatchReasonWhy the patch cannot be read.
Field line#
pub line: usizeThe line number of the problem, from 1.
Implementation Debug for PatchError#
impl Debug for PatchErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
! WriterError
? &mut WriterWrites the fields, as PatchError { reason: ..., line: ... }.
ApplyError#
pub struct ApplyErrorThe error from apply or applyBytes: a hunk whose old lines are not in the original.
Field hunk#
pub hunk: usizeThe index of the hunk in FilePatch.hunks.
Implementation Debug for ApplyError#
impl Debug for ApplyErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> ()
! WriterError
? &mut WriterWrites the fields, as ApplyError { hunk: ... }.
parse#
pub effect fn parse(text: string) -> Patch
! PatchError | OutOfMemoryError
? &mut AllocatorReads the patch text, as diff -u, git diff, and git format-patch write it.
When to use#
Use this function to read a patch before apply applies its files, or to list the files and
changes that a patch makes.
Details#
A file starts at a diff --git line, or at a --- line that a +++ line follows. Git
extended header lines give the modes, renames, copies, and binary changes. Each hunk reads as
many lines as its @@ header gives, and a \ No newline at end of file line removes the \n
of the line before it. Other lines, such as a commit message or index lines, are skipped.
The parts of the result are Span ranges of text.
Errors#
PatchErrorwith reasonInvalidHunkHeaderfor an@@line that is not a hunk header,InvalidHunkLinefor a hunk line that does not start with' ','-','+', or'\\'or that is more than the header gives,UnexpectedEndif the text ends in a hunk,MissingFileHeaderfor a hunk before any file header, andInvalidModefor a mode that is not octal.OutOfMemoryErrorif theAllocatorcannot supply the result.
Gotchas#
Quoted git paths, which git writes for names with special characters, keep their quotes and escapes.
Examples#
List the files of a git patch#
import silk.text.diff
import silk.text.diff { FileChange, FilePatch, PatchError, Span }
import silk.collections.list { List }
import silk.core.option { Option }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
effect fn check() -> i32 ! PatchError | OutOfMemoryError ? &mut Allocator {
let text = "diff --git a/src/app.js b/src/app.js\nindex 1111111..2222222 100644\n--- a/src/app.js\n+++ b/src/app.js\n@@ -1 +1 @@\n-let x = 1\n+let x = 2\ndiff --git a/notes.txt b/notes.txt\nnew file mode 100644\n--- /dev/null\n+++ b/notes.txt\n@@ -0,0 +1 @@\n+hello\n"
let patch = run diff.parse(text)
let files = List<FilePatch>.asSlice(&patch.files)
if files.length != 2 || files[1].change != FileChange.Created {
return 1
}
let path = match files[0].newPath {
Option<Span>.Some {value} => value.text(text)
Option<Span>.None => ""
}
if path != "src/app.js" {
return 1
}
return 0
}
effect fn failed(error: PatchError | OutOfMemoryError) -> i32 {
drop error
return 2
}
pub fn main() -> i32 {
let mut allocator = Allocator.systemAllocatorProvider()
return run Effect.catchAll(check(), failed) |> Effect.provideMut<Allocator>(&mut allocator)
}parseBytes#
pub effect fn parseBytes(text: &[u8]) -> Patch
! PatchError | OutOfMemoryError
? &mut AllocatorReads the patch text, given as bytes that need not be UTF-8, as diff -u, git diff, and
git format-patch write it.
When to use#
Use this function for a patch read from a file, whose contents need not be UTF-8. For text, use
parse.
Details#
A file starts at a diff --git line, or at a --- line that a +++ line follows. Git
extended header lines give the modes, renames, copies, and binary changes. Each hunk reads as
many lines as its @@ header gives, and a \ No newline at end of file line removes the \n
of the line before it. Other lines are skipped. The parts of the result are Span ranges of
text.
Errors#
PatchErrorwith reasonInvalidHunkHeader,InvalidHunkLine,UnexpectedEnd,MissingFileHeader, orInvalidMode, and the line number of the problem.OutOfMemoryErrorif theAllocatorcannot supply the result.
apply#
pub effect fn apply(original: string, patch: string, file: &FilePatch) -> String
! ApplyError | OutOfMemoryError
? &mut AllocatorReturns original with the hunks of file applied, where file was read from patch.
When to use#
Use this function to apply one file of a patch that parse read, for example to patch a
dependency before a build.
Details#
The hunks apply in order. Each hunk goes where its context and deleted lines are the lines of
original, first at the line that its header gives, adjusted by how far the last hunk moved,
then at the nearest line before or after that place. Lines are compared byte for byte,
including the \n. A new file applies to an empty original. Only the hunks change the text:
renames, modes, and binary changes are for the caller to make.
Errors#
ApplyErrorwith the index of the first hunk whose old lines are not inoriginalafter the last applied hunk.OutOfMemoryErrorif theAllocatorcannot supply the result.
Gotchas#
patch must be the text that file was read from, because the lines of file are ranges of
it.
Examples#
Apply a hunk after the file grew#
import silk.text.diff
import silk.text.diff { ApplyError, FilePatch, PatchError }
import silk.collections.list { List }
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.text.string { String }
effect fn check() -> i32 ! PatchError | ApplyError | OutOfMemoryError ? &mut Allocator {
let text = "--- a/list.txt\n+++ b/list.txt\n@@ -1,3 +1,3 @@\n apple\n-banana\n+cherry\n melon\n"
let patch = run diff.parse(text)
let file = &List<FilePatch>.asSlice(&patch.files)[0]
let patched = run diff.apply("fig\napple\nbanana\nmelon\n", text, file)
if String.view(&patched) != "fig\napple\ncherry\nmelon\n" {
return 1
}
return 0
}
effect fn failed(error: PatchError | ApplyError | OutOfMemoryError) -> i32 {
drop error
return 2
}
pub fn main() -> i32 {
let mut allocator = Allocator.systemAllocatorProvider()
return run Effect.catchAll(check(), failed) |> Effect.provideMut<Allocator>(&mut allocator)
}applyBytes#
pub effect fn applyBytes(original: &[u8], patch: &[u8], file: &FilePatch) -> Bytes
! ApplyError | OutOfMemoryError
? &mut AllocatorReturns the bytes original with the hunks of file applied, where file was read from the
bytes patch.
When to use#
Use this function for file contents that need not be UTF-8. For text, use apply.
Details#
The hunks apply in order. Each hunk goes where its context and deleted lines are the lines of
original, first at the line that its header gives, adjusted by how far the last hunk moved,
then at the nearest line before or after that place. Lines are compared byte for byte,
including the \n. Only the hunks change the bytes: renames, modes, and binary changes are for
the caller to make.
Errors#
ApplyErrorwith the index of the first hunk whose old lines are not inoriginalafter the last applied hunk.OutOfMemoryErrorif theAllocatorcannot supply the result.
Gotchas#
patch must be the bytes that file was read from, because the lines of file are ranges of
them.
Category: Text.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.