Skip to content

Commit

Permalink
Optimize String.escaped
Browse files Browse the repository at this point in the history
If the String doesn't contain any characters to escape then we can just
return the String as-is and avoid redundant allocations. This in turn
can improve performance of generating JSON a bit, as the JSON generator
uses String.escaped for generating string values.

Changelog: performance
  • Loading branch information
yorickpeterse committed Jan 20, 2025
1 parent 6307d38 commit a8dd525
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 10 deletions.
34 changes: 24 additions & 10 deletions std/src/std/string.inko
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions std/test/std/test_string.inko
Original file line number Diff line number Diff line change
Expand Up @@ -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\\""')
Expand Down

0 comments on commit a8dd525

Please sign in to comment.