diff options
Diffstat (limited to 'vendor/github.com/d5')
18 files changed, 1555 insertions, 48 deletions
diff --git a/vendor/github.com/d5/tengo/README.md b/vendor/github.com/d5/tengo/README.md index e68b0f86..6a35cfd1 100644 --- a/vendor/github.com/d5/tengo/README.md +++ b/vendor/github.com/d5/tengo/README.md @@ -7,6 +7,7 @@ [![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo/script) [![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo) [![Build Status](https://travis-ci.org/d5/tengo.svg?branch=master)](https://travis-ci.org/d5/tengo) +[![Sourcegraph](https://sourcegraph.com/github.com/d5/tengo/-/badge.svg)](https://sourcegraph.com/github.com/d5/tengo?badge) **Tengo is a small, dynamic, fast, secure script language for Go.** diff --git a/vendor/github.com/d5/tengo/compiler/ast/ident_list.go b/vendor/github.com/d5/tengo/compiler/ast/ident_list.go index ee8f7db2..8dd6d307 100644 --- a/vendor/github.com/d5/tengo/compiler/ast/ident_list.go +++ b/vendor/github.com/d5/tengo/compiler/ast/ident_list.go @@ -8,9 +8,10 @@ import ( // IdentList represents a list of identifiers. type IdentList struct { - LParen source.Pos - List []*Ident - RParen source.Pos + LParen source.Pos + VarArgs bool + List []*Ident + RParen source.Pos } // Pos returns the position of first character belonging to the node. @@ -50,8 +51,12 @@ func (n *IdentList) NumFields() int { func (n *IdentList) String() string { var list []string - for _, e := range n.List { - list = append(list, e.String()) + for i, e := range n.List { + if n.VarArgs && i == len(n.List)-1 { + list = append(list, "..."+e.String()) + } else { + list = append(list, e.String()) + } } return "(" + strings.Join(list, ", ") + ")" diff --git a/vendor/github.com/d5/tengo/compiler/compiler.go b/vendor/github.com/d5/tengo/compiler/compiler.go index 4a3ec3ad..8bde5dc9 100644 --- a/vendor/github.com/d5/tengo/compiler/compiler.go +++ b/vendor/github.com/d5/tengo/compiler/compiler.go @@ -477,6 +477,7 @@ func (c *Compiler) Compile(node ast.Node) error { Instructions: instructions, NumLocals: numLocals, NumParameters: len(node.Type.Params.List), + VarArgs: node.Type.Params.VarArgs, SourceMap: sourceMap, } diff --git a/vendor/github.com/d5/tengo/compiler/instructions.go b/vendor/github.com/d5/tengo/compiler/instructions.go index 80c88d13..14dde1d8 100644 --- a/vendor/github.com/d5/tengo/compiler/instructions.go +++ b/vendor/github.com/d5/tengo/compiler/instructions.go @@ -13,7 +13,7 @@ func MakeInstruction(opcode Opcode, operands ...int) []byte { totalLen += w } - instruction := make([]byte, totalLen, totalLen) + instruction := make([]byte, totalLen) instruction[0] = byte(opcode) offset := 1 diff --git a/vendor/github.com/d5/tengo/compiler/parser/parser.go b/vendor/github.com/d5/tengo/compiler/parser/parser.go index 1f609ab5..27dd48f0 100644 --- a/vendor/github.com/d5/tengo/compiler/parser/parser.go +++ b/vendor/github.com/d5/tengo/compiler/parser/parser.go @@ -610,19 +610,31 @@ func (p *Parser) parseIdentList() *ast.IdentList { var params []*ast.Ident lparen := p.expect(token.LParen) + isVarArgs := false if p.token != token.RParen { + if p.token == token.Ellipsis { + isVarArgs = true + p.next() + } + params = append(params, p.parseIdent()) - for p.token == token.Comma { + for !isVarArgs && p.token == token.Comma { p.next() + if p.token == token.Ellipsis { + isVarArgs = true + p.next() + } params = append(params, p.parseIdent()) } } + rparen := p.expect(token.RParen) return &ast.IdentList{ - LParen: lparen, - RParen: rparen, - List: params, + LParen: lparen, + RParen: rparen, + VarArgs: isVarArgs, + List: params, } } diff --git a/vendor/github.com/d5/tengo/compiler/symbol_scopes.go b/vendor/github.com/d5/tengo/compiler/symbol_scopes.go index 15204b35..e0c0d94b 100644 --- a/vendor/github.com/d5/tengo/compiler/symbol_scopes.go +++ b/vendor/github.com/d5/tengo/compiler/symbol_scopes.go @@ -6,7 +6,7 @@ type SymbolScope string // List of symbol scopes const ( ScopeGlobal SymbolScope = "GLOBAL" - ScopeLocal = "LOCAL" - ScopeBuiltin = "BUILTIN" - ScopeFree = "FREE" + ScopeLocal SymbolScope = "LOCAL" + ScopeBuiltin SymbolScope = "BUILTIN" + ScopeFree SymbolScope = "FREE" ) diff --git a/vendor/github.com/d5/tengo/objects/builtin_format.go b/vendor/github.com/d5/tengo/objects/builtin_format.go new file mode 100644 index 00000000..1f0e75ed --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_format.go @@ -0,0 +1,27 @@ +package objects + +func builtinFormat(args ...Object) (Object, error) { + numArgs := len(args) + if numArgs == 0 { + return nil, ErrWrongNumArguments + } + + format, ok := args[0].(*String) + if !ok { + return nil, ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } + } + if numArgs == 1 { + return format, nil // okay to return 'format' directly as String is immutable + } + + s, err := Format(format.Value, args[1:]...) + if err != nil { + return nil, err + } + + return &String{Value: s}, nil +} diff --git a/vendor/github.com/d5/tengo/objects/builtins.go b/vendor/github.com/d5/tengo/objects/builtins.go index bfd004dd..773636ec 100644 --- a/vendor/github.com/d5/tengo/objects/builtins.go +++ b/vendor/github.com/d5/tengo/objects/builtins.go @@ -111,4 +111,8 @@ var Builtins = []*BuiltinFunction{ Name: "type_name", Value: builtinTypeName, }, + { + Name: "format", + Value: builtinFormat, + }, } diff --git a/vendor/github.com/d5/tengo/objects/bytes.go b/vendor/github.com/d5/tengo/objects/bytes.go index 6710c7c1..5159c22f 100644 --- a/vendor/github.com/d5/tengo/objects/bytes.go +++ b/vendor/github.com/d5/tengo/objects/bytes.go @@ -57,7 +57,7 @@ func (o *Bytes) Equals(x Object) bool { return false } - return bytes.Compare(o.Value, t.Value) == 0 + return bytes.Equal(o.Value, t.Value) } // IndexGet returns an element (as Int) at a given index. diff --git a/vendor/github.com/d5/tengo/objects/compiled_function.go b/vendor/github.com/d5/tengo/objects/compiled_function.go index 606e3d90..d42e69ec 100644 --- a/vendor/github.com/d5/tengo/objects/compiled_function.go +++ b/vendor/github.com/d5/tengo/objects/compiled_function.go @@ -10,6 +10,7 @@ type CompiledFunction struct { Instructions []byte NumLocals int // number of local variables (including function parameters) NumParameters int + VarArgs bool SourceMap map[int]source.Pos } @@ -34,6 +35,7 @@ func (o *CompiledFunction) Copy() Object { Instructions: append([]byte{}, o.Instructions...), NumLocals: o.NumLocals, NumParameters: o.NumParameters, + VarArgs: o.VarArgs, } } diff --git a/vendor/github.com/d5/tengo/objects/conversion.go b/vendor/github.com/d5/tengo/objects/conversion.go index d7cb3a03..27514132 100644 --- a/vendor/github.com/d5/tengo/objects/conversion.go +++ b/vendor/github.com/d5/tengo/objects/conversion.go @@ -254,7 +254,7 @@ func FromInterface(v interface{}) (Object, error) { case []Object: return &Array{Value: v}, nil case []interface{}: - arr := make([]Object, len(v), len(v)) + arr := make([]Object, len(v)) for i, e := range v { vo, err := FromInterface(e) if err != nil { diff --git a/vendor/github.com/d5/tengo/objects/formatter.go b/vendor/github.com/d5/tengo/objects/formatter.go new file mode 100644 index 00000000..95d7f6b1 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/formatter.go @@ -0,0 +1,1212 @@ +package objects + +import ( + "strconv" + "sync" + "unicode/utf8" + + "github.com/d5/tengo" +) + +// Strings for use with buffer.WriteString. +// This is less overhead than using buffer.Write with byte arrays. +const ( + commaSpaceString = ", " + nilParenString = "(nil)" + percentBangString = "%!" + missingString = "(MISSING)" + badIndexString = "(BADINDEX)" + extraString = "%!(EXTRA " + badWidthString = "%!(BADWIDTH)" + badPrecString = "%!(BADPREC)" + noVerbString = "%!(NOVERB)" +) + +const ( + ldigits = "0123456789abcdefx" + udigits = "0123456789ABCDEFX" +) + +const ( + signed = true + unsigned = false +) + +// flags placed in a separate struct for easy clearing. +type fmtFlags struct { + widPresent bool + precPresent bool + minus bool + plus bool + sharp bool + space bool + zero bool + + // For the formats %+v %#v, we set the plusV/sharpV flags + // and clear the plus/sharp flags since %+v and %#v are in effect + // different, flagless formats set at the top level. + plusV bool + sharpV bool + + // error-related flags. + inDetail bool + needNewline bool + needColon bool +} + +// A formatter is the raw formatter used by Printf etc. +// It prints into a buffer that must be set up separately. +type formatter struct { + buf *buffer + + fmtFlags + + wid int // width + prec int // precision + + // intbuf is large enough to store %b of an int64 with a sign and + // avoids padding at the end of the struct on 32 bit architectures. + intbuf [68]byte +} + +func (f *formatter) clearflags() { + f.fmtFlags = fmtFlags{} +} + +func (f *formatter) init(buf *buffer) { + f.buf = buf + f.clearflags() +} + +// writePadding generates n bytes of padding. +func (f *formatter) writePadding(n int) { + if n <= 0 { // No padding bytes needed. + return + } + buf := *f.buf + oldLen := len(buf) + newLen := oldLen + n + + if newLen > tengo.MaxStringLen { + panic(ErrStringLimit) + } + + // Make enough room for padding. + if newLen > cap(buf) { + buf = make(buffer, cap(buf)*2+n) + copy(buf, *f.buf) + } + // Decide which byte the padding should be filled with. + padByte := byte(' ') + if f.zero { + padByte = byte('0') + } + // Fill padding with padByte. + padding := buf[oldLen:newLen] + for i := range padding { + padding[i] = padByte + } + *f.buf = buf[:newLen] +} + +// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus). +func (f *formatter) pad(b []byte) { + if !f.widPresent || f.wid == 0 { + f.buf.Write(b) + return + } + width := f.wid - utf8.RuneCount(b) + if !f.minus { + // left padding + f.writePadding(width) + f.buf.Write(b) + } else { + // right padding + f.buf.Write(b) + f.writePadding(width) + } +} + +// padString appends s to f.buf, padded on left (!f.minus) or right (f.minus). +func (f *formatter) padString(s string) { + if !f.widPresent || f.wid == 0 { + f.buf.WriteString(s) + return + } + width := f.wid - utf8.RuneCountInString(s) + if !f.minus { + // left padding + f.writePadding(width) + f.buf.WriteString(s) + } else { + // right padding + f.buf.WriteString(s) + f.writePadding(width) + } +} + +// fmtBoolean formats a boolean. +func (f *formatter) fmtBoolean(v bool) { + if v { + f.padString("true") + } else { + f.padString("false") + } +} + +// fmtUnicode formats a uint64 as "U+0078" or with f.sharp set as "U+0078 'x'". +func (f *formatter) fmtUnicode(u uint64) { + buf := f.intbuf[0:] + + // With default precision set the maximum needed buf length is 18 + // for formatting -1 with %#U ("U+FFFFFFFFFFFFFFFF") which fits + // into the already allocated intbuf with a capacity of 68 bytes. + prec := 4 + if f.precPresent && f.prec > 4 { + prec = f.prec + // Compute space needed for "U+" , number, " '", character, "'". + width := 2 + prec + 2 + utf8.UTFMax + 1 + if width > len(buf) { + buf = make([]byte, width) + } + } + + // Format into buf, ending at buf[i]. Formatting numbers is easier right-to-left. + i := len(buf) + + // For %#U we want to add a space and a quoted character at the end of the buffer. + if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) { + i-- + buf[i] = '\'' + i -= utf8.RuneLen(rune(u)) + utf8.EncodeRune(buf[i:], rune(u)) + i-- + buf[i] = '\'' + i-- + buf[i] = ' ' + } + // Format the Unicode code point u as a hexadecimal number. + for u >= 16 { + i-- + buf[i] = udigits[u&0xF] + prec-- + u >>= 4 + } + i-- + buf[i] = udigits[u] + prec-- + // Add zeros in front of the number until requested precision is reached. + for prec > 0 { + i-- + buf[i] = '0' + prec-- + } + // Add a leading "U+". + i-- + buf[i] = '+' + i-- + buf[i] = 'U' + + oldZero := f.zero + f.zero = false + f.pad(buf[i:]) + f.zero = oldZero +} + +// fmtInteger formats signed and unsigned integers. +func (f *formatter) fmtInteger(u uint64, base int, isSigned bool, verb rune, digits string) { + negative := isSigned && int64(u) < 0 + if negative { + u = -u + } + + buf := f.intbuf[0:] + // The already allocated f.intbuf with a capacity of 68 bytes + // is large enough for integer formatting when no precision or width is set. + if f.widPresent || f.precPresent { + // Account 3 extra bytes for possible addition of a sign and "0x". + width := 3 + f.wid + f.prec // wid and prec are always positive. + if width > len(buf) { + // We're going to need a bigger boat. + buf = make([]byte, width) + } + } + + // Two ways to ask for extra leading zero digits: %.3d or %03d. + // If both are specified the f.zero flag is ignored and + // padding with spaces is used instead. + prec := 0 + if f.precPresent { + prec = f.prec + // Precision of 0 and value of 0 means "print nothing" but padding. + if prec == 0 && u == 0 { + oldZero := f.zero + f.zero = false + f.writePadding(f.wid) + f.zero = oldZero + return + } + } else if f.zero && f.widPresent { + prec = f.wid + if negative || f.plus || f.space { + prec-- // leave room for sign + } + } + + // Because printing is easier right-to-left: format u into buf, ending at buf[i]. + // We could make things marginally faster by splitting the 32-bit case out + // into a separate block but it's not worth the duplication, so u has 64 bits. + i := len(buf) + // Use constants for the division and modulo for more efficient code. + // Switch cases ordered by popularity. + switch base { + case 10: + for u >= 10 { + i-- + next := u / 10 + buf[i] = byte('0' + u - next*10) + u = next + } + case 16: + for u >= 16 { + i-- + buf[i] = digits[u&0xF] + u >>= 4 + } + case 8: + for u >= 8 { + i-- + buf[i] = byte('0' + u&7) + u >>= 3 + } + case 2: + for u >= 2 { + i-- + buf[i] = byte('0' + u&1) + u >>= 1 + } + default: + panic("fmt: unknown base; can't happen") + } + i-- + buf[i] = digits[u] + for i > 0 && prec > len(buf)-i { + i-- + buf[i] = '0' + } + + // Various prefixes: 0x, -, etc. + if f.sharp { + switch base { + case 2: + // Add a leading 0b. + i-- + buf[i] = 'b' + i-- + buf[i] = '0' + case 8: + if buf[i] != '0' { + i-- + buf[i] = '0' + } + case 16: + // Add a leading 0x or 0X. + i-- + buf[i] = digits[16] + i-- + buf[i] = '0' + } + } + if verb == 'O' { + i-- + buf[i] = 'o' + i-- + buf[i] = '0' + } + + if negative { + i-- + buf[i] = '-' + } else if f.plus { + i-- + buf[i] = '+' + } else if f.space { + i-- + buf[i] = ' ' + } + + // Left padding with zeros has already been handled like precision earlier + // or the f.zero flag is ignored due to an explicitly set precision. + oldZero := f.zero + f.zero = false + f.pad(buf[i:]) + f.zero = oldZero +} + +// truncate truncates the string s to the specified precision, if present. +func (f *formatter) truncateString(s string) string { + if f.precPresent { + n := f.prec + for i := range s { + n-- + if n < 0 { + return s[:i] + } + } + } + return s +} + +// truncate truncates the byte slice b as a string of the specified precision, if present. +func (f *formatter) truncate(b []byte) []byte { + if f.precPresent { + n := f.prec + for i := 0; i < len(b); { + n-- + if n < 0 { + return b[:i] + } + wid := 1 + if b[i] >= utf8.RuneSelf { + _, wid = utf8.DecodeRune(b[i:]) + } + i += wid + } + } + return b +} + +// fmtS formats a string. +func (f *formatter) fmtS(s string) { + s = f.truncateString(s) + f.padString(s) +} + +// fmtBs formats the byte slice b as if it was formatted as string with fmtS. +func (f *formatter) fmtBs(b []byte) { + b = f.truncate(b) + f.pad(b) +} + +// fmtSbx formats a string or byte slice as a hexadecimal encoding of its bytes. +func (f *formatter) fmtSbx(s string, b []byte, digits string) { + length := len(b) + if b == nil { + // No byte slice present. Assume string s should be encoded. + length = len(s) + } + // Set length to not process more bytes than the precision demands. + if f.precPresent && f.prec < length { + length = f.prec + } + // Compute width of the encoding taking into account the f.sharp and f.space flag. + width := 2 * length + if width > 0 { + if f.space { + // Each element encoded by two hexadecimals will get a leading 0x or 0X. + if f.sharp { + width *= 2 + } + // Elements will be separated by a space. + width += length - 1 + } else if f.sharp { + // Only a leading 0x or 0X will be added for the whole string. + width += 2 + } + } else { // The byte slice or string that should be encoded is empty. + if f.widPresent { + f.writePadding(f.wid) + } + return + } + // Handle padding to the left. + if f.widPresent && f.wid > width && !f.minus { + f.writePadding(f.wid - width) + } + // Write the encoding directly into the output buffer. + buf := *f.buf + if f.sharp { + // Add leading 0x or 0X. + buf = append(buf, '0', digits[16]) + } + var c byte + for i := 0; i < length; i++ { + if f.space && i > 0 { + // Separate elements with a space. + buf = append(buf, ' ') + if f.sharp { + // Add leading 0x or 0X for each element. + buf = append(buf, '0', digits[16]) + } + } + if b != nil { + c = b[i] // Take a byte from the input byte slice. + } else { + c = s[i] // Take a byte from the input string. + } + // Encode each byte as two hexadecimal digits. + buf = append(buf, digits[c>>4], digits[c&0xF]) + } + *f.buf = buf + // Handle padding to the right. + if f.widPresent && f.wid > width && f.minus { + f.writePadding(f.wid - width) + } +} + +// fmtSx formats a string as a hexadecimal encoding of its bytes. +func (f *formatter) fmtSx(s, digits string) { + f.fmtSbx(s, nil, digits) +} + +// fmtBx formats a byte slice as a hexadecimal encoding of its bytes. +func (f *formatter) fmtBx(b []byte, digits string) { + f.fmtSbx("", b, digits) +} + +// fmtQ formats a string as a double-quoted, escaped Go string constant. +// If f.sharp is set a raw (backquoted) string may be returned instead +// if the string does not contain any control characters other than tab. +func (f *formatter) fmtQ(s string) { + s = f.truncateString(s) + if f.sharp && strconv.CanBackquote(s) { + f.padString("`" + s + "`") + return + } + buf := f.intbuf[:0] + if f.plus { + f.pad(strconv.AppendQuoteToASCII(buf, s)) + } else { + f.pad(strconv.AppendQuote(buf, s)) + } +} + +// fmtC formats an integer as a Unicode character. +// If the character is not valid Unicode, it will print '\ufffd'. +func (f *formatter) fmtC(c uint64) { + r := rune(c) + if c > utf8.MaxRune { + r = utf8.RuneError + } + buf := f.intbuf[:0] + w := utf8.EncodeRune(buf[:utf8.UTFMax], r) + f.pad(buf[:w]) +} + +// fmtQc formats an integer as a single-quoted, escaped Go character constant. +// If the character is not valid Unicode, it will print '\ufffd'. +func (f *formatter) fmtQc(c uint64) { + r := rune(c) + if c > utf8.MaxRune { + r = utf8.RuneError + } + buf := f.intbuf[:0] + if f.plus { + f.pad(strconv.AppendQuoteRuneToASCII(buf, r)) + } else { + f.pad(strconv.AppendQuoteRune(buf, r)) + } +} + +// fmtFloat formats a float64. It assumes that verb is a valid format specifier +// for strconv.AppendFloat and therefore fits into a byte. +func (f *formatter) fmtFloat(v float64, size int, verb rune, prec int) { + // Explicit precision in format specifier overrules default precision. + if f.precPresent { + prec = f.prec + } + // Format number, reserving space for leading + sign if needed. + num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size) + if num[1] == '-' || num[1] == '+' { + num = num[1:] + } else { + num[0] = '+' + } + // f.space means to add a leading space instead of a "+" sign unless + // the sign is explicitly asked for by f.plus. + if f.space && num[0] == '+' && !f.plus { + num[0] = ' ' + } + // Special handling for infinities and NaN, + // which don't look like a number so shouldn't be padded with zeros. + if num[1] == 'I' || num[1] == 'N' { + oldZero := f.zero + f.zero = false + // Remove sign before NaN if not asked for. + if num[1] == 'N' && !f.space && !f.plus { + num = num[1:] + } + f.pad(num) + f.zero = oldZero + return + } + // The sharp flag forces printing a decimal point for non-binary formats + // and retains trailing zeros, which we may need to restore. + if f.sharp && verb != 'b' { + digits := 0 + switch verb { + case 'v', 'g', 'G', 'x': + digits = prec + // If no precision is set explicitly use a precision of 6. + if digits == -1 { + digits = 6 + } + } + + // Buffer pre-allocated with enough room for + // exponent notations of the form "e+123" or "p-1023". + var tailBuf [6]byte + tail := tailBuf[:0] + + hasDecimalPoint := false + // Starting from i = 1 to skip sign at num[0]. + for i := 1; i < len(num); i++ { + switch num[i] { + case '.': + hasDecimalPoint = true + case 'p', 'P': + tail = append(tail, num[i:]...) + num = num[:i] + case 'e', 'E': + if verb != 'x' && verb != 'X' { + tail = append(tail, num[i:]...) + num = num[:i] + break + } + fallthrough + default: + digits-- + } + } + if !hasDecimalPoint { + num = append(num, '.') + } + for digits > 0 { + num = append(num, '0') + digits-- + } + num = append(num, tail...) + } + // We want a sign if asked for and if the sign is not positive. + if f.plus || num[0] != '+' { + // If we're zero padding to the left we want the sign before the leading zeros. + // Achieve this by writing the sign out and then padding the unsigned number. + if f.zero && f.widPresent && f.wid > len(num) { + f.buf.WriteSingleByte(num[0]) + f.writePadding(f.wid - len(num)) + f.buf.Write(num[1:]) + return + } + f.pad(num) + return + } + // No sign to show and the number is positive; just print the unsigned number. + f.pad(num[1:]) +} + +// Use simple []byte instead of bytes.Buffer to avoid large dependency. +type buffer []byte + +func (b *buffer) Write(p []byte) { + if len(*b)+len(p) > tengo.MaxStringLen { + panic(ErrStringLimit) + } + + *b = append(*b, p...) +} + +func (b *buffer) WriteString(s string) { + if len(*b)+len(s) > tengo.MaxStringLen { + panic(ErrStringLimit) + } + + *b = append(*b, s...) +} + +func (b *buffer) WriteSingleByte(c byte) { + if len(*b) >= tengo.MaxStringLen { + panic(ErrStringLimit) + } + + *b = append(*b, c) +} + +func (b *buffer) WriteRune(r rune) { + if len(*b)+utf8.RuneLen(r) > tengo.MaxStringLen { + panic(ErrStringLimit) + } + + if r < utf8.RuneSelf { + *b = append(*b, byte(r)) + return + } + + b2 := *b + n := len(b2) + for n+utf8.UTFMax > cap(b2) { + b2 = append(b2, 0) + } + w := utf8.EncodeRune(b2[n:n+utf8.UTFMax], r) + *b = b2[:n+w] +} + +// pp is used to store a printer's state and is reused with sync.Pool to avoid allocations. +type pp struct { + buf buffer + + // arg holds the current item. + arg Object + + // fmt is used to format basic items such as integers or strings. + fmt formatter + + // reordered records whether the format string used argument reordering. + reordered bool + + // goodArgNum records whether the most recent reordering directive was valid. + goodArgNum bool + + // erroring is set when printing an error string to guard against calling handleMethods. + erroring bool +} + +var ppFree = sync.Pool{ + New: func() interface{} { return new(pp) }, +} + +// newPrinter allocates a new pp struct or grabs a cached one. +func newPrinter() *pp { + p := ppFree.Get().(*pp) + p.erroring = false + p.fmt.init(&p.buf) + return p +} + +// free saves used pp structs in ppFree; avoids an allocation per invocation. +func (p *pp) free() { + // Proper usage of a sync.Pool requires each entry to have approximately + // the same memory cost. To obtain this property when the stored type + // contains a variably-sized buffer, we add a hard limit on the maximum buffer + // to place back in the pool. + // + // See https://golang.org/issue/23199 + if cap(p.buf) > 64<<10 { + return + } + + p.buf = p.buf[:0] + p.arg = nil + ppFree.Put(p) +} + +func (p *pp) Width() (wid int, ok bool) { return p.fmt.wid, p.fmt.widPresent } + +func (p *pp) Precision() (prec int, ok bool) { return p.fmt.prec, p.fmt.precPresent } + +func (p *pp) Flag(b int) bool { + switch b { + case '-': + return p.fmt.minus + case '+': + return p.fmt.plus || p.fmt.plusV + case '#': + return p.fmt.sharp || p.fmt.sharpV + case ' ': + return p.fmt.space + case '0': + return p.fmt.zero + } + return false +} + +// Implement Write so we can call Fprintf on a pp (through State), for +// recursive use in custom verbs. +func (p *pp) Write(b []byte) (ret int, err error) { + p.buf.Write(b) + return len(b), nil +} + +// Implement WriteString so that we can call io.WriteString +// on a pp (through state), for efficiency. +func (p *pp) WriteString(s string) (ret int, err error) { + p.buf.WriteString(s) + return len(s), nil +} + +func (p *pp) WriteRune(r rune) (ret int, err error) { + p.buf.WriteRune(r) + return utf8.RuneLen(r), nil +} + +func (p *pp) WriteSingleByte(c byte) (ret int, err error) { + p.buf.WriteSingleByte(c) + return 1, nil +} + +// tooLarge reports whether the magnitude of the integer is +// too large to be used as a formatting width or precision. +func tooLarge(x int) bool { + const max int = 1e6 + return x > max || x < -max +} + +// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no number present. +func parsenum(s string, start, end int) (num int, isnum bool, newi int) { + if start >= end { + return 0, false, end + } + for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ { + if tooLarge(num) { + return 0, false, end // Overflow; crazy long number most likely. + } + num = num*10 + int(s[newi]-'0') + isnum = true + } + return +} + +func (p *pp) badVerb(verb rune) { + p.erroring = true + _, _ = p.WriteString(percentBangString) + _, _ = p.WriteRune(verb) + _, _ = p.WriteSingleByte('(') + switch { + case p.arg != nil: + _, _ = p.WriteString(p.arg.String()) + _, _ = p.WriteSingleByte('=') + p.printArg(p.arg, 'v') + default: + _, _ = p.WriteString(UndefinedValue.String()) + } + _, _ = p.WriteSingleByte(')') + p.erroring = false +} + +func (p *pp) fmtBool(v bool, verb rune) { + switch verb { + case 't', 'v': + p.fmt.fmtBoolean(v) + default: + p.badVerb(verb) + } +} + +// fmt0x64 formats a uint64 in hexadecimal and prefixes it with 0x or +// not, as requested, by temporarily setting the sharp flag. +func (p *pp) fmt0x64(v uint64, leading0x bool) { + sharp := p.fmt.sharp + p.fmt.sharp = leading0x + p.fmt.fmtInteger(v, 16, unsigned, 'v', ldigits) + p.fmt.sharp = sharp +} + +// fmtInteger formats a signed or unsigned integer. +func (p *pp) fmtInteger(v uint64, isSigned bool, verb rune) { + switch verb { + case 'v': + if p.fmt.sharpV && !isSigned { + p.fmt0x64(v, true) + } else { + p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) + } + case 'd': + p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) + case 'b': + p.fmt.fmtInteger(v, 2, isSigned, verb, ldigits) + case 'o', 'O': + p.fmt.fmtInteger(v, 8, isSigned, verb, ldigits) + case 'x': + p.fmt.fmtInteger(v, 16, isSigned, verb, ldigits) + case 'X': + p.fmt.fmtInteger(v, 16, isSigned, verb, udigits) + case 'c': + p.fmt.fmtC(v) + case 'q': + if v <= utf8.MaxRune { + p.fmt.fmtQc(v) + } else { + p.badVerb(verb) + } + case 'U': + p.fmt.fmtUnicode(v) + default: + p.badVerb(verb) + } +} + +// fmtFloat formats a float. The default precision for each verb +// is specified as last argument in the call to fmt_float. +func (p *pp) fmtFloat(v float64, size int, verb rune) { + switch verb { + case 'v': + p.fmt.fmtFloat(v, size, 'g', -1) + case 'b', 'g', 'G', 'x', 'X': + p.fmt.fmtFloat(v, size, verb, -1) + case 'f', 'e', 'E': + p.fmt.fmtFloat(v, size, verb, 6) + case 'F': + p.fmt.fmtFloat(v, size, 'f', 6) + default: + p.badVerb(verb) + } +} + +func (p *pp) fmtString(v string, verb rune) { + switch verb { + case 'v': + if p.fmt.sharpV { + p.fmt.fmtQ(v) + } else { + p.fmt.fmtS(v) + } + case 's': + p.fmt.fmtS(v) + case 'x': + p.fmt.fmtSx(v, ldigits) + case 'X': + p.fmt.fmtSx(v, udigits) + case 'q': + p.fmt.fmtQ(v) + default: + p.badVerb(verb) + } +} + +func (p *pp) fmtBytes(v []byte, verb rune, typeString string) { + switch verb { + case 'v', 'd': + if p.fmt.sharpV { + _, _ = p.WriteString(typeString) + if v == nil { + _, _ = p.WriteString(nilParenString) + return + } + _, _ = p.WriteSingleByte('{') + for i, c := range v { + if i > 0 { + _, _ = p.WriteString(commaSpaceString) + } + p.fmt0x64(uint64(c), true) + } + _, _ = p.WriteSingleByte('}') + } else { + _, _ = p.WriteSingleByte('[') + for i, c := range v { + if i > 0 { + _, _ = p.WriteSingleByte(' ') + } + p.fmt.fmtInteger(uint64(c), 10, unsigned, verb, ldigits) + } + _, _ = p.WriteSingleByte(']') + } + case 's': + p.fmt.fmtBs(v) + case 'x': + p.fmt.fmtBx(v, ldigits) + case 'X': + p.fmt.fmtBx(v, udigits) + case 'q': + p.fmt.fmtQ(string(v)) + } +} + +func (p *pp) printArg(arg Object, verb rune) { + p.arg = arg + + if arg == nil { + arg = UndefinedValue + } + + // Special processing considerations. + // %T (the value's type) and %p (its address) are special; we always do them first. + switch verb { + case 'T': + p.fmt.fmtS(arg.TypeName()) + return + case 'v': + p.fmt.fmtS(arg.String()) + return + } + + // Some types can be done without reflection. + switch f := arg.(type) { + case *Bool: + p.fmtBool(!f.IsFalsy(), verb) + case *Float: + p.fmtFloat(f.Value, 64, verb) + case *Int: + p.fmtInteger(uint64(f.Value), signed, verb) + case *String: + p.fmtString(f.Value, verb) + case *Bytes: + p.fmtBytes(f.Value, verb, "[]byte") + default: + p.fmtString(f.String(), verb) + } +} + +// intFromArg gets the argNumth element of a. On return, isInt reports whether the argument has integer type. +func intFromArg(a []Object, argNum int) (num int, isInt bool, newArgNum int) { + newArgNum = argNum + if argNum < len(a) { + var num64 int64 + num64, isInt = ToInt64(a[argNum]) + num = int(num64) + newArgNum = argNum + 1 + if tooLarge(num) { + num = 0 + isInt = false + } + } + return +} + +// parseArgNumber returns the value of the bracketed number, minus 1 +// (explicit argument numbers are one-indexed but we want zero-indexed). +// The opening bracket is known to be present at format[0]. +// The returned values are the index, the number of bytes to consume +// up to the closing paren, if present, and whether the number parsed +// ok. The bytes to consume will be 1 if no closing paren is present. +func parseArgNumber(format string) (index int, wid int, ok bool) { + // There must be at least 3 bytes: [n]. + if len(format) < 3 { + return 0, 1, false + } + + // Find closing bracket. + for i := 1; i < len(format); i++ { + if format[i] == ']' { + width, ok, newi := parsenum(format, 1, i) + if !ok || newi != i { + return 0, i + 1, false + } + return width - 1, i + 1, true // arg numbers are one-indexed and skip paren. + } + } + return 0, 1, false +} + +// argNumber returns the next argument to evaluate, which is either the value of the passed-in +// argNum or the value of the bracketed integer that begins format[i:]. It also returns +// the new value of i, that is, the index of the next byte of the format to process. +func (p *pp) argNumber(argNum int, format string, i int, numArgs int) (newArgNum, newi int, found bool) { + if len(format) <= i || format[i] != '[' { + return argNum, i, false + } + p.reordered = true + index, wid, ok := parseArgNumber(format[i:]) + if ok && 0 <= index && index < numArgs { + return index, i + wid, true + } + p.goodArgNum = false + return argNum, i + wid, ok +} + +func (p *pp) badArgNum(verb rune) { + _, _ = p.WriteString(percentBangString) + _, _ = p.WriteRune(verb) + _, _ = p.WriteString(badIndexString) +} + +func (p *pp) missingArg(verb rune) { + _, _ = p.WriteString(percentBangString) + _, _ = p.WriteRune(verb) + _, _ = p.WriteString(missingString) +} + +func (p *pp) doFormat(format string, a []Object) (err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(error); ok && e == ErrStringLimit { + err = e + return + } + panic(r) + } + }() + + end := len(format) + argNum := 0 // we process one argument per non-trivial format + afterIndex := false // previous item in format was an index like [3]. + p.reordered = false +formatLoop: + for i := 0; i < end; { + p.goodArgNum = true + lasti := i + for i < end && format[i] != '%' { + i++ + } + if i > lasti { + _, _ = p.WriteString(format[lasti:i]) + } + if i >= end { + // done processing format string + break + } + + // Process one verb + i++ + + // Do we have flags? + p.fmt.clearflags() + simpleFormat: + for ; i < end; i++ { + c := format[i] + switch c { + case '#': + p.fmt.sharp = true + case '0': + p.fmt.zero = !p.fmt.minus // Only allow zero padding to the left. + case '+': + p.fmt.plus = true + case '-': + p.fmt.minus = true + p.fmt.zero = false // Do not pad with zeros to the right. + case ' ': + p.fmt.space = true + default: + // Fast path for common case of ascii lower case simple verbs + // without precision or width or argument indices. + if 'a' <= c && c <= 'z' && argNum < len(a) { + if c == 'v' { + // Go syntax + p.fmt.sharpV = p.fmt.sharp + p.fmt.sharp = false + // Struct-field syntax + p.fmt.plusV = p.fmt.plus + p.fmt.plus = false + } + p.printArg(a[argNum], rune(c)) + argNum++ + i++ + continue formatLoop + } + // Format is more complex than simple flags and a verb or is malformed. + break simpleFormat + } + } + + // Do we have an explicit argument index? + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + + // Do we have width? + if i < end && format[i] == '*' { + i++ + p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum) + + if !p.fmt.widPresent { + _, _ = p.WriteString(badWidthString) + } + + // We have a negative width, so take its value and ensure + // that the minus flag is set + if p.fmt.wid < 0 { + p.fmt.wid = -p.fmt.wid + p.fmt.minus = true + p.fmt.zero = false // Do not pad with zeros to the right. + } + afterIndex = false + } else { + p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end) + if afterIndex && p.fmt.widPresent { // "%[3]2d" + p.goodArgNum = false + } + } + + // Do we have precision? + if i+1 < end && format[i] == '.' { + i++ + if afterIndex { // "%[3].2d" + p.goodArgNum = false + } + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + if i < end && format[i] == '*' { + i++ + p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum) + // Negative precision arguments don't make sense + if p.fmt.prec < 0 { + p.fmt.prec = 0 + p.fmt.precPresent = false + } + if !p.fmt.precPresent { + _, _ = p.WriteString(badPrecString) + } + afterIndex = false + } else { + p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end) + if !p.fmt.precPresent { + p.fmt.prec = 0 + p.fmt.precPresent = true + } + } + } + + if !afterIndex { + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + } + + if i >= end { + _, _ = p.WriteString(noVerbString) + break + } + + verb, size := rune(format[i]), 1 + if verb >= utf8.RuneSelf { + verb, size = utf8.DecodeRuneInString(format[i:]) + } + i += size + + switch { + case verb == '%': // Percent does not absorb operands and ignores f.wid and f.prec. + _, _ = p.WriteSingleByte('%') + case !p.goodArgNum: + p.badArgNum(verb) + case argNum >= len(a): // No argument left over to print for the current verb. + p.missingArg(verb) + case verb == 'v': + // Go syntax + p.fmt.sharpV = p.fmt.sharp + p.fmt.sharp = false + // Struct-field syntax + p.fmt.plusV = p.fmt.plus + p.fmt.plus = false + fallthrough + default: + p.printArg(a[argNum], verb) + argNum++ + } + } + + // Check for extra arguments unless the call accessed the arguments + // out of order, in which case it's too expensive to detect if they've all + // been used and arguably OK if they're not. + if !p.reordered && argNum < len(a) { + p.fmt.clearflags() + _, _ = p.WriteString(extraString) + for i, arg := range a[argNum:] { + if i > 0 { + _, _ = p.WriteString(commaSpaceString) + } + if arg == nil { + _, _ = p.WriteString(UndefinedValue.String()) + } else { + _, _ = p.WriteString(arg.TypeName()) + _, _ = p.WriteSingleByte('=') + p.printArg(arg, 'v') + } + } + _, _ = p.WriteSingleByte(')') + } + + return nil +} + +// Format formats according to a format specifier and returns the resulting string. +func Format(format string, a ...Object) (string, error) { + p := newPrinter() + err := p.doFormat(format, a) + s := string(p.buf) + p.free() + + return s, err +} diff --git a/vendor/github.com/d5/tengo/objects/map.go b/vendor/github.com/d5/tengo/objects/map.go index c42ffe93..9208872c 100644 --- a/vendor/github.com/d5/tengo/objects/map.go +++ b/vendor/github.com/d5/tengo/objects/map.go @@ -76,13 +76,13 @@ func (o *Map) Equals(x Object) bool { // IndexGet returns the value for the given key. func (o *Map) IndexGet(index Object) (res Object, err error) { - strIdx, ok := index.(*String) + strIdx, ok := ToString(index) if !ok { err = ErrInvalidIndexType return } - val, ok := o.Value[strIdx.Value] + val, ok := o.Value[strIdx] if !ok { val = UndefinedValue } diff --git a/vendor/github.com/d5/tengo/objects/undefined.go b/vendor/github.com/d5/tengo/objects/undefined.go index 79a380f5..0fdbc084 100644 --- a/vendor/github.com/d5/tengo/objects/undefined.go +++ b/vendor/github.com/d5/tengo/objects/undefined.go @@ -40,3 +40,23 @@ func (o *Undefined) Equals(x Object) bool { func (o *Undefined) IndexGet(index Object) (Object, error) { return UndefinedValue, nil } + +// Iterate creates a map iterator. +func (o *Undefined) Iterate() Iterator { + return o +} + +// Next returns true if there are more elements to iterate. +func (o *Undefined) Next() bool { + return false +} + +// Key returns the key or index value of the current element. +func (o *Undefined) Key() Object { + return o +} + +// Value returns the value of the current element. +func (o *Undefined) Value() Object { + return o +} diff --git a/vendor/github.com/d5/tengo/runtime/vm.go b/vendor/github.com/d5/tengo/runtime/vm.go index dde52db5..07f6e530 100644 --- a/vendor/github.com/d5/tengo/runtime/vm.go +++ b/vendor/github.com/d5/tengo/runtime/vm.go @@ -642,9 +642,31 @@ func (v *VM) run() { switch callee := value.(type) { case *objects.Closure: + if callee.Fn.VarArgs { + // if the closure is variadic, + // roll up all variadic parameters into an array + realArgs := callee.Fn.NumParameters - 1 + varArgs := numArgs - realArgs + if varArgs >= 0 { + numArgs = realArgs + 1 + args := make([]objects.Object, varArgs) + spStart := v.sp - varArgs + for i := spStart; i < v.sp; i++ { + args[i-spStart] = v.stack[i] + } + v.stack[spStart] = &objects.Array{Value: args} + v.sp = spStart + 1 + } + } + if numArgs != callee.Fn.NumParameters { - v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", - callee.Fn.NumParameters, numArgs) + if callee.Fn.VarArgs { + v.err = fmt.Errorf("wrong number of arguments: want>=%d, got=%d", + callee.Fn.NumParameters-1, numArgs) + } else { + v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", + callee.Fn.NumParameters, numArgs) + } return } @@ -674,9 +696,31 @@ func (v *VM) run() { v.sp = v.sp - numArgs + callee.Fn.NumLocals case *objects.CompiledFunction: + if callee.VarArgs { + // if the closure is variadic, + // roll up all variadic parameters into an array + realArgs := callee.NumParameters - 1 + varArgs := numArgs - realArgs + if varArgs >= 0 { + numArgs = realArgs + 1 + args := make([]objects.Object, varArgs) + spStart := v.sp - varArgs + for i := spStart; i < v.sp; i++ { + args[i-spStart] = v.stack[i] + } + v.stack[spStart] = &objects.Array{Value: args} + v.sp = spStart + 1 + } + } + if numArgs != callee.NumParameters { - v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", - callee.NumParameters, numArgs) + if callee.VarArgs { + v.err = fmt.Errorf("wrong number of arguments: want>=%d, got=%d", + callee.NumParameters-1, numArgs) + } else { + v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", + callee.NumParameters, numArgs) + } return } @@ -707,9 +751,7 @@ func (v *VM) run() { case objects.Callable: var args []objects.Object - for _, arg := range v.stack[v.sp-numArgs : v.sp] { - args = append(args, arg) - } + args = append(args, v.stack[v.sp-numArgs:v.sp]...) ret, e := callee.Call(args...) v.sp -= numArgs + 1 @@ -817,9 +859,12 @@ func (v *VM) run() { val := v.stack[v.sp-numSelectors-1] v.sp -= numSelectors + 1 - sp := v.curFrame.basePointer + localIndex + dst := v.stack[v.curFrame.basePointer+localIndex] + if obj, ok := dst.(*objects.ObjectPtr); ok { + dst = *obj.Value + } - if e := indexAssign(v.stack[sp], val, selectors); e != nil { + if e := indexAssign(dst, val, selectors); e != nil { v.err = e return } diff --git a/vendor/github.com/d5/tengo/script/script.go b/vendor/github.com/d5/tengo/script/script.go index cdf36713..2ee67b61 100644 --- a/vendor/github.com/d5/tengo/script/script.go +++ b/vendor/github.com/d5/tengo/script/script.go @@ -183,12 +183,3 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []obj return } - -func (s *Script) copyVariables() map[string]*Variable { - vars := make(map[string]*Variable) - for n, v := range s.variables { - vars[n] = v - } - - return vars -} diff --git a/vendor/github.com/d5/tengo/stdlib/fmt.go b/vendor/github.com/d5/tengo/stdlib/fmt.go index 9c75fc33..b8f64278 100644 --- a/vendor/github.com/d5/tengo/stdlib/fmt.go +++ b/vendor/github.com/d5/tengo/stdlib/fmt.go @@ -44,12 +44,12 @@ func fmtPrintf(args ...objects.Object) (ret objects.Object, err error) { return nil, nil } - formatArgs := make([]interface{}, numArgs-1, numArgs-1) - for idx, arg := range args[1:] { - formatArgs[idx] = objects.ToInterface(arg) + s, err := objects.Format(format.Value, args[1:]...) + if err != nil { + return nil, err } - fmt.Printf(format.Value, formatArgs...) + fmt.Print(s) return nil, nil } @@ -84,15 +84,9 @@ func fmtSprintf(args ...objects.Object) (ret objects.Object, err error) { return format, nil // okay to return 'format' directly as String is immutable } - formatArgs := make([]interface{}, numArgs-1, numArgs-1) - for idx, arg := range args[1:] { - formatArgs[idx] = objects.ToInterface(arg) - } - - s := fmt.Sprintf(format.Value, formatArgs...) - - if len(s) > tengo.MaxStringLen { - return nil, objects.ErrStringLimit + s, err := objects.Format(format.Value, args[1:]...) + if err != nil { + return nil, err } return &objects.String{Value: s}, nil diff --git a/vendor/github.com/d5/tengo/stdlib/text.go b/vendor/github.com/d5/tengo/stdlib/text.go index 9f9770b8..4b5729ec 100644 --- a/vendor/github.com/d5/tengo/stdlib/text.go +++ b/vendor/github.com/d5/tengo/stdlib/text.go @@ -32,6 +32,7 @@ var textModule = map[string]objects.Object{ "last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int "repeat": &objects.UserFunction{Name: "repeat", Value: textRepeat}, // repeat(s, count) => string "replace": &objects.UserFunction{Name: "replace", Value: textReplace}, // replace(s, old, new, n) => string + "substr": &objects.UserFunction{Name: "substr", Value: textSubstring}, // substr(s, lower, upper) => string "split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string] "split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string] "split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string] @@ -40,6 +41,9 @@ var textModule = map[string]objects.Object{ "to_lower": &objects.UserFunction{Name: "to_lower", Value: FuncASRS(strings.ToLower)}, // to_lower(s) => string "to_title": &objects.UserFunction{Name: "to_title", Value: FuncASRS(strings.ToTitle)}, // to_title(s) => string "to_upper": &objects.UserFunction{Name: "to_upper", Value: FuncASRS(strings.ToUpper)}, // to_upper(s) => string + "pad_left": &objects.UserFunction{Name: "pad_left", Value: textPadLeft}, // pad_left(s, pad_len, pad_with) => string + "pad_right": &objects.UserFunction{Name: "pad_right", Value: textPadRight}, // pad_right(s, pad_len, pad_with) => string + "trim": &objects.UserFunction{Name: "trim", Value: FuncASSRS(strings.Trim)}, // trim(s, cutset) => string "trim_left": &objects.UserFunction{Name: "trim_left", Value: FuncASSRS(strings.TrimLeft)}, // trim_left(s, cutset) => string "trim_prefix": &objects.UserFunction{Name: "trim_prefix", Value: FuncASSRS(strings.TrimPrefix)}, // trim_prefix(s, prefix) => string "trim_right": &objects.UserFunction{Name: "trim_right", Value: FuncASSRS(strings.TrimRight)}, // trim_right(s, cutset) => string @@ -376,6 +380,195 @@ func textReplace(args ...objects.Object) (ret objects.Object, err error) { return } +func textSubstring(args ...objects.Object) (ret objects.Object, err error) { + argslen := len(args) + if argslen != 2 && argslen != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + strlen := len(s1) + i3 := strlen + if argslen == 3 { + i3, ok = objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + if i2 > i3 { + err = objects.ErrInvalidIndexType + return + } + + if i2 < 0 { + i2 = 0 + } else if i2 > strlen { + i2 = strlen + } + + if i3 < 0 { + i3 = 0 + } else if i3 > strlen { + i3 = strlen + } + + ret = &objects.String{Value: s1[i2:i3]} + + return +} + +func textPadLeft(args ...objects.Object) (ret objects.Object, err error) { + argslen := len(args) + if argslen != 2 && argslen != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + if i2 > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + sLen := len(s1) + if sLen >= i2 { + ret = &objects.String{Value: s1} + return + } + + s3 := " " + if argslen == 3 { + s3, ok = objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + padStrLen := len(s3) + if padStrLen == 0 { + ret = &objects.String{Value: s1} + return + } + + padCount := ((i2 - padStrLen) / padStrLen) + 1 + retStr := strings.Repeat(s3, int(padCount)) + s1 + ret = &objects.String{Value: retStr[len(retStr)-i2:]} + + return +} + +func textPadRight(args ...objects.Object) (ret objects.Object, err error) { + argslen := len(args) + if argslen != 2 && argslen != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + if i2 > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + sLen := len(s1) + if sLen >= i2 { + ret = &objects.String{Value: s1} + return + } + + s3 := " " + if argslen == 3 { + s3, ok = objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + padStrLen := len(s3) + if padStrLen == 0 { + ret = &objects.String{Value: s1} + return + } + + padCount := ((i2 - padStrLen) / padStrLen) + 1 + retStr := s1 + strings.Repeat(s3, int(padCount)) + ret = &objects.String{Value: retStr[:i2]} + + return +} + func textRepeat(args ...objects.Object) (ret objects.Object, err error) { if len(args) != 2 { return nil, objects.ErrWrongNumArguments |