diff --git a/interp/functions.go b/interp/functions.go index c95bd93..a82e450 100644 --- a/interp/functions.go +++ b/interp/functions.go @@ -449,51 +449,3 @@ func (p *interp) sprintf(format string, args []value) (string, error) { } return fmt.Sprintf(format, converted...), nil } - -func substrChars(s string, pos int) string { - // Count characters till we get to pos. - chars := 1 - start := 0 - for start = range s { - chars++ - if chars > pos { - break - } - } - if pos >= chars { - start = len(s) - } - return s[start:] -} - -func substrLengthChars(s string, pos, length int) string { - // Count characters till we get to pos. - chars := 1 - start := 0 - for start = range s { - chars++ - if chars > pos { - break - } - } - if pos >= chars { - start = len(s) - } - - // Count characters from start till we reach length. - chars = 0 - end := 0 - for end = range s[start:] { - chars++ - if chars > length { - break - } - } - if length >= chars { - end = len(s) - } else { - end += start - } - - return s[start:end] -} diff --git a/interp/value.go b/interp/value.go index 3079568..c584628 100644 --- a/interp/value.go +++ b/interp/value.go @@ -22,7 +22,8 @@ const ( type value struct { typ valueType // Type of value s string // String value (for typeStr and typeNumStr) - n float64 // Numeric value (for typeNum) + r *[]rune + n float64 // Numeric value (for typeNum) } // Create a new null value @@ -37,12 +38,16 @@ func num(n float64) value { // Create a new string value func str(s string) value { - return value{typ: typeStr, s: s} + return value{typ: typeStr, s: s, r: new([]rune)} +} + +func strFromRunes(runes []rune) value { + return value{typ: typeStr, s: string(runes), r: &runes} } // Create a new value to represent a "numeric string" from an input field func numStr(s string) value { - return value{typ: typeNumStr, s: s} + return value{typ: typeNumStr, s: s, r: new([]rune)} } // Create a numeric value from a Go bool @@ -53,6 +58,18 @@ func boolean(b bool) value { return num(0) } +func (v value) runes(floatFormat string) []rune { + switch v.typ { + case typeStr, typeNumStr: + if *v.r == nil { + *v.r = []rune(v.s) + } + return *v.r + default: // typeNum, typeNull + return []rune(v.str(floatFormat)) + } +} + // String returns a string representation of v for debugging. func (v value) String() string { switch v.typ { diff --git a/interp/vm.go b/interp/vm.go index 0e3b1b0..eed564c 100644 --- a/interp/vm.go +++ b/interp/vm.go @@ -1001,12 +1001,12 @@ func (p *interp) callBuiltin(builtinOp compiler.BuiltinOp) error { p.push(num(float64(length))) case compiler.BuiltinLengthArg: - s := p.toString(p.peekTop()) + s := p.peekTop() var length int if p.chars { - length = utf8.RuneCountInString(s) + length = len(s.runes(p.convertFormat)) } else { - length = len(s) + length = len(p.toString(s)) } p.replaceTop(num(float64(length))) @@ -1066,9 +1066,18 @@ func (p *interp) callBuiltin(builtinOp compiler.BuiltinOp) error { sValue, posValue := p.peekPop() pos := int(posValue.num()) s := p.toString(sValue) - var substr string + var substr value if p.chars { - substr = substrChars(s, pos) + runes := sValue.runes(p.convertFormat) + if pos > len(runes) { + pos = len(runes) + 1 + } + if pos < 1 { + pos = 1 + } + length := len(runes) - pos + 1 + runes = runes[pos-1 : pos-1+length] + substr = strFromRunes(runes) } else { if pos > len(s) { pos = len(s) + 1 @@ -1077,19 +1086,34 @@ func (p *interp) callBuiltin(builtinOp compiler.BuiltinOp) error { pos = 1 } length := len(s) - pos + 1 - substr = s[pos-1 : pos-1+length] + substr = str(s[pos-1 : pos-1+length]) } - p.replaceTop(str(substr)) + p.replaceTop(substr) case compiler.BuiltinSubstrLength: posValue, lengthValue := p.popTwo() length := int(lengthValue.num()) pos := int(posValue.num()) - s := p.toString(p.peekTop()) - var substr string + sValue := p.peekTop() + var substr value if p.chars { - substr = substrLengthChars(s, pos, length) + runes := sValue.runes(p.convertFormat) + if pos > len(runes) { + pos = len(runes) + 1 + } + if pos < 1 { + pos = 1 + } + maxLength := len(runes) - pos + 1 + if length < 0 { + length = 0 + } + if length > maxLength { + length = maxLength + } + substr = strFromRunes(runes[pos-1 : pos-1+length]) } else { + s := p.toString(sValue) if pos > len(s) { pos = len(s) + 1 } @@ -1103,9 +1127,9 @@ func (p *interp) callBuiltin(builtinOp compiler.BuiltinOp) error { if length > maxLength { length = maxLength } - substr = s[pos-1 : pos-1+length] + substr = str(s[pos-1 : pos-1+length]) } - p.replaceTop(str(substr)) + p.replaceTop(substr) case compiler.BuiltinSystem: if p.noExec {