// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT // Command interpres-decode reads a TOML document from standard input and writes // the toml-test "tagged JSON" representation to standard output. // // It exits non-zero on a parse error, which is how the toml-test harness checks // that invalid documents are rejected. Run the official suite against it with: // // toml-test ./interpres-decode package main import ( "encoding/json" "fmt" "io" "math" "os" "strconv" "time" "sourcedock.dev/petrbalvin/interpres" ) func main() { os.Exit(Run(os.Stdin, os.Stdout, os.Stderr)) } // Run reads a TOML document from stdin, emits the toml-test tagged-JSON form // on stdout, and returns the process exit code (0 success, 1 parse error, // 2 I/O, encoding, or unsupported-value error). func Run(stdin io.Reader, stdout, stderr io.Writer) int { data, err := io.ReadAll(stdin) if err != nil { fmt.Fprintln(stderr, "read stdin:", err) return 2 } tree, err := interpres.Parse(data) if err != nil { fmt.Fprintln(stderr, err) return 1 } tagged, err := tag(tree) if err != nil { fmt.Fprintln(stderr, err) return 2 } enc := json.NewEncoder(stdout) enc.SetEscapeHTML(false) if err := enc.Encode(tagged); err != nil { fmt.Fprintln(stderr, "encode:", err) return 2 } return 0 } // tag converts an interpres value into its toml-test tagged-JSON form. Tables // become JSON objects and arrays become JSON arrays; scalars are wrapped in a // {"type", "value"} object. An error is returned for value types the encoder // has no tagged representation for. func tag(v any) (any, error) { switch x := v.(type) { case map[string]any: out := make(map[string]any, len(x)) for k, val := range x { tagged, err := tag(val) if err != nil { return nil, err } out[k] = tagged } return out, nil case []any: out := make([]any, len(x)) for i, e := range x { tagged, err := tag(e) if err != nil { return nil, err } out[i] = tagged } return out, nil case []map[string]any: out := make([]any, len(x)) for i, e := range x { tagged, err := tag(e) if err != nil { return nil, err } out[i] = tagged } return out, nil case string: return tagged("string", x), nil case bool: return tagged("bool", strconv.FormatBool(x)), nil case int64: return tagged("integer", strconv.FormatInt(x, 10)), nil case float64: return tagged("float", formatFloat(x)), nil case time.Time: return tagged("datetime", x.Format(time.RFC3339Nano)), nil case interpres.LocalDateTime: return tagged("datetime-local", x.Format("2006-01-02T15:04:05.999999999")), nil case interpres.LocalDate: return tagged("date-local", x.Format("2006-01-02")), nil case interpres.LocalTime: return tagged("time-local", x.Format("15:04:05.999999999")), nil default: return nil, fmt.Errorf("unsupported value type %T", v) } } func tagged(typ, value string) map[string]string { return map[string]string{"type": typ, "value": value} } func formatFloat(f float64) string { switch { case math.IsInf(f, 1): return "inf" case math.IsInf(f, -1): return "-inf" case math.IsNaN(f): return "nan" default: return strconv.FormatFloat(f, 'g', -1, 64) } }