blob: 46588fe203f4fd2c810960db922b92d23c5631f0 [file] [log] [blame]
/**
* 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
}