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.
//
// 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
import (
......@@ -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 {
l := len(b)
b = xslice.Grow(b, utf8.UTFMax)
......@@ -142,3 +164,5 @@ func (b *Buffer) X016(x uint64) *Buffer {
*b = AppendHex016(*b, x)
return b
}
// TODO Qs Qb ?
......@@ -8,29 +8,30 @@ import (
"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
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{}
xbuf := reflect.ValueOf(buf)
......@@ -81,10 +82,46 @@ func TestXFmt(t *testing.T) {
}
}
func BenchmarkFmt(t *testing.T) {
// TODO
}
func BenchmarkXFmt(b *testing.B) {
buf := &Buffer{}
func BenchmarkXFmt(t *testing.T) {
// TODO
for _, tt := range testv {
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 (
"bytes"
......@@ -9,20 +9,25 @@ import (
)
const hex = "0123456789abcdef"
// pyQuote quotes string the way python repr(str) would do
func pyQuote(s string) string {
out := pyQuoteBytes(mem.Bytes(s))
return mem.String(out)
}
func pyQuoteBytes(b []byte) []byte {
buf := make([]byte, 0, (len(b) + 2) /* to reduce allocations when quoting */ * 2)
return pyAppendQuoteBytes(buf, b)
// TODO remove - not needed ?
// // pyQuote quotes string the way python repr(str) would do
// func pyQuote(s string) string {
// out := pyQuoteBytes(mem.Bytes(s))
// return mem.String(out)
// }
//
// func pyQuoteBytes(b []byte) []byte {
// buf := make([]byte, 0, (len(b) + 2) /* to reduce allocations when quoting */ * 2)
// 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
// https://github.com/python/cpython/blob/v2.7.13-116-g1aa1803b3d/Objects/stringobject.c#L947
quote := byte('\'')
......@@ -38,7 +43,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte {
switch r {
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):
buf = append(buf, '\\', byte(r))
case rune(noquote):
......@@ -57,7 +62,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte {
switch {
case r < ' ':
// 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:
// we already escaped all < RuneSelf runes
......@@ -70,7 +75,7 @@ func pyAppendQuoteBytes(buf, b []byte) []byte {
default:
// everything else goes in numeric byte escapes
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 {
// Qpy appends string quoted as Python would do
func (b *Buffer) Qpy(s string) *Buffer {
*b = ... // TODO
*b = AppendQuotePy(*b, s)
return b
}
// Qpyb appends []byte quoted as Python would do
func (b *Buffer) Qpyb(x []byte) *Buffer {
*b = ... // TODO
// Qbpy appends []byte quoted as Python would do
func (b *Buffer) Qbpy(x []byte) *Buffer {
*b = AppendQuotePyBytes(*b, x)
return b
}
......@@ -2,8 +2,6 @@ package xfmt
import (
"testing"
"lab.nexedi.com/kirr/go123/mem"
)
// byterange returns []byte with element [start,stop)
......@@ -45,8 +43,11 @@ var pyQuoteTestv = []struct {in, quoted string} {
}
func TestPyQuote(t *testing.T) {
buf := []byte{}
for _, tt := range pyQuoteTestv {
quoted := pyQuote(tt.in)
buf = buf[:0]
buf = AppendQuotePy(buf, tt.in)
quoted := string(buf)
if 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) {
for i := 0; i < b.N; i++ {
for _, tt := range pyQuoteTestv {
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