func utf8Offset(state *lua.State) int {
	var (
		s = state.CheckString(1)
		n = state.CheckInt(2)
		i = int64(1)
	)
	if n < 0 {
		i = int64(len(s) + 1)
	}
	i = int64(strPos(len(s), int(state.OptInt(3, i))))
	if i < 1 || i > int64(len(s))+1 {
		panic(fmt.Errorf("position out of range"))
	}
	i--
	if n == 0 {
		for i > 0 && isContByte(s[i]) {
			i--
		}
	} else {
		if i < int64(len(s)) && isContByte(s[i]) {
			panic(fmt.Errorf("initial position is a continuation byte"))
		}
		if n < 0 {
			for n < 0 && i > 0 {
				for {
					i--
					if !(i > 0 && isContByte(s[i])) {
						break
					}
				}
				n++
			}
		} else {
			n--
			for n > 0 && i < int64(len(s)) {
				for {
					i++
					if i >= int64(len(s)) || !isContByte(s[i]) {
						break
					}
				}
				n--
			}
		}
	}
	if n == 0 {
		state.Push(i + 1)
	} else {
		state.Push(nil)
	}
	return 1
}
utf8.offset (s, n [, i])

Returns the position (in bytes) where the encoding of the n-th character
of s (counting from position i) starts. A negative n gets characters before
position i. The default for i is 1 when n is non-negative and #s + 1 otherwise,
so that utf8.offset(s, -n) gets the offset of the n-th character from the end of
the string. If the specified character is neither in the subject nor right after
its end, the function returns nil. As a special case, when n is 0 the function
returns the start of the encoding of the character that contains the i-th byte
of s.

This function assumes that s is a valid UTF-8 string.

offset(s, n, [i]) -> index where n-th character counting from position 'i'
starts; 0 means character at 'i'.

See https://www.lua.org/manual/5.3/manual.html#pdf-utf8.offset
package utf8
import (
	"fmt"
	"unicode/utf8"
	"github.com/Azure/golua/lua"
)
const unicodeMax = 0x10FFFF
func Open(state *lua.State) int {
	var utf8Funcs = map[string]lua.Func{
		"char":      lua.Func(utf8Char),
		"codepoint": lua.Func(utf8CodePoint),
		"codes":     lua.Func(utf8Codes),
		"len":       lua.Func(utf8Len),
		"offset":    lua.Func(utf8Offset),
	}
	state.NewTableSize(0, len(utf8Funcs))
	state.SetFuncs(utf8Funcs, 0)
	const pattern = `[\0-\x7F\xC2-\xF4][\x80-\xBF]*`
	state.Push(pattern)
	state.SetField(-2, "charpattern")
	return 1
}
func utf8Char(state *lua.State) int {
	runes := make([]rune, state.Top())
	for i := 1; i <= state.Top(); i++ {
		c := state.CheckInt(i)
		if c < 0 || c > unicodeMax {
			panic(fmt.Errorf("bad argument #1 to 'char' (value out of range)"))
		}
		runes[i-1] = rune(c)
	}
	var (
		buf = make([]byte, 6)
		str string
	)
	for _, r := range runes {
		w := utf8.EncodeRune(buf, r)
		str += string(buf[:w])
	}
	state.Push(str)
	return 1
}
func utf8Codes(state *lua.State) int {
	iter := lua.Func(func(state *lua.State) int {
		var (
			s = state.CheckString(1)
			n = state.ToInt(2)
		)
		if n > 0 && n < int64(len(s)) {
			n++
			for n < int64(len(s)) && isContByte(s[n]) {
				n++
			}
		}
		if n >= int64(len(s)) {
			return 0
		}
		r, _ := utf8.DecodeRuneInString(s[n:])
		if r == utf8.RuneError {
			panic(fmt.Errorf("invalid UTF-8 code"))
		}
		state.Push(n + 1)
		state.Push(int64(r))
		return 2
	})
	state.CheckString(1)
	state.Push(iter)
	state.PushIndex(1)
	state.Push(0)
	return 3
}
func utf8CodePoint(state *lua.State) int {
	var (
		s = state.CheckString(1)
		i = state.OptInt(2, 1)
		j = state.OptInt(3, i)
		n = 0
	)
	if i = int64(strPos(len(s), int(i))); i < 1 {
		panic(fmt.Errorf("bad argument #2 to 'codepoint' (out of range)"))
	}
	if j = int64(strPos(len(s), int(j))); j > int64(len(s)) {
		panic(fmt.Errorf("bad argument #3 to 'codepoint' (out of range)"))
	}
	for s = s[i-1:]; i <= j; {
		r, w := utf8.DecodeRuneInString(s)
		if r == utf8.RuneError {
			panic(fmt.Errorf("invalid UTF-8 code"))
		}
		state.Push(int64(r))
		i += int64(w)
		s = s[w:]
		n++
	}
	return n
}
func utf8Len(state *lua.State) int {
	var (
		s = state.CheckString(1)
		i = int64(strPos(len(s), int(state.OptInt(2, 1))))
		j = int64(strPos(len(s), int(state.OptInt(3, -1))))
		n = int64(0)
	)
	state.ArgCheck(1 <= i && i <= int64(len(s))+1, 2, "initial position out of string")
	state.ArgCheck(j <= int64(len(s)), 3, "final position out of string")
	if i <= j {
		for s = s[i-1 : j]; len(s) > 0; {
			r, w := utf8.DecodeRuneInString(s)
			if r == utf8.RuneError {
				state.Push(false)
				state.Push(i)
				return 2
			}
			i += int64(w)
			s = s[w:]
			n++
		}
	}
	state.Push(n)
	return 1
}