Commit b1f25716 authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent 1380e37e
// TODO copyright/license // Copyright (C) 2017 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 2, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// Package xfmt provide addons to std fmt and strconv packages with focus on // Package xfmt provides addons to std fmt and strconv packages with focus on
// formatting text without allocations. // formatting text without allocations.
//
// For example if in fmt speak you have
//
// s := fmt.Sprintf("hello %q %d %x", "world", 1, []byte("data"))
//
// xfmt analog would be
//
// xbuf := xfmt.Buffer{}
// xbuf .S("hello ") .Qs("world") .C(' ') .D(1) .C(' ') .Xb([]byte("data"))
// s := xbuf.Bytes()
//
// xfmt.Buffer can be reused several times via Buffer.Reset() .
package xfmt package xfmt
import ( import (
...@@ -76,7 +98,7 @@ func (b *Buffer) Cb(c byte) *Buffer { ...@@ -76,7 +98,7 @@ func (b *Buffer) Cb(c byte) *Buffer {
} }
// AppendRune appends to be UTF-8 encoding of r // AppendRune appends to b UTF-8 encoding of r
func AppendRune(b []byte, r rune) []byte { func AppendRune(b []byte, r rune) []byte {
l := len(b) l := len(b)
b = xslice.Grow(b, utf8.UTFMax) b = xslice.Grow(b, utf8.UTFMax)
...@@ -142,3 +164,5 @@ func (b *Buffer) X016(x uint64) *Buffer { ...@@ -142,3 +164,5 @@ func (b *Buffer) X016(x uint64) *Buffer {
*b = AppendHex016(*b, x) *b = AppendHex016(*b, x)
return b return b
} }
// TODO Qs Qb ?
...@@ -8,29 +8,30 @@ import ( ...@@ -8,29 +8,30 @@ import (
"testing" "testing"
) )
var testv = []struct {format, xformatMeth string; value interface{}} {
{"%c", "Cb", byte('A')},
{"%c", "C", rune(-1)},
{"%c", "C", 'B'}, // 1-byte encoded
{"%c", "C", 'и'}, // 2-bytes encoded
{"%c", "C", '\u20ac'}, // 3-bytes encoded
{"%c", "C", '\U00010001'}, // 4-bytes encoded
// TODO %q qb qr qs qcb qc
{"%s", "S", "hello"},
{"%s", "Sb", []byte("world")},
{"%x", "Xb", []byte("hexstring")},
{"%x", "Xs", "stringhex"},
{"%d", "D", 12765},
{"%x", "X", 12789},
{"%016x", "X016", uint64(124)},
// TODO .V
}
// verify formatting result is the same in between std fmt and xfmt // verify formatting result is the same in between std fmt and xfmt
func TestXFmt(t *testing.T) { func TestXFmt(t *testing.T) {
testv := []struct {format, xformatMeth string; value interface{}} {
{"%c", "Cb", byte('A')},
{"%c", "C", rune(-1)},
{"%c", "C", 'B'}, // 1-byte encoded
{"%c", "C", 'и'}, // 2-bytes encoded
{"%c", "C", '\u20ac'}, // 3-bytes encoded
{"%c", "C", '\U00010001'}, // 4-bytes encoded
// TODO %q qb qr qs qcb qc
{"%s", "S", "hello"},
{"%s", "Sb", []byte("world")},
{"%x", "Xb", []byte("hexstring")},
{"%x", "Xs", "stringhex"},
{"%d", "D", 12765},
{"%x", "X", 12789},
{"%016x", "X016", uint64(124)},
// TODO .V
}
buf := &Buffer{} buf := &Buffer{}
xbuf := reflect.ValueOf(buf) xbuf := reflect.ValueOf(buf)
...@@ -81,10 +82,46 @@ func TestXFmt(t *testing.T) { ...@@ -81,10 +82,46 @@ func TestXFmt(t *testing.T) {
} }
} }
func BenchmarkFmt(t *testing.T) { func BenchmarkXFmt(b *testing.B) {
// TODO buf := &Buffer{}
}
func BenchmarkXFmt(t *testing.T) { for _, tt := range testv {
// TODO b.Run(fmt.Sprintf("%s(%#v)", tt.format, tt.value), func (b *testing.B) {
for i := 0; i < b.N; i++ {
fmt.Sprintf(tt.format, tt.value)
}
})
// construct methProxy for natively calling (not via reflect) method associated with tt.xformatMeth
// (calling via reflect allocates a lot and is slow)
// NOTE because of proxies the call is a bit slower than e.g. directly calling buf.S("...")
var methProxy func(buf *Buffer, v interface{})
xmeth, ok := reflect.TypeOf(buf).MethodByName(tt.xformatMeth)
if !ok {
b.Errorf(".%v: no such method", tt.xformatMeth)
continue
}
// XXX a bit ugly -> use code generation instead?
meth := xmeth.Func.Interface()
switch tt.value.(type) {
case byte: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, byte) *Buffer)(buf, v.(byte)) }
case rune: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, rune) *Buffer)(buf, v.(rune)) }
case string: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, string) *Buffer)(buf, v.(string)) }
case []byte: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, []byte) *Buffer)(buf, v.([]byte)) }
case int: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, int) *Buffer)(buf, v.(int)) }
case uint64: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, uint64) *Buffer)(buf, v.(uint64)) }
default:
b.Fatalf("TODO add support for %T", tt.value)
}
b.Run(fmt.Sprintf(".%s(%#v)", tt.xformatMeth, tt.value), func (b *testing.B) {
for i := 0; i < b.N; i++ {
buf.Reset()
methProxy(buf, tt.value)
}
})
}
} }
package xmft package xfmt
import ( import (
"bytes" "bytes"
...@@ -9,20 +9,25 @@ import ( ...@@ -9,20 +9,25 @@ import (
) )
const hex = "0123456789abcdef" // TODO remove - not needed ?
// // pyQuote quotes string the way python repr(str) would do
// pyQuote quotes string the way python repr(str) would do // func pyQuote(s string) string {
func pyQuote(s string) string { // out := pyQuoteBytes(mem.Bytes(s))
out := pyQuoteBytes(mem.Bytes(s)) // return mem.String(out)
return mem.String(out) // }
} //
// func pyQuoteBytes(b []byte) []byte {
func pyQuoteBytes(b []byte) []byte { // buf := make([]byte, 0, (len(b) + 2) /* to reduce allocations when quoting */ * 2)
buf := make([]byte, 0, (len(b) + 2) /* to reduce allocations when quoting */ * 2) // return pyAppendQuoteBytes(buf, b)
return pyAppendQuoteBytes(buf, b) // }
// AppendQuotePy appends to buf Python quoting of s
func AppendQuotePy(buf []byte, s string) []byte {
return AppendQuotePyBytes(buf, mem.Bytes(s))
} }
func pyAppendQuoteBytes(buf, b []byte) []byte { // AppendQuotePyBytes appends to buf Python quoting of b
func AppendQuotePyBytes(buf, b []byte) []byte {
// smartquotes: choose ' or " as quoting character // smartquotes: choose ' or " as quoting character
// https://github.com/python/cpython/blob/v2.7.13-116-g1aa1803b3d/Objects/stringobject.c#L947 // https://github.com/python/cpython/blob/v2.7.13-116-g1aa1803b3d/Objects/stringobject.c#L947
quote := byte('\'') quote := byte('\'')
...@@ -38,7 +43,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte { ...@@ -38,7 +43,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte {
switch r { switch r {
case utf8.RuneError: case utf8.RuneError:
buf = append(buf, '\\', 'x', hex[b[0]>>4], hex[b[0]&0xf]) buf = append(buf, '\\', 'x', hexdigits[b[0]>>4], hexdigits[b[0]&0xf])
case '\\', rune(quote): case '\\', rune(quote):
buf = append(buf, '\\', byte(r)) buf = append(buf, '\\', byte(r))
case rune(noquote): case rune(noquote):
...@@ -57,7 +62,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte { ...@@ -57,7 +62,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte {
switch { switch {
case r < ' ': case r < ' ':
// we already converted to \<letter> what python represents as such above // we already converted to \<letter> what python represents as such above
buf = append(buf, '\\', 'x', hex[b[0]>>4], hex[b[0]&0xf]) buf = append(buf, '\\', 'x', hexdigits[b[0]>>4], hexdigits[b[0]&0xf])
case r < utf8.RuneSelf /* RuneSelf itself is not printable */ - 1: case r < utf8.RuneSelf /* RuneSelf itself is not printable */ - 1:
// we already escaped all < RuneSelf runes // we already escaped all < RuneSelf runes
...@@ -70,7 +75,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte { ...@@ -70,7 +75,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte {
default: default:
// everything else goes in numeric byte escapes // everything else goes in numeric byte escapes
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
buf = append(buf, '\\', 'x', hex[b[i]>>4], hex[b[i]&0xf]) buf = append(buf, '\\', 'x', hexdigits[b[i]>>4], hexdigits[b[i]&0xf])
} }
} }
} }
...@@ -85,12 +90,12 @@ func pyAppendQuoteBytes(buf, b []byte) []byte { ...@@ -85,12 +90,12 @@ func pyAppendQuoteBytes(buf, b []byte) []byte {
// Qpy appends string quoted as Python would do // Qpy appends string quoted as Python would do
func (b *Buffer) Qpy(s string) *Buffer { func (b *Buffer) Qpy(s string) *Buffer {
*b = ... // TODO *b = AppendQuotePy(*b, s)
return b return b
} }
// Qpyb appends []byte quoted as Python would do // Qbpy appends []byte quoted as Python would do
func (b *Buffer) Qpyb(x []byte) *Buffer { func (b *Buffer) Qbpy(x []byte) *Buffer {
*b = ... // TODO *b = AppendQuotePyBytes(*b, x)
return b return b
} }
...@@ -2,8 +2,6 @@ package xfmt ...@@ -2,8 +2,6 @@ package xfmt
import ( import (
"testing" "testing"
"lab.nexedi.com/kirr/go123/mem"
) )
// byterange returns []byte with element [start,stop) // byterange returns []byte with element [start,stop)
...@@ -45,8 +43,11 @@ var pyQuoteTestv = []struct {in, quoted string} { ...@@ -45,8 +43,11 @@ var pyQuoteTestv = []struct {in, quoted string} {
} }
func TestPyQuote(t *testing.T) { func TestPyQuote(t *testing.T) {
buf := []byte{}
for _, tt := range pyQuoteTestv { for _, tt := range pyQuoteTestv {
quoted := pyQuote(tt.in) buf = buf[:0]
buf = AppendQuotePy(buf, tt.in)
quoted := string(buf)
if quoted != tt.quoted { if quoted != tt.quoted {
t.Errorf("pyQuote(%q) ->\nhave: %s\nwant: %s", tt.in, quoted, tt.quoted) t.Errorf("pyQuote(%q) ->\nhave: %s\nwant: %s", tt.in, quoted, tt.quoted)
} }
...@@ -59,7 +60,7 @@ func BenchmarkPyQuote(b *testing.B) { ...@@ -59,7 +60,7 @@ func BenchmarkPyQuote(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
for _, tt := range pyQuoteTestv { for _, tt := range pyQuoteTestv {
buf = buf[:0] buf = buf[:0]
buf = pyAppendQuoteBytes(buf, mem.Bytes(tt.in)) buf = AppendQuotePy(buf, tt.in)
} }
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment