diff --git a/std/src/std/string.inko b/std/src/std/string.inko index 13252801..d8208733 100644 --- a/std/src/std/string.inko +++ b/std/src/std/string.inko @@ -597,23 +597,24 @@ type builtin String { # "hello\\world" # => 'hello\\world' # ``` fn pub escaped -> String { + # If the String doesn't contain any special characters then we can/should + # avoid the allocations that take place below. + if escape?.false? { return self } + let buff = ByteArray.new - let max = ESCAPE_TABLE.size + let mut i = 0 - bytes.each(fn (byte) { - if byte >= max { - buff.push(byte) - return - } + while i < size { + let byte = byte_unchecked(i := i + 1) - match ESCAPE_TABLE.get(byte) { - case -1 -> buff.push(byte) - case byte -> { + match ESCAPE_TABLE.opt(byte) { + case Some(-1) or None -> buff.push(byte) + case Some(byte) -> { buff.push(BSLASH) buff.push(byte) } } - }) + } buff.into_string } @@ -678,6 +679,19 @@ type builtin String { fn inline byte_unchecked(index: Int) -> Int { (@ptr as Int + index as Pointer[UInt8]).0 as Int } + + fn inline escape? -> Bool { + let mut i = 0 + + while i < size { + match ESCAPE_TABLE.opt(byte_unchecked(i := i + 1)) { + case Some(-1) or None -> {} + case Some(_) -> return true + } + } + + false + } } impl Bytes for String { diff --git a/std/test/std/test_string.inko b/std/test/std/test_string.inko index 9f1263ac..83d324f5 100644 --- a/std/test/std/test_string.inko +++ b/std/test/std/test_string.inko @@ -294,6 +294,14 @@ fn pub tests(t: mut Tests) { t.equal('foo\u{8}bar'.escaped, 'foo\\bbar') }) + t.test('String.escape?', fn (t) { + t.false(''.escape?) + t.false('foo'.escape?) + t.false('foo bar'.escape?) + t.true('foo\nbar'.escape?) + t.true('foo\rbar'.escape?) + }) + t.test('String.fmt', fn (t) { t.equal(fmt('foo'), '"foo"') t.equal(fmt('"foo"'), '"\\"foo\\""')