| package toml | |
| import ( | |
| "fmt" | |
| "math" | |
| "strconv" | |
| "strings" | |
| "time" | |
| "unicode/utf8" | |
| "github.com/BurntSushi/toml/internal" | |
| ) | |
| type parser struct { | |
| lx *lexer | |
| maxNest int | |
| context Key // Full key for the current hash in scope. | |
| currentKey string // Base key name for everything except hashes. | |
| pos Position // Current position in the TOML file. | |
| ordered []Key // List of keys in the order that they appear in the TOML data. | |
| keyInfo map[string]keyInfo // Map keyname → info about the TOML key. | |
| mapping map[string]any // Map keyname → key value. | |
| implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names"). | |
| } | |
| type keyInfo struct { | |
| pos Position | |
| tomlType tomlType | |
| } | |
| func parse(data string, maxNest int) (p *parser, err error) { | |
| defer func() { | |
| if r := recover(); r != nil { | |
| if pErr, ok := r.(ParseError); ok { | |
| pErr.input = data | |
| err = pErr | |
| return | |
| } | |
| panic(r) | |
| } | |
| }() | |
| // Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString() | |
| // which mangles stuff. UTF-16 BOM isn't strictly valid, but some tools add | |
| // it anyway. | |
| if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { // UTF-16 | |
| data = data[2:] | |
| } else if strings.HasPrefix(data, "\xef\xbb\xbf") { // UTF-8 | |
| data = data[3:] | |
| } | |
| // Examine first few bytes for NULL bytes; this probably means it's a UTF-16 | |
| // file (second byte in surrogate pair being NULL). Again, do this here to | |
| // avoid having to deal with UTF-8/16 stuff in the lexer. | |
| ex := 6 | |
| if len(data) < 6 { | |
| ex = len(data) | |
| } | |
| if i := strings.IndexRune(data[:ex], 0); i > -1 { | |
| return nil, ParseError{ | |
| Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8", | |
| Position: Position{Line: 1, Col: 1, Start: i, Len: 1}, | |
| Line: 1, | |
| input: data, | |
| } | |
| } | |
| p = &parser{ | |
| lx: lex(data), | |
| maxNest: maxNest, | |
| keyInfo: make(map[string]keyInfo), | |
| mapping: make(map[string]any), | |
| ordered: make([]Key, 0), | |
| implicits: make(map[string]struct{}), | |
| } | |
| for { | |
| item := p.next() | |
| if item.typ == itemEOF { | |
| break | |
| } | |
| p.topLevel(item) | |
| } | |
| return p, nil | |
| } | |
| func (p *parser) panicErr(it item, err error) { | |
| panic(ParseError{ | |
| Message: err.Error(), | |
| err: err, | |
| Position: it.pos.withCol(p.lx.input), | |
| Line: it.pos.Len, | |
| LastKey: p.current(), | |
| }) | |
| } | |
| func (p *parser) panicItemf(it item, format string, v ...any) { | |
| panic(ParseError{ | |
| Message: fmt.Sprintf(format, v...), | |
| Position: it.pos.withCol(p.lx.input), | |
| Line: it.pos.Len, | |
| LastKey: p.current(), | |
| }) | |
| } | |
| func (p *parser) panicf(format string, v ...any) { | |
| panic(ParseError{ | |
| Message: fmt.Sprintf(format, v...), | |
| Position: p.pos.withCol(p.lx.input), | |
| Line: p.pos.Line, | |
| LastKey: p.current(), | |
| }) | |
| } | |
| func (p *parser) next() item { | |
| it := p.lx.nextItem() | |
| //fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val) | |
| if it.typ == itemError { | |
| if it.err != nil { | |
| panic(ParseError{ | |
| Message: it.err.Error(), | |
| err: it.err, | |
| Position: it.pos.withCol(p.lx.input), | |
| Line: it.pos.Line, | |
| LastKey: p.current(), | |
| }) | |
| } | |
| p.panicItemf(it, "%s", it.val) | |
| } | |
| return it | |
| } | |
| func (p *parser) nextPos() item { | |
| it := p.next() | |
| p.pos = it.pos | |
| return it | |
| } | |
| func (p *parser) bug(format string, v ...any) { | |
| panic(fmt.Sprintf("BUG: "+format+"\n\n", v...)) | |
| } | |
| func (p *parser) expect(typ itemType) item { | |
| it := p.next() | |
| p.assertEqual(typ, it.typ) | |
| return it | |
| } | |
| func (p *parser) assertEqual(expected, got itemType) { | |
| if expected != got { | |
| p.bug("Expected '%s' but got '%s'.", expected, got) | |
| } | |
| } | |
| func (p *parser) topLevel(item item) { | |
| switch item.typ { | |
| case itemCommentStart: // # .. | |
| p.expect(itemText) | |
| case itemTableStart: // [ .. ] | |
| name := p.nextPos() | |
| var key Key | |
| for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() { | |
| key = append(key, p.keyString(name)) | |
| } | |
| p.assertEqual(itemTableEnd, name.typ) | |
| p.addContext(key, false) | |
| p.setType("", tomlHash, item.pos) | |
| p.ordered = append(p.ordered, key) | |
| case itemArrayTableStart: // [[ .. ]] | |
| name := p.nextPos() | |
| var key Key | |
| for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() { | |
| key = append(key, p.keyString(name)) | |
| } | |
| p.assertEqual(itemArrayTableEnd, name.typ) | |
| p.addContext(key, true) | |
| p.setType("", tomlArrayHash, item.pos) | |
| p.ordered = append(p.ordered, key) | |
| case itemKeyStart: // key = .. | |
| outerContext := p.context | |
| /// Read all the key parts (e.g. 'a' and 'b' in 'a.b') | |
| k := p.nextPos() | |
| var key Key | |
| for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { | |
| key = append(key, p.keyString(k)) | |
| } | |
| p.assertEqual(itemKeyEnd, k.typ) | |
| /// The current key is the last part. | |
| p.currentKey = key.last() | |
| /// All the other parts (if any) are the context; need to set each part | |
| /// as implicit. | |
| context := key.parent() | |
| for i := range context { | |
| p.addImplicitContext(append(p.context, context[i:i+1]...)) | |
| } | |
| p.ordered = append(p.ordered, p.context.add(p.currentKey)) | |
| /// Set value. | |
| vItem := p.next() | |
| val, typ := p.value(vItem, false) | |
| p.setValue(p.currentKey, val) | |
| p.setType(p.currentKey, typ, vItem.pos) | |
| /// Remove the context we added (preserving any context from [tbl] lines). | |
| p.context = outerContext | |
| p.currentKey = "" | |
| default: | |
| p.bug("Unexpected type at top level: %s", item.typ) | |
| } | |
| } | |
| // Gets a string for a key (or part of a key in a table name). | |
| func (p *parser) keyString(it item) string { | |
| switch it.typ { | |
| case itemText: | |
| return it.val | |
| case itemString, itemStringEsc, itemMultilineString, | |
| itemRawString, itemRawMultilineString: | |
| s, _ := p.value(it, false) | |
| return s.(string) | |
| default: | |
| p.bug("Unexpected key type: %s", it.typ) | |
| } | |
| panic("unreachable") | |
| } | |
| var datetimeRepl = strings.NewReplacer( | |
| "z", "Z", | |
| "t", "T", | |
| " ", "T") | |
| // value translates an expected value from the lexer into a Go value wrapped | |
| // as an empty interface. | |
| func (p *parser) value(it item, parentIsArray bool) (any, tomlType) { | |
| switch it.typ { | |
| case itemString: | |
| return it.val, p.typeOfPrimitive(it) | |
| case itemStringEsc: | |
| return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it) | |
| case itemMultilineString: | |
| return p.replaceEscapes(it, p.stripEscapedNewlines(stripFirstNewline(it.val))), p.typeOfPrimitive(it) | |
| case itemRawString: | |
| return it.val, p.typeOfPrimitive(it) | |
| case itemRawMultilineString: | |
| return stripFirstNewline(it.val), p.typeOfPrimitive(it) | |
| case itemInteger: | |
| return p.valueInteger(it) | |
| case itemFloat: | |
| return p.valueFloat(it) | |
| case itemBool: | |
| switch it.val { | |
| case "true": | |
| return true, p.typeOfPrimitive(it) | |
| case "false": | |
| return false, p.typeOfPrimitive(it) | |
| default: | |
| p.bug("Expected boolean value, but got '%s'.", it.val) | |
| } | |
| case itemDatetime: | |
| return p.valueDatetime(it) | |
| case itemArray: | |
| return p.valueArray(it) | |
| case itemInlineTableStart: | |
| return p.valueInlineTable(it, parentIsArray) | |
| default: | |
| p.bug("Unexpected value type: %s", it.typ) | |
| } | |
| panic("unreachable") | |
| } | |
| func (p *parser) valueInteger(it item) (any, tomlType) { | |
| if !numUnderscoresOK(it.val) { | |
| p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val) | |
| } | |
| if numHasLeadingZero(it.val) { | |
| p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val) | |
| } | |
| num, err := strconv.ParseInt(it.val, 0, 64) | |
| if err != nil { | |
| // Distinguish integer values. Normally, it'd be a bug if the lexer | |
| // provides an invalid integer, but it's possible that the number is | |
| // out of range of valid values (which the lexer cannot determine). | |
| // So mark the former as a bug but the latter as a legitimate user | |
| // error. | |
| if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { | |
| p.panicErr(it, errParseRange{i: it.val, size: "int64"}) | |
| } else { | |
| p.bug("Expected integer value, but got '%s'.", it.val) | |
| } | |
| } | |
| return num, p.typeOfPrimitive(it) | |
| } | |
| func (p *parser) valueFloat(it item) (any, tomlType) { | |
| parts := strings.FieldsFunc(it.val, func(r rune) bool { | |
| switch r { | |
| case '.', 'e', 'E': | |
| return true | |
| } | |
| return false | |
| }) | |
| for _, part := range parts { | |
| if !numUnderscoresOK(part) { | |
| p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val) | |
| } | |
| } | |
| if len(parts) > 0 && numHasLeadingZero(parts[0]) { | |
| p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val) | |
| } | |
| if !numPeriodsOK(it.val) { | |
| // As a special case, numbers like '123.' or '1.e2', | |
| // which are valid as far as Go/strconv are concerned, | |
| // must be rejected because TOML says that a fractional | |
| // part consists of '.' followed by 1+ digits. | |
| p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val) | |
| } | |
| val := strings.Replace(it.val, "_", "", -1) | |
| signbit := false | |
| if val == "+nan" || val == "-nan" { | |
| signbit = val == "-nan" | |
| val = "nan" | |
| } | |
| num, err := strconv.ParseFloat(val, 64) | |
| if err != nil { | |
| if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { | |
| p.panicErr(it, errParseRange{i: it.val, size: "float64"}) | |
| } else { | |
| p.panicItemf(it, "Invalid float value: %q", it.val) | |
| } | |
| } | |
| if signbit { | |
| num = math.Copysign(num, -1) | |
| } | |
| return num, p.typeOfPrimitive(it) | |
| } | |
| var dtTypes = []struct { | |
| fmt string | |
| zone *time.Location | |
| }{ | |
| {time.RFC3339Nano, time.Local}, | |
| {"2006-01-02T15:04:05.999999999", internal.LocalDatetime}, | |
| {"2006-01-02", internal.LocalDate}, | |
| {"15:04:05.999999999", internal.LocalTime}, | |
| {"2006-01-02T15:04Z07:00", time.Local}, | |
| {"2006-01-02T15:04", internal.LocalDatetime}, | |
| {"15:04", internal.LocalTime}, | |
| } | |
| func (p *parser) valueDatetime(it item) (any, tomlType) { | |
| it.val = datetimeRepl.Replace(it.val) | |
| var ( | |
| t time.Time | |
| ok bool | |
| err error | |
| ) | |
| for _, dt := range dtTypes { | |
| t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone) | |
| if err == nil { | |
| if missingLeadingZero(it.val, dt.fmt) { | |
| p.panicErr(it, errParseDate{it.val}) | |
| } | |
| ok = true | |
| break | |
| } | |
| } | |
| if !ok { | |
| p.panicErr(it, errParseDate{it.val}) | |
| } | |
| return t, p.typeOfPrimitive(it) | |
| } | |
| // Go's time.Parse() will accept numbers without a leading zero; there isn't any | |
| // way to require it. https://github.com/golang/go/issues/29911 | |
| // | |
| // Depend on the fact that the separators (- and :) should always be at the same | |
| // location. | |
| func missingLeadingZero(d, l string) bool { | |
| for i, c := range []byte(l) { | |
| if c == '.' || c == 'Z' { | |
| return false | |
| } | |
| if (c < '0' || c > '9') && d[i] != c { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| func (p *parser) valueArray(it item) (any, tomlType) { | |
| p.setType(p.currentKey, tomlArray, it.pos) | |
| var ( | |
| // Initialize to a non-nil slice to make it consistent with how S = [] | |
| // decodes into a non-nil slice inside something like struct { S | |
| // []string }. See #338 | |
| array = make([]any, 0, 2) | |
| ) | |
| for it = p.next(); it.typ != itemArrayEnd; it = p.next() { | |
| if it.typ == itemCommentStart { | |
| p.expect(itemText) | |
| continue | |
| } | |
| val, typ := p.value(it, true) | |
| array = append(array, val) | |
| // XXX: type isn't used here, we need it to record the accurate type | |
| // information. | |
| // | |
| // Not entirely sure how to best store this; could use "key[0]", | |
| // "key[1]" notation, or maybe store it on the Array type? | |
| _ = typ | |
| } | |
| return array, tomlArray | |
| } | |
| func (p *parser) valueInlineTable(it item, parentIsArray bool) (any, tomlType) { | |
| var ( | |
| topHash = make(map[string]any) | |
| outerContext = p.context | |
| outerKey = p.currentKey | |
| ) | |
| p.context = append(p.context, p.currentKey) | |
| prevContext := p.context | |
| p.currentKey = "" | |
| p.addImplicit(p.context) | |
| p.addContext(p.context, parentIsArray) | |
| /// Loop over all table key/value pairs. | |
| for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { | |
| if it.typ == itemCommentStart { | |
| p.expect(itemText) | |
| continue | |
| } | |
| /// Read all key parts. | |
| k := p.nextPos() | |
| var key Key | |
| for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { | |
| key = append(key, p.keyString(k)) | |
| } | |
| p.assertEqual(itemKeyEnd, k.typ) | |
| /// The current key is the last part. | |
| p.currentKey = key.last() | |
| /// All the other parts (if any) are the context; need to set each part | |
| /// as implicit. | |
| context := key.parent() | |
| for i := range context { | |
| p.addImplicitContext(append(p.context, context[i:i+1]...)) | |
| } | |
| p.ordered = append(p.ordered, p.context.add(p.currentKey)) | |
| /// Set the value. | |
| val, typ := p.value(p.next(), false) | |
| p.setValue(p.currentKey, val) | |
| p.setType(p.currentKey, typ, it.pos) | |
| hash := topHash | |
| for _, c := range context { | |
| h, ok := hash[c] | |
| if !ok { | |
| h = make(map[string]any) | |
| hash[c] = h | |
| } | |
| hash, ok = h.(map[string]any) | |
| if !ok { | |
| p.panicf("%q is not a table", p.context) | |
| } | |
| } | |
| hash[p.currentKey] = val | |
| /// Restore context. | |
| p.context = prevContext | |
| } | |
| p.context = outerContext | |
| p.currentKey = outerKey | |
| return topHash, tomlHash | |
| } | |
| // numHasLeadingZero checks if this number has leading zeroes, allowing for '0', | |
| // +/- signs, and base prefixes. | |
| func numHasLeadingZero(s string) bool { | |
| if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x | |
| return true | |
| } | |
| if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' { | |
| return true | |
| } | |
| return false | |
| } | |
| // numUnderscoresOK checks whether each underscore in s is surrounded by | |
| // characters that are not underscores. | |
| func numUnderscoresOK(s string) bool { | |
| switch s { | |
| case "nan", "+nan", "-nan", "inf", "-inf", "+inf": | |
| return true | |
| } | |
| accept := false | |
| for _, r := range s { | |
| if r == '_' { | |
| if !accept { | |
| return false | |
| } | |
| } | |
| // isHex is a superset of all the permissible characters surrounding an | |
| // underscore. | |
| accept = isHex(r) | |
| } | |
| return accept | |
| } | |
| // numPeriodsOK checks whether every period in s is followed by a digit. | |
| func numPeriodsOK(s string) bool { | |
| period := false | |
| for _, r := range s { | |
| if period && !isDigit(r) { | |
| return false | |
| } | |
| period = r == '.' | |
| } | |
| return !period | |
| } | |
| // Set the current context of the parser, where the context is either a hash or | |
| // an array of hashes, depending on the value of the `array` parameter. | |
| // | |
| // Establishing the context also makes sure that the key isn't a duplicate, and | |
| // will create implicit hashes automatically. | |
| func (p *parser) addContext(key Key, array bool) { | |
| if p.maxNest > 0 && len(key) >= p.maxNest { | |
| // Don't use panicf() helper because we don't want to set LastKey here – | |
| // it's going to be a very long string and not very useful. | |
| panic(ParseError{ | |
| Message: fmt.Sprintf("too many nested tables: can have up to %d nested tables", p.maxNest), | |
| Position: p.pos.withCol(p.lx.input), | |
| Line: p.pos.Line, | |
| }) | |
| } | |
| /// Always start at the top level and drill down for our context. | |
| hashContext := p.mapping | |
| keyContext := make(Key, 0, len(key)-1) | |
| /// We only need implicit hashes for the parents. | |
| for _, k := range key.parent() { | |
| _, ok := hashContext[k] | |
| keyContext = append(keyContext, k) | |
| // No key? Make an implicit hash and move on. | |
| if !ok { | |
| p.addImplicit(keyContext) | |
| hashContext[k] = make(map[string]any) | |
| } | |
| // If the hash context is actually an array of tables, then set | |
| // the hash context to the last element in that array. | |
| // | |
| // Otherwise, it better be a table, since this MUST be a key group (by | |
| // virtue of it not being the last element in a key). | |
| switch t := hashContext[k].(type) { | |
| case []map[string]any: | |
| hashContext = t[len(t)-1] | |
| case map[string]any: | |
| hashContext = t | |
| default: | |
| p.panicf("Key '%s' was already created as a hash.", keyContext) | |
| } | |
| } | |
| p.context = keyContext | |
| if array { | |
| // If this is the first element for this array, then allocate a new | |
| // list of tables for it. | |
| k := key.last() | |
| if _, ok := hashContext[k]; !ok { | |
| hashContext[k] = make([]map[string]any, 0, 4) | |
| } | |
| // Add a new table. But make sure the key hasn't already been used | |
| // for something else. | |
| if hash, ok := hashContext[k].([]map[string]any); ok { | |
| hashContext[k] = append(hash, make(map[string]any)) | |
| } else { | |
| p.panicf("Key '%s' was already created and cannot be used as an array.", key) | |
| } | |
| } else { | |
| p.setValue(key.last(), make(map[string]any)) | |
| } | |
| p.context = append(p.context, key.last()) | |
| } | |
| // setValue sets the given key to the given value in the current context. | |
| // It will make sure that the key hasn't already been defined, account for | |
| // implicit key groups. | |
| func (p *parser) setValue(key string, value any) { | |
| var ( | |
| tmpHash any | |
| ok bool | |
| hash = p.mapping | |
| keyContext = make(Key, 0, len(p.context)+1) | |
| ) | |
| for _, k := range p.context { | |
| keyContext = append(keyContext, k) | |
| if tmpHash, ok = hash[k]; !ok { | |
| p.bug("Context for key '%s' has not been established.", keyContext) | |
| } | |
| switch t := tmpHash.(type) { | |
| case []map[string]any: | |
| // The context is a table of hashes. Pick the most recent table | |
| // defined as the current hash. | |
| hash = t[len(t)-1] | |
| case map[string]any: | |
| hash = t | |
| default: | |
| p.panicf("Key '%s' has already been defined.", keyContext) | |
| } | |
| } | |
| keyContext = append(keyContext, key) | |
| if _, ok := hash[key]; ok { | |
| // Normally redefining keys isn't allowed, but the key could have been | |
| // defined implicitly and it's allowed to be redefined concretely. (See | |
| // the `valid/implicit-and-explicit-after.toml` in toml-test) | |
| // | |
| // But we have to make sure to stop marking it as an implicit. (So that | |
| // another redefinition provokes an error.) | |
| // | |
| // Note that since it has already been defined (as a hash), we don't | |
| // want to overwrite it. So our business is done. | |
| if p.isArray(keyContext) { | |
| if !p.isImplicit(keyContext) { | |
| if _, ok := hash[key]; ok { | |
| p.panicf("Key '%s' has already been defined.", keyContext) | |
| } | |
| } | |
| p.removeImplicit(keyContext) | |
| hash[key] = value | |
| return | |
| } | |
| if p.isImplicit(keyContext) { | |
| p.removeImplicit(keyContext) | |
| return | |
| } | |
| // Otherwise, we have a concrete key trying to override a previous key, | |
| // which is *always* wrong. | |
| p.panicf("Key '%s' has already been defined.", keyContext) | |
| } | |
| hash[key] = value | |
| } | |
| // setType sets the type of a particular value at a given key. It should be | |
| // called immediately AFTER setValue. | |
| // | |
| // Note that if `key` is empty, then the type given will be applied to the | |
| // current context (which is either a table or an array of tables). | |
| func (p *parser) setType(key string, typ tomlType, pos Position) { | |
| keyContext := make(Key, 0, len(p.context)+1) | |
| keyContext = append(keyContext, p.context...) | |
| if len(key) > 0 { // allow type setting for hashes | |
| keyContext = append(keyContext, key) | |
| } | |
| // Special case to make empty keys ("" = 1) work. | |
| // Without it it will set "" rather than `""`. | |
| // TODO: why is this needed? And why is this only needed here? | |
| if len(keyContext) == 0 { | |
| keyContext = Key{""} | |
| } | |
| p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos} | |
| } | |
| // Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and | |
| // "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly). | |
| func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} } | |
| func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) } | |
| func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok } | |
| func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray } | |
| func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) } | |
| // current returns the full key name of the current context. | |
| func (p *parser) current() string { | |
| if len(p.currentKey) == 0 { | |
| return p.context.String() | |
| } | |
| if len(p.context) == 0 { | |
| return p.currentKey | |
| } | |
| return fmt.Sprintf("%s.%s", p.context, p.currentKey) | |
| } | |
| func stripFirstNewline(s string) string { | |
| if len(s) > 0 && s[0] == '\n' { | |
| return s[1:] | |
| } | |
| if len(s) > 1 && s[0] == '\r' && s[1] == '\n' { | |
| return s[2:] | |
| } | |
| return s | |
| } | |
| // stripEscapedNewlines removes whitespace after line-ending backslashes in | |
| // multiline strings. | |
| // | |
| // A line-ending backslash is an unescaped \ followed only by whitespace until | |
| // the next newline. After a line-ending backslash, all whitespace is removed | |
| // until the next non-whitespace character. | |
| func (p *parser) stripEscapedNewlines(s string) string { | |
| var ( | |
| b strings.Builder | |
| i int | |
| ) | |
| b.Grow(len(s)) | |
| for { | |
| ix := strings.Index(s[i:], `\`) | |
| if ix < 0 { | |
| b.WriteString(s) | |
| return b.String() | |
| } | |
| i += ix | |
| if len(s) > i+1 && s[i+1] == '\\' { | |
| // Escaped backslash. | |
| i += 2 | |
| continue | |
| } | |
| // Scan until the next non-whitespace. | |
| j := i + 1 | |
| whitespaceLoop: | |
| for ; j < len(s); j++ { | |
| switch s[j] { | |
| case ' ', '\t', '\r', '\n': | |
| default: | |
| break whitespaceLoop | |
| } | |
| } | |
| if j == i+1 { | |
| // Not a whitespace escape. | |
| i++ | |
| continue | |
| } | |
| if !strings.Contains(s[i:j], "\n") { | |
| // This is not a line-ending backslash. (It's a bad escape sequence, | |
| // but we can let replaceEscapes catch it.) | |
| i++ | |
| continue | |
| } | |
| b.WriteString(s[:i]) | |
| s = s[j:] | |
| i = 0 | |
| } | |
| } | |
| func (p *parser) replaceEscapes(it item, str string) string { | |
| var ( | |
| b strings.Builder | |
| skip = 0 | |
| ) | |
| b.Grow(len(str)) | |
| for i, c := range str { | |
| if skip > 0 { | |
| skip-- | |
| continue | |
| } | |
| if c != '\\' { | |
| b.WriteRune(c) | |
| continue | |
| } | |
| if i >= len(str) { | |
| p.bug("Escape sequence at end of string.") | |
| return "" | |
| } | |
| switch str[i+1] { | |
| default: | |
| p.bug("Expected valid escape code after \\, but got %q.", str[i+1]) | |
| case ' ', '\t': | |
| p.panicItemf(it, "invalid escape: '\\%c'", str[i+1]) | |
| case 'b': | |
| b.WriteByte(0x08) | |
| skip = 1 | |
| case 't': | |
| b.WriteByte(0x09) | |
| skip = 1 | |
| case 'n': | |
| b.WriteByte(0x0a) | |
| skip = 1 | |
| case 'f': | |
| b.WriteByte(0x0c) | |
| skip = 1 | |
| case 'r': | |
| b.WriteByte(0x0d) | |
| skip = 1 | |
| case 'e': | |
| b.WriteByte(0x1b) | |
| skip = 1 | |
| case '"': | |
| b.WriteByte(0x22) | |
| skip = 1 | |
| case '\\': | |
| b.WriteByte(0x5c) | |
| skip = 1 | |
| // The lexer guarantees the correct number of characters are present; | |
| // don't need to check here. | |
| case 'x': | |
| escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4]) | |
| b.WriteRune(escaped) | |
| skip = 3 | |
| case 'u': | |
| escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6]) | |
| b.WriteRune(escaped) | |
| skip = 5 | |
| case 'U': | |
| escaped := p.asciiEscapeToUnicode(it, str[i+2:i+10]) | |
| b.WriteRune(escaped) | |
| skip = 9 | |
| } | |
| } | |
| return b.String() | |
| } | |
| func (p *parser) asciiEscapeToUnicode(it item, s string) rune { | |
| hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) | |
| if err != nil { | |
| p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err) | |
| } | |
| if !utf8.ValidRune(rune(hex)) { | |
| p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s) | |
| } | |
| return rune(hex) | |
| } |