2023-10-03 05:22:23 +00:00
|
|
|
//go:build !yq_nolua
|
|
|
|
|
2023-10-03 05:00:51 +00:00
|
|
|
package yqlib
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"math"
|
|
|
|
|
|
|
|
lua "github.com/yuin/gopher-lua"
|
|
|
|
yaml "gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
|
|
|
type luaDecoder struct {
|
|
|
|
reader io.Reader
|
|
|
|
finished bool
|
|
|
|
prefs LuaPreferences
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewLuaDecoder(prefs LuaPreferences) Decoder {
|
|
|
|
return &luaDecoder{
|
|
|
|
prefs: prefs,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dec *luaDecoder) Init(reader io.Reader) error {
|
|
|
|
dec.reader = reader
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dec *luaDecoder) convertToYamlNode(ls *lua.LState, lv lua.LValue) *yaml.Node {
|
|
|
|
switch lv.Type() {
|
|
|
|
case lua.LTNil:
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "!!null",
|
|
|
|
Value: "",
|
|
|
|
}
|
|
|
|
case lua.LTBool:
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "!!bool",
|
|
|
|
Value: lv.String(),
|
|
|
|
}
|
|
|
|
case lua.LTNumber:
|
|
|
|
n := float64(lua.LVAsNumber(lv))
|
|
|
|
// various special case floats
|
|
|
|
if math.IsNaN(n) {
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "!!float",
|
|
|
|
Value: ".nan",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if math.IsInf(n, 1) {
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "!!float",
|
|
|
|
Value: ".inf",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if math.IsInf(n, -1) {
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "!!float",
|
|
|
|
Value: "-.inf",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// does it look like an integer?
|
|
|
|
if n == float64(int(n)) {
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "!!int",
|
|
|
|
Value: lv.String(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "!!float",
|
|
|
|
Value: lv.String(),
|
|
|
|
}
|
|
|
|
case lua.LTString:
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "!!str",
|
|
|
|
Value: lv.String(),
|
|
|
|
}
|
|
|
|
case lua.LTFunction:
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
Tag: "tag:lua.org,2006,function",
|
|
|
|
Value: lv.String(),
|
|
|
|
}
|
|
|
|
case lua.LTTable:
|
|
|
|
// Simultaneously create a sequence and a map, pick which one to return
|
|
|
|
// based on whether all keys were consecutive integers
|
|
|
|
i := 1
|
|
|
|
yaml_sequence := &yaml.Node{
|
|
|
|
Kind: yaml.SequenceNode,
|
|
|
|
Tag: "!!seq",
|
|
|
|
}
|
|
|
|
yaml_map := &yaml.Node{
|
|
|
|
Kind: yaml.MappingNode,
|
|
|
|
Tag: "!!map",
|
|
|
|
}
|
|
|
|
t := lv.(*lua.LTable)
|
|
|
|
k, v := ls.Next(t, lua.LNil)
|
|
|
|
for k != lua.LNil {
|
|
|
|
if ki, ok := k.(lua.LNumber); i != 0 && ok && math.Mod(float64(ki), 1) == 0 && int(ki) == i {
|
|
|
|
i++
|
|
|
|
} else {
|
|
|
|
i = 0
|
|
|
|
}
|
|
|
|
yaml_map.Content = append(yaml_map.Content, dec.convertToYamlNode(ls, k))
|
|
|
|
yv := dec.convertToYamlNode(ls, v)
|
|
|
|
yaml_map.Content = append(yaml_map.Content, yv)
|
|
|
|
if i != 0 {
|
|
|
|
yaml_sequence.Content = append(yaml_sequence.Content, yv)
|
|
|
|
}
|
|
|
|
k, v = ls.Next(t, k)
|
|
|
|
}
|
|
|
|
if i != 0 {
|
|
|
|
return yaml_sequence
|
|
|
|
}
|
|
|
|
return yaml_map
|
|
|
|
default:
|
|
|
|
return &yaml.Node{
|
|
|
|
Kind: yaml.ScalarNode,
|
|
|
|
LineComment: fmt.Sprintf("Unhandled Lua type: %s", lv.Type().String()),
|
|
|
|
Tag: "!!null",
|
|
|
|
Value: lv.String(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dec *luaDecoder) decideTopLevelNode(ls *lua.LState) *yaml.Node {
|
|
|
|
if ls.GetTop() == 0 {
|
|
|
|
// no items were explicitly returned, encode the globals table instead
|
|
|
|
return dec.convertToYamlNode(ls, ls.Get(lua.GlobalsIndex))
|
|
|
|
}
|
|
|
|
return dec.convertToYamlNode(ls, ls.Get(1))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (dec *luaDecoder) Decode() (*CandidateNode, error) {
|
|
|
|
if dec.finished {
|
|
|
|
return nil, io.EOF
|
|
|
|
}
|
|
|
|
ls := lua.NewState(lua.Options{SkipOpenLibs: true})
|
|
|
|
defer ls.Close()
|
|
|
|
fn, err := ls.Load(dec.reader, "@input")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
ls.Push(fn)
|
|
|
|
err = ls.PCall(0, lua.MultRet, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
firstNode := dec.decideTopLevelNode(ls)
|
|
|
|
dec.finished = true
|
|
|
|
return &CandidateNode{
|
|
|
|
Node: &yaml.Node{
|
|
|
|
Kind: yaml.DocumentNode,
|
|
|
|
Content: []*yaml.Node{firstNode},
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|