yaml/decode.go (214 lines of code) (raw):

/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package yaml import ( "errors" "fmt" "strconv" ) type decodeState int const ( CTXT_STATE_NONE decodeState = iota CTXT_STATE_SCALAR decodeState = iota CTXT_STATE_MAPPING decodeState = iota CTXT_STATE_SEQUENCE decodeState = iota CTXT_STATE_DONE decodeState = iota ) type decodeCtxt struct { state decodeState value interface{} } type YamlDispatchFn func(*yaml_parser_t, *yaml_event_t, *decodeCtxt) (decodeCtxt, error) var decodeFilename string var decodeDispatch map[yaml_event_type_t]YamlDispatchFn // Fills in the decodeDispatch table. This function is necessary because // statically initializing the table triggers a spurious "initialization loop" // error. func initDecodeDispatch() { if decodeDispatch != nil { return } decodeDispatch = map[yaml_event_type_t]YamlDispatchFn{ yaml_STREAM_START_EVENT: decodeNoOp, yaml_DOCUMENT_START_EVENT: decodeNoOp, yaml_DOCUMENT_END_EVENT: decodeNoOp, yaml_ALIAS_EVENT: decodeNoOp, yaml_STREAM_END_EVENT: decodeStreamEnd, yaml_SCALAR_EVENT: decodeScalar, yaml_SEQUENCE_START_EVENT: decodeSequenceStart, yaml_SEQUENCE_END_EVENT: decodeSequenceEnd, yaml_MAPPING_START_EVENT: decodeMappingStart, yaml_MAPPING_END_EVENT: decodeMappingEnd, } } func decodeError(parser *yaml_parser_t, format string, sprintfArgs ...interface{}) error { prefix := fmt.Sprintf("[%s:%d]: ", decodeFilename, parser.mark.line+1) msg := prefix + fmt.Sprintf(format, sprintfArgs...) return errors.New(msg) } // Appends the specified value to the end of a sequence-context's value slice. func sequenceAdd(ctxt *decodeCtxt, value interface{}) { if ctxt.value == nil { ctxt.value = []interface{}{} } ctxt.value = append(ctxt.value.([]interface{}), value) } // Inserts the specified value into a mapping-context's value map. func mappingAdd(ctxt *decodeCtxt, key string, value interface{}) { if ctxt.value == nil { ctxt.value = map[interface{}]interface{}{} } ctxt.value.(map[interface{}]interface{})[key] = value } func genValue(strVal string) interface{} { intVal, err := strconv.Atoi(strVal) if err == nil { return intVal } boolVal, err := strconv.ParseBool(strVal) if err == nil { return boolVal } return strVal } func stringValue(value interface{}) string { switch value.(type) { case int: return strconv.FormatInt(int64(value.(int)), 10) case bool: return strconv.FormatBool(value.(bool)) case string: return value.(string) default: panic("unexpected type") } } func decodeNoOp(parser *yaml_parser_t, event *yaml_event_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { return decodeCtxt{state: CTXT_STATE_NONE}, nil } func decodeStreamEnd(parser *yaml_parser_t, event *yaml_event_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { return decodeCtxt{state: CTXT_STATE_DONE}, nil } func decodeNextValue(parser *yaml_parser_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { for { ctxt, err := decodeEvent(parser, parentCtxt) if err != nil { return ctxt, err } if ctxt.state != CTXT_STATE_NONE { return ctxt, nil } } } func decodeSequenceStart(parser *yaml_parser_t, event *yaml_event_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { ctxt := decodeCtxt{state: CTXT_STATE_SEQUENCE} // For each element in the sequence, decode it and append it to the // seqeunce-context's value slice. for { subCtxt, err := decodeNextValue(parser, &ctxt) if err != nil { return ctxt, err } if subCtxt.state == CTXT_STATE_DONE { break } sequenceAdd(&ctxt, subCtxt.value) } return ctxt, nil } func decodeSequenceEnd(parser *yaml_parser_t, event *yaml_event_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { ctxt := decodeCtxt{state: CTXT_STATE_DONE} if parentCtxt.state != CTXT_STATE_SEQUENCE { return ctxt, decodeError(parser, "sequence end without start") } return ctxt, nil } func decodeMappingKey(parser *yaml_parser_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { ctxt, err := decodeNextValue(parser, parentCtxt) if err != nil { return ctxt, err } switch ctxt.state { case CTXT_STATE_DONE, CTXT_STATE_SCALAR: // Mapping complete or key decoded. return ctxt, nil default: return ctxt, decodeError(parser, "mapping lacks scalar key") } } func decodeMappingStart(parser *yaml_parser_t, event *yaml_event_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { ctxt := decodeCtxt{state: CTXT_STATE_MAPPING} for { subCtxt, err := decodeMappingKey(parser, &ctxt) if err != nil || subCtxt.state == CTXT_STATE_DONE { return ctxt, err } key := stringValue(subCtxt.value) subCtxt, err = decodeNextValue(parser, &ctxt) if err != nil { return ctxt, err } if subCtxt.state == CTXT_STATE_DONE { return ctxt, decodeError(parser, "mapping lacks value") } mappingAdd(&ctxt, key, subCtxt.value) } return ctxt, nil } func decodeMappingEnd(parser *yaml_parser_t, event *yaml_event_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { ctxt := decodeCtxt{state: CTXT_STATE_DONE} if parentCtxt.state != CTXT_STATE_MAPPING { return ctxt, decodeError(parser, "mapping end without start") } return ctxt, nil } func decodeScalar(parser *yaml_parser_t, event *yaml_event_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { ctxt := decodeCtxt{state: CTXT_STATE_SCALAR} strVal := string(event.value) ctxt.value = genValue(strVal) return ctxt, nil } func decodeEvent(parser *yaml_parser_t, parentCtxt *decodeCtxt) (decodeCtxt, error) { event := yaml_event_t{} defer yaml_event_delete(&event) ctxt := decodeCtxt{state: CTXT_STATE_NONE} rc := yaml_parser_parse(parser, &event) if !rc { return ctxt, decodeError(parser, "%s", parser.problem) } fn := decodeDispatch[event.typ] if fn == nil { return ctxt, decodeError(parser, "Invalid event type: %s (%d)", constNames[event.typ], event.typ) } ctxt, err := fn(parser, &event, parentCtxt) return ctxt, err } func DecodeStream(b []byte, values map[string]interface{}) error { parser := yaml_parser_t{} initDecodeDispatch() yaml_parser_initialize(&parser) yaml_parser_set_input_string(&parser, b) // Decode YAML events until we get a valid mapping. for { ctxt := decodeCtxt{state: CTXT_STATE_NONE} subCtxt, err := decodeEvent(&parser, &ctxt) if err != nil { return err } switch subCtxt.state { case CTXT_STATE_MAPPING: // Copy mapping to input variable. for k, v := range subCtxt.value.(map[interface{}]interface{}) { values[k.(string)] = v } case CTXT_STATE_DONE: return nil default: // Unneeded metadata; proceed to next event. break } } } func SetFilename(filename string) { decodeFilename = filename }