podman
811 строк · 21.6 Кб
1package toml
2
3import (
4"fmt"
5"os"
6"strconv"
7"strings"
8"time"
9"unicode/utf8"
10
11"github.com/BurntSushi/toml/internal"
12)
13
14type parser struct {
15lx *lexer
16context Key // Full key for the current hash in scope.
17currentKey string // Base key name for everything except hashes.
18pos Position // Current position in the TOML file.
19tomlNext bool
20
21ordered []Key // List of keys in the order that they appear in the TOML data.
22
23keyInfo map[string]keyInfo // Map keyname → info about the TOML key.
24mapping map[string]interface{} // Map keyname → key value.
25implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names").
26}
27
28type keyInfo struct {
29pos Position
30tomlType tomlType
31}
32
33func parse(data string) (p *parser, err error) {
34_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
35
36defer func() {
37if r := recover(); r != nil {
38if pErr, ok := r.(ParseError); ok {
39pErr.input = data
40err = pErr
41return
42}
43panic(r)
44}
45}()
46
47// Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString()
48// which mangles stuff. UTF-16 BOM isn't strictly valid, but some tools add
49// it anyway.
50if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { // UTF-16
51data = data[2:]
52} else if strings.HasPrefix(data, "\xef\xbb\xbf") { // UTF-8
53data = data[3:]
54}
55
56// Examine first few bytes for NULL bytes; this probably means it's a UTF-16
57// file (second byte in surrogate pair being NULL). Again, do this here to
58// avoid having to deal with UTF-8/16 stuff in the lexer.
59ex := 6
60if len(data) < 6 {
61ex = len(data)
62}
63if i := strings.IndexRune(data[:ex], 0); i > -1 {
64return nil, ParseError{
65Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8",
66Position: Position{Line: 1, Start: i, Len: 1},
67Line: 1,
68input: data,
69}
70}
71
72p = &parser{
73keyInfo: make(map[string]keyInfo),
74mapping: make(map[string]interface{}),
75lx: lex(data, tomlNext),
76ordered: make([]Key, 0),
77implicits: make(map[string]struct{}),
78tomlNext: tomlNext,
79}
80for {
81item := p.next()
82if item.typ == itemEOF {
83break
84}
85p.topLevel(item)
86}
87
88return p, nil
89}
90
91func (p *parser) panicErr(it item, err error) {
92panic(ParseError{
93err: err,
94Position: it.pos,
95Line: it.pos.Len,
96LastKey: p.current(),
97})
98}
99
100func (p *parser) panicItemf(it item, format string, v ...interface{}) {
101panic(ParseError{
102Message: fmt.Sprintf(format, v...),
103Position: it.pos,
104Line: it.pos.Len,
105LastKey: p.current(),
106})
107}
108
109func (p *parser) panicf(format string, v ...interface{}) {
110panic(ParseError{
111Message: fmt.Sprintf(format, v...),
112Position: p.pos,
113Line: p.pos.Line,
114LastKey: p.current(),
115})
116}
117
118func (p *parser) next() item {
119it := p.lx.nextItem()
120//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val)
121if it.typ == itemError {
122if it.err != nil {
123panic(ParseError{
124Position: it.pos,
125Line: it.pos.Line,
126LastKey: p.current(),
127err: it.err,
128})
129}
130
131p.panicItemf(it, "%s", it.val)
132}
133return it
134}
135
136func (p *parser) nextPos() item {
137it := p.next()
138p.pos = it.pos
139return it
140}
141
142func (p *parser) bug(format string, v ...interface{}) {
143panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
144}
145
146func (p *parser) expect(typ itemType) item {
147it := p.next()
148p.assertEqual(typ, it.typ)
149return it
150}
151
152func (p *parser) assertEqual(expected, got itemType) {
153if expected != got {
154p.bug("Expected '%s' but got '%s'.", expected, got)
155}
156}
157
158func (p *parser) topLevel(item item) {
159switch item.typ {
160case itemCommentStart: // # ..
161p.expect(itemText)
162case itemTableStart: // [ .. ]
163name := p.nextPos()
164
165var key Key
166for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() {
167key = append(key, p.keyString(name))
168}
169p.assertEqual(itemTableEnd, name.typ)
170
171p.addContext(key, false)
172p.setType("", tomlHash, item.pos)
173p.ordered = append(p.ordered, key)
174case itemArrayTableStart: // [[ .. ]]
175name := p.nextPos()
176
177var key Key
178for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() {
179key = append(key, p.keyString(name))
180}
181p.assertEqual(itemArrayTableEnd, name.typ)
182
183p.addContext(key, true)
184p.setType("", tomlArrayHash, item.pos)
185p.ordered = append(p.ordered, key)
186case itemKeyStart: // key = ..
187outerContext := p.context
188/// Read all the key parts (e.g. 'a' and 'b' in 'a.b')
189k := p.nextPos()
190var key Key
191for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
192key = append(key, p.keyString(k))
193}
194p.assertEqual(itemKeyEnd, k.typ)
195
196/// The current key is the last part.
197p.currentKey = key[len(key)-1]
198
199/// All the other parts (if any) are the context; need to set each part
200/// as implicit.
201context := key[:len(key)-1]
202for i := range context {
203p.addImplicitContext(append(p.context, context[i:i+1]...))
204}
205p.ordered = append(p.ordered, p.context.add(p.currentKey))
206
207/// Set value.
208vItem := p.next()
209val, typ := p.value(vItem, false)
210p.set(p.currentKey, val, typ, vItem.pos)
211
212/// Remove the context we added (preserving any context from [tbl] lines).
213p.context = outerContext
214p.currentKey = ""
215default:
216p.bug("Unexpected type at top level: %s", item.typ)
217}
218}
219
220// Gets a string for a key (or part of a key in a table name).
221func (p *parser) keyString(it item) string {
222switch it.typ {
223case itemText:
224return it.val
225case itemString, itemMultilineString,
226itemRawString, itemRawMultilineString:
227s, _ := p.value(it, false)
228return s.(string)
229default:
230p.bug("Unexpected key type: %s", it.typ)
231}
232panic("unreachable")
233}
234
235var datetimeRepl = strings.NewReplacer(
236"z", "Z",
237"t", "T",
238" ", "T")
239
240// value translates an expected value from the lexer into a Go value wrapped
241// as an empty interface.
242func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) {
243switch it.typ {
244case itemString:
245return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it)
246case itemMultilineString:
247return p.replaceEscapes(it, p.stripEscapedNewlines(stripFirstNewline(it.val))), p.typeOfPrimitive(it)
248case itemRawString:
249return it.val, p.typeOfPrimitive(it)
250case itemRawMultilineString:
251return stripFirstNewline(it.val), p.typeOfPrimitive(it)
252case itemInteger:
253return p.valueInteger(it)
254case itemFloat:
255return p.valueFloat(it)
256case itemBool:
257switch it.val {
258case "true":
259return true, p.typeOfPrimitive(it)
260case "false":
261return false, p.typeOfPrimitive(it)
262default:
263p.bug("Expected boolean value, but got '%s'.", it.val)
264}
265case itemDatetime:
266return p.valueDatetime(it)
267case itemArray:
268return p.valueArray(it)
269case itemInlineTableStart:
270return p.valueInlineTable(it, parentIsArray)
271default:
272p.bug("Unexpected value type: %s", it.typ)
273}
274panic("unreachable")
275}
276
277func (p *parser) valueInteger(it item) (interface{}, tomlType) {
278if !numUnderscoresOK(it.val) {
279p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val)
280}
281if numHasLeadingZero(it.val) {
282p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val)
283}
284
285num, err := strconv.ParseInt(it.val, 0, 64)
286if err != nil {
287// Distinguish integer values. Normally, it'd be a bug if the lexer
288// provides an invalid integer, but it's possible that the number is
289// out of range of valid values (which the lexer cannot determine).
290// So mark the former as a bug but the latter as a legitimate user
291// error.
292if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
293p.panicErr(it, errParseRange{i: it.val, size: "int64"})
294} else {
295p.bug("Expected integer value, but got '%s'.", it.val)
296}
297}
298return num, p.typeOfPrimitive(it)
299}
300
301func (p *parser) valueFloat(it item) (interface{}, tomlType) {
302parts := strings.FieldsFunc(it.val, func(r rune) bool {
303switch r {
304case '.', 'e', 'E':
305return true
306}
307return false
308})
309for _, part := range parts {
310if !numUnderscoresOK(part) {
311p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val)
312}
313}
314if len(parts) > 0 && numHasLeadingZero(parts[0]) {
315p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val)
316}
317if !numPeriodsOK(it.val) {
318// As a special case, numbers like '123.' or '1.e2',
319// which are valid as far as Go/strconv are concerned,
320// must be rejected because TOML says that a fractional
321// part consists of '.' followed by 1+ digits.
322p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val)
323}
324val := strings.Replace(it.val, "_", "", -1)
325if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does.
326val = "nan"
327}
328num, err := strconv.ParseFloat(val, 64)
329if err != nil {
330if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
331p.panicErr(it, errParseRange{i: it.val, size: "float64"})
332} else {
333p.panicItemf(it, "Invalid float value: %q", it.val)
334}
335}
336return num, p.typeOfPrimitive(it)
337}
338
339var dtTypes = []struct {
340fmt string
341zone *time.Location
342next bool
343}{
344{time.RFC3339Nano, time.Local, false},
345{"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false},
346{"2006-01-02", internal.LocalDate, false},
347{"15:04:05.999999999", internal.LocalTime, false},
348
349// tomlNext
350{"2006-01-02T15:04Z07:00", time.Local, true},
351{"2006-01-02T15:04", internal.LocalDatetime, true},
352{"15:04", internal.LocalTime, true},
353}
354
355func (p *parser) valueDatetime(it item) (interface{}, tomlType) {
356it.val = datetimeRepl.Replace(it.val)
357var (
358t time.Time
359ok bool
360err error
361)
362for _, dt := range dtTypes {
363if dt.next && !p.tomlNext {
364continue
365}
366t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
367if err == nil {
368ok = true
369break
370}
371}
372if !ok {
373p.panicItemf(it, "Invalid TOML Datetime: %q.", it.val)
374}
375return t, p.typeOfPrimitive(it)
376}
377
378func (p *parser) valueArray(it item) (interface{}, tomlType) {
379p.setType(p.currentKey, tomlArray, it.pos)
380
381var (
382types []tomlType
383
384// Initialize to a non-nil empty slice. This makes it consistent with
385// how S = [] decodes into a non-nil slice inside something like struct
386// { S []string }. See #338
387array = []interface{}{}
388)
389for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
390if it.typ == itemCommentStart {
391p.expect(itemText)
392continue
393}
394
395val, typ := p.value(it, true)
396array = append(array, val)
397types = append(types, typ)
398
399// XXX: types isn't used here, we need it to record the accurate type
400// information.
401//
402// Not entirely sure how to best store this; could use "key[0]",
403// "key[1]" notation, or maybe store it on the Array type?
404_ = types
405}
406return array, tomlArray
407}
408
409func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tomlType) {
410var (
411hash = make(map[string]interface{})
412outerContext = p.context
413outerKey = p.currentKey
414)
415
416p.context = append(p.context, p.currentKey)
417prevContext := p.context
418p.currentKey = ""
419
420p.addImplicit(p.context)
421p.addContext(p.context, parentIsArray)
422
423/// Loop over all table key/value pairs.
424for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
425if it.typ == itemCommentStart {
426p.expect(itemText)
427continue
428}
429
430/// Read all key parts.
431k := p.nextPos()
432var key Key
433for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
434key = append(key, p.keyString(k))
435}
436p.assertEqual(itemKeyEnd, k.typ)
437
438/// The current key is the last part.
439p.currentKey = key[len(key)-1]
440
441/// All the other parts (if any) are the context; need to set each part
442/// as implicit.
443context := key[:len(key)-1]
444for i := range context {
445p.addImplicitContext(append(p.context, context[i:i+1]...))
446}
447p.ordered = append(p.ordered, p.context.add(p.currentKey))
448
449/// Set the value.
450val, typ := p.value(p.next(), false)
451p.set(p.currentKey, val, typ, it.pos)
452hash[p.currentKey] = val
453
454/// Restore context.
455p.context = prevContext
456}
457p.context = outerContext
458p.currentKey = outerKey
459return hash, tomlHash
460}
461
462// numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
463// +/- signs, and base prefixes.
464func numHasLeadingZero(s string) bool {
465if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x
466return true
467}
468if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' {
469return true
470}
471return false
472}
473
474// numUnderscoresOK checks whether each underscore in s is surrounded by
475// characters that are not underscores.
476func numUnderscoresOK(s string) bool {
477switch s {
478case "nan", "+nan", "-nan", "inf", "-inf", "+inf":
479return true
480}
481accept := false
482for _, r := range s {
483if r == '_' {
484if !accept {
485return false
486}
487}
488
489// isHexadecimal is a superset of all the permissable characters
490// surrounding an underscore.
491accept = isHexadecimal(r)
492}
493return accept
494}
495
496// numPeriodsOK checks whether every period in s is followed by a digit.
497func numPeriodsOK(s string) bool {
498period := false
499for _, r := range s {
500if period && !isDigit(r) {
501return false
502}
503period = r == '.'
504}
505return !period
506}
507
508// Set the current context of the parser, where the context is either a hash or
509// an array of hashes, depending on the value of the `array` parameter.
510//
511// Establishing the context also makes sure that the key isn't a duplicate, and
512// will create implicit hashes automatically.
513func (p *parser) addContext(key Key, array bool) {
514var ok bool
515
516// Always start at the top level and drill down for our context.
517hashContext := p.mapping
518keyContext := make(Key, 0)
519
520// We only need implicit hashes for key[0:-1]
521for _, k := range key[0 : len(key)-1] {
522_, ok = hashContext[k]
523keyContext = append(keyContext, k)
524
525// No key? Make an implicit hash and move on.
526if !ok {
527p.addImplicit(keyContext)
528hashContext[k] = make(map[string]interface{})
529}
530
531// If the hash context is actually an array of tables, then set
532// the hash context to the last element in that array.
533//
534// Otherwise, it better be a table, since this MUST be a key group (by
535// virtue of it not being the last element in a key).
536switch t := hashContext[k].(type) {
537case []map[string]interface{}:
538hashContext = t[len(t)-1]
539case map[string]interface{}:
540hashContext = t
541default:
542p.panicf("Key '%s' was already created as a hash.", keyContext)
543}
544}
545
546p.context = keyContext
547if array {
548// If this is the first element for this array, then allocate a new
549// list of tables for it.
550k := key[len(key)-1]
551if _, ok := hashContext[k]; !ok {
552hashContext[k] = make([]map[string]interface{}, 0, 4)
553}
554
555// Add a new table. But make sure the key hasn't already been used
556// for something else.
557if hash, ok := hashContext[k].([]map[string]interface{}); ok {
558hashContext[k] = append(hash, make(map[string]interface{}))
559} else {
560p.panicf("Key '%s' was already created and cannot be used as an array.", key)
561}
562} else {
563p.setValue(key[len(key)-1], make(map[string]interface{}))
564}
565p.context = append(p.context, key[len(key)-1])
566}
567
568// set calls setValue and setType.
569func (p *parser) set(key string, val interface{}, typ tomlType, pos Position) {
570p.setValue(key, val)
571p.setType(key, typ, pos)
572}
573
574// setValue sets the given key to the given value in the current context.
575// It will make sure that the key hasn't already been defined, account for
576// implicit key groups.
577func (p *parser) setValue(key string, value interface{}) {
578var (
579tmpHash interface{}
580ok bool
581hash = p.mapping
582keyContext Key
583)
584for _, k := range p.context {
585keyContext = append(keyContext, k)
586if tmpHash, ok = hash[k]; !ok {
587p.bug("Context for key '%s' has not been established.", keyContext)
588}
589switch t := tmpHash.(type) {
590case []map[string]interface{}:
591// The context is a table of hashes. Pick the most recent table
592// defined as the current hash.
593hash = t[len(t)-1]
594case map[string]interface{}:
595hash = t
596default:
597p.panicf("Key '%s' has already been defined.", keyContext)
598}
599}
600keyContext = append(keyContext, key)
601
602if _, ok := hash[key]; ok {
603// Normally redefining keys isn't allowed, but the key could have been
604// defined implicitly and it's allowed to be redefined concretely. (See
605// the `valid/implicit-and-explicit-after.toml` in toml-test)
606//
607// But we have to make sure to stop marking it as an implicit. (So that
608// another redefinition provokes an error.)
609//
610// Note that since it has already been defined (as a hash), we don't
611// want to overwrite it. So our business is done.
612if p.isArray(keyContext) {
613p.removeImplicit(keyContext)
614hash[key] = value
615return
616}
617if p.isImplicit(keyContext) {
618p.removeImplicit(keyContext)
619return
620}
621
622// Otherwise, we have a concrete key trying to override a previous
623// key, which is *always* wrong.
624p.panicf("Key '%s' has already been defined.", keyContext)
625}
626
627hash[key] = value
628}
629
630// setType sets the type of a particular value at a given key. It should be
631// called immediately AFTER setValue.
632//
633// Note that if `key` is empty, then the type given will be applied to the
634// current context (which is either a table or an array of tables).
635func (p *parser) setType(key string, typ tomlType, pos Position) {
636keyContext := make(Key, 0, len(p.context)+1)
637keyContext = append(keyContext, p.context...)
638if len(key) > 0 { // allow type setting for hashes
639keyContext = append(keyContext, key)
640}
641// Special case to make empty keys ("" = 1) work.
642// Without it it will set "" rather than `""`.
643// TODO: why is this needed? And why is this only needed here?
644if len(keyContext) == 0 {
645keyContext = Key{""}
646}
647p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos}
648}
649
650// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
651// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly).
652func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} }
653func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) }
654func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok }
655func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray }
656func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) }
657
658// current returns the full key name of the current context.
659func (p *parser) current() string {
660if len(p.currentKey) == 0 {
661return p.context.String()
662}
663if len(p.context) == 0 {
664return p.currentKey
665}
666return fmt.Sprintf("%s.%s", p.context, p.currentKey)
667}
668
669func stripFirstNewline(s string) string {
670if len(s) > 0 && s[0] == '\n' {
671return s[1:]
672}
673if len(s) > 1 && s[0] == '\r' && s[1] == '\n' {
674return s[2:]
675}
676return s
677}
678
679// stripEscapedNewlines removes whitespace after line-ending backslashes in
680// multiline strings.
681//
682// A line-ending backslash is an unescaped \ followed only by whitespace until
683// the next newline. After a line-ending backslash, all whitespace is removed
684// until the next non-whitespace character.
685func (p *parser) stripEscapedNewlines(s string) string {
686var b strings.Builder
687var i int
688for {
689ix := strings.Index(s[i:], `\`)
690if ix < 0 {
691b.WriteString(s)
692return b.String()
693}
694i += ix
695
696if len(s) > i+1 && s[i+1] == '\\' {
697// Escaped backslash.
698i += 2
699continue
700}
701// Scan until the next non-whitespace.
702j := i + 1
703whitespaceLoop:
704for ; j < len(s); j++ {
705switch s[j] {
706case ' ', '\t', '\r', '\n':
707default:
708break whitespaceLoop
709}
710}
711if j == i+1 {
712// Not a whitespace escape.
713i++
714continue
715}
716if !strings.Contains(s[i:j], "\n") {
717// This is not a line-ending backslash.
718// (It's a bad escape sequence, but we can let
719// replaceEscapes catch it.)
720i++
721continue
722}
723b.WriteString(s[:i])
724s = s[j:]
725i = 0
726}
727}
728
729func (p *parser) replaceEscapes(it item, str string) string {
730replaced := make([]rune, 0, len(str))
731s := []byte(str)
732r := 0
733for r < len(s) {
734if s[r] != '\\' {
735c, size := utf8.DecodeRune(s[r:])
736r += size
737replaced = append(replaced, c)
738continue
739}
740r += 1
741if r >= len(s) {
742p.bug("Escape sequence at end of string.")
743return ""
744}
745switch s[r] {
746default:
747p.bug("Expected valid escape code after \\, but got %q.", s[r])
748case ' ', '\t':
749p.panicItemf(it, "invalid escape: '\\%c'", s[r])
750case 'b':
751replaced = append(replaced, rune(0x0008))
752r += 1
753case 't':
754replaced = append(replaced, rune(0x0009))
755r += 1
756case 'n':
757replaced = append(replaced, rune(0x000A))
758r += 1
759case 'f':
760replaced = append(replaced, rune(0x000C))
761r += 1
762case 'r':
763replaced = append(replaced, rune(0x000D))
764r += 1
765case 'e':
766if p.tomlNext {
767replaced = append(replaced, rune(0x001B))
768r += 1
769}
770case '"':
771replaced = append(replaced, rune(0x0022))
772r += 1
773case '\\':
774replaced = append(replaced, rune(0x005C))
775r += 1
776case 'x':
777if p.tomlNext {
778escaped := p.asciiEscapeToUnicode(it, s[r+1:r+3])
779replaced = append(replaced, escaped)
780r += 3
781}
782case 'u':
783// At this point, we know we have a Unicode escape of the form
784// `uXXXX` at [r, r+5). (Because the lexer guarantees this
785// for us.)
786escaped := p.asciiEscapeToUnicode(it, s[r+1:r+5])
787replaced = append(replaced, escaped)
788r += 5
789case 'U':
790// At this point, we know we have a Unicode escape of the form
791// `uXXXX` at [r, r+9). (Because the lexer guarantees this
792// for us.)
793escaped := p.asciiEscapeToUnicode(it, s[r+1:r+9])
794replaced = append(replaced, escaped)
795r += 9
796}
797}
798return string(replaced)
799}
800
801func (p *parser) asciiEscapeToUnicode(it item, bs []byte) rune {
802s := string(bs)
803hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
804if err != nil {
805p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err)
806}
807if !utf8.ValidRune(rune(hex)) {
808p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s)
809}
810return rune(hex)
811}
812