Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
165 lines (145 sloc) 4.16 KB
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
module Regex;
class Match(
private sliceByteOffsets: (Int, Int) ~> String,
private matches: Array<Int>,
) uses Equality {
fun size(): Int {
this.matches.size() / 2
}
fun get(i: Int): String {
this.sliceByteOffsets(this.matches[i * 2], this.matches[i * 2 + 1])
}
fun ==(other: Match): Bool {
this.size() == other.size() && this.toArray() == other.toArray()
}
// Mostly useful for tests
fun toArray(): Array<String> {
Array::fillBy(this.size(), i -> this[i])
}
}
value class .Regex(private handle: Runtime.GCPointer) {
private static fun createWithoutCheck(
pattern: String,
flags: String = "",
): Regex {
options = 0x0800; // PCRE_UTF8
if (flags.contains("i")) {
!options = options.or(0x0001); // PCRE_CASELESS
};
if (flags.contains("m")) {
!options = options.or(0x0002); // PCRE_MULTILINE
};
Regex(initialize(pattern, options))
}
}
@cpp_runtime
native private fun initialize(pattern: String, flags: Int): Runtime.GCPointer;
module end;
module String;
extension class .String {
fun matches(regex: Regex): Bool {
this.match(regex).isSome()
}
fun match(regex: Regex): ?Regex.Match {
sliceByteOffsets = this.sliceByteOffsets;
matches = this.matchInternal(regex, 0);
if (matches.size() == 0) {
None()
} else {
Some(Regex.Match((start, end) ~> sliceByteOffsets(start, end), matches))
}
}
fun matchAll(regex: Regex): mutable Iterator<Regex.Match> {
mutable MatchIterator(this.sliceByteOffsets, this.matchInternal, regex)
}
fun replaceRegex(regex: Regex, callback: Regex.Match -> String): String {
output = mutable Vector[];
sliceByteOffsets = this.sliceByteOffsets;
i = 0;
while ({
matches = this.matchInternal(regex, i);
if (matches.size() == 0) {
false
} else {
start = matches[0];
end = matches[1];
if (i != start) {
output.push(sliceByteOffsets(i, start));
};
output.push(
callback(
Regex.Match((start, end) ~> sliceByteOffsets(start, end), matches),
),
);
!i = end;
true
}
}) void;
end = Unsafe.string_utf8_size(this);
if (i != end) {
output.push(sliceByteOffsets(i, end));
};
output.join("")
}
fun splitRegex(regex: Regex): mutable Iterator<String> {
mutable SplitRegexIterator(
this.sliceByteOffsets,
() ~> Unsafe.string_utf8_size(this),
this.matchInternal,
regex,
)
}
@cpp_runtime
private native fun sliceByteOffsets(start: Int, end: Int): String;
@cpp_runtime
private native fun matchInternal(regex: Regex, index: Int): Array<Int>;
}
mutable private class MatchIterator(
private sliceByteOffsets: (Int, Int) ~> String,
private matchInternal: (Regex, Int) ~> Array<Int>,
private regex: Regex,
private mutable i: Int = 0,
) extends Iterator<Regex.Match> {
mutable fun next(): ?Regex.Match {
matches = this.matchInternal(this.regex, this.i);
sliceByteOffsets = this.sliceByteOffsets;
if (matches.size() == 0) {
None()
} else {
// We want to go to the end of the matched string to restart the search.
// The first two offsets are the beginning and end of this string.
this.!i = matches[1];
Some(Regex.Match((start, end) ~> sliceByteOffsets(start, end), matches))
}
}
}
mutable private class SplitRegexIterator(
private sliceByteOffsets: (Int, Int) ~> String,
private byteLength: () ~> Int,
private matchInternal: (Regex, Int) ~> Array<Int>,
private regex: Regex,
private mutable i: Int = 0,
) extends Iterator<String> {
mutable fun next(): ?String {
if (this.i == -1) {
None()
} else {
matches = this.matchInternal(this.regex, this.i);
i = this.i;
if (matches.size() == 0) {
this.!i = -1;
Some(this.sliceByteOffsets(i, this.byteLength()))
} else {
this.!i = matches[1];
Some(this.sliceByteOffsets(i, matches[0]))
}
}
}
}
module end;