whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -0,0 +1,537 @@
// Copyright 2020 The Bazel Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package json defines utilities for converting Starlark values
// to/from JSON strings. The most recent IETF standard for JSON is
// https://www.ietf.org/rfc/rfc7159.txt.
package json // import "go.starlark.net/lib/json"
import (
"bytes"
"encoding/json"
"fmt"
"math"
"math/big"
"reflect"
"sort"
"strconv"
"strings"
"unicode/utf8"
"unsafe"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
)
// Module json is a Starlark module of JSON-related functions.
//
// json = module(
// encode,
// decode,
// indent,
// )
//
// def encode(x):
//
// The encode function accepts one required positional argument,
// which it converts to JSON by cases:
// - A Starlark value that implements Go's standard json.Marshal
// interface defines its own JSON encoding.
// - None, True, and False are converted to null, true, and false, respectively.
// - Starlark int values, no matter how large, are encoded as decimal integers.
// Some decoders may not be able to decode very large integers.
// - Starlark float values are encoded using decimal point notation,
// even if the value is an integer.
// It is an error to encode a non-finite floating-point value.
// - Starlark strings are encoded as JSON strings, using UTF-16 escapes.
// - a Starlark IterableMapping (e.g. dict) is encoded as a JSON object.
// It is an error if any key is not a string.
// - any other Starlark Iterable (e.g. list, tuple) is encoded as a JSON array.
// - a Starlark HasAttrs (e.g. struct) is encoded as a JSON object.
//
// It an application-defined type matches more than one the cases describe above,
// (e.g. it implements both Iterable and HasFields), the first case takes precedence.
// Encoding any other value yields an error.
//
// def decode(x[, default]):
//
// The decode function has one required positional parameter, a JSON string.
// It returns the Starlark value that the string denotes.
// - Numbers are parsed as int or float, depending on whether they
// contain a decimal point.
// - JSON objects are parsed as new unfrozen Starlark dicts.
// - JSON arrays are parsed as new unfrozen Starlark lists.
//
// If x is not a valid JSON string, the behavior depends on the "default"
// parameter: if present, Decode returns its value; otherwise, Decode fails.
//
// def indent(str, *, prefix="", indent="\t"):
//
// The indent function pretty-prints a valid JSON encoding,
// and returns a string containing the indented form.
// It accepts one required positional parameter, the JSON string,
// and two optional keyword-only string parameters, prefix and indent,
// that specify a prefix of each new line, and the unit of indentation.
var Module = &starlarkstruct.Module{
Name: "json",
Members: starlark.StringDict{
"encode": starlark.NewBuiltin("json.encode", encode),
"decode": starlark.NewBuiltin("json.decode", decode),
"indent": starlark.NewBuiltin("json.indent", indent),
},
}
func encode(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x starlark.Value
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &x); err != nil {
return nil, err
}
buf := new(bytes.Buffer)
var quoteSpace [128]byte
quote := func(s string) {
// Non-trivial escaping is handled by Go's encoding/json.
if isPrintableASCII(s) {
buf.Write(strconv.AppendQuote(quoteSpace[:0], s))
} else {
// TODO(adonovan): opt: RFC 8259 mandates UTF-8 for JSON.
// Can we avoid this call?
data, _ := json.Marshal(s)
buf.Write(data)
}
}
path := make([]unsafe.Pointer, 0, 8)
var emit func(x starlark.Value) error
emit = func(x starlark.Value) error {
// It is only necessary to push/pop the item when it might contain
// itself (i.e. the last three switch cases), but omitting it in the other
// cases did not show significant improvement on the benchmarks.
if ptr := pointer(x); ptr != nil {
if pathContains(path, ptr) {
return fmt.Errorf("cycle in JSON structure")
}
path = append(path, ptr)
defer func() { path = path[0 : len(path)-1] }()
}
switch x := x.(type) {
case json.Marshaler:
// Application-defined starlark.Value types
// may define their own JSON encoding.
data, err := x.MarshalJSON()
if err != nil {
return err
}
buf.Write(data)
case starlark.NoneType:
buf.WriteString("null")
case starlark.Bool:
if x {
buf.WriteString("true")
} else {
buf.WriteString("false")
}
case starlark.Int:
fmt.Fprint(buf, x)
case starlark.Float:
if !isFinite(float64(x)) {
return fmt.Errorf("cannot encode non-finite float %v", x)
}
fmt.Fprintf(buf, "%g", x) // always contains a decimal point
case starlark.String:
quote(string(x))
case starlark.IterableMapping:
// e.g. dict (must have string keys)
buf.WriteByte('{')
items := x.Items()
for _, item := range items {
if _, ok := item[0].(starlark.String); !ok {
return fmt.Errorf("%s has %s key, want string", x.Type(), item[0].Type())
}
}
sort.Slice(items, func(i, j int) bool {
return items[i][0].(starlark.String) < items[j][0].(starlark.String)
})
for i, item := range items {
if i > 0 {
buf.WriteByte(',')
}
k, _ := starlark.AsString(item[0])
quote(k)
buf.WriteByte(':')
if err := emit(item[1]); err != nil {
return fmt.Errorf("in %s key %s: %v", x.Type(), item[0], err)
}
}
buf.WriteByte('}')
case starlark.Iterable:
// e.g. tuple, list
buf.WriteByte('[')
iter := x.Iterate()
defer iter.Done()
var elem starlark.Value
for i := 0; iter.Next(&elem); i++ {
if i > 0 {
buf.WriteByte(',')
}
if err := emit(elem); err != nil {
return fmt.Errorf("at %s index %d: %v", x.Type(), i, err)
}
}
buf.WriteByte(']')
case starlark.HasAttrs:
// e.g. struct
buf.WriteByte('{')
var names []string
names = append(names, x.AttrNames()...)
sort.Strings(names)
for i, name := range names {
v, err := x.Attr(name)
if err != nil {
return fmt.Errorf("cannot access attribute %s.%s: %w", x.Type(), name, err)
}
if v == nil {
// x.AttrNames() returned name, but x.Attr(name) returned nil, stating
// that the field doesn't exist.
return fmt.Errorf("missing attribute %s.%s (despite %q appearing in dir()", x.Type(), name, name)
}
if i > 0 {
buf.WriteByte(',')
}
quote(name)
buf.WriteByte(':')
if err := emit(v); err != nil {
return fmt.Errorf("in field .%s: %v", name, err)
}
}
buf.WriteByte('}')
default:
return fmt.Errorf("cannot encode %s as JSON", x.Type())
}
return nil
}
if err := emit(x); err != nil {
return nil, fmt.Errorf("%s: %v", b.Name(), err)
}
return starlark.String(buf.String()), nil
}
func pointer(i interface{}) unsafe.Pointer {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Ptr, reflect.Chan, reflect.Map, reflect.UnsafePointer, reflect.Slice:
// TODO(adonovan): use v.Pointer() when we drop go1.17.
return unsafe.Pointer(v.Pointer())
default:
return nil
}
}
func pathContains(path []unsafe.Pointer, item unsafe.Pointer) bool {
for _, p := range path {
if p == item {
return true
}
}
return false
}
// isPrintableASCII reports whether s contains only printable ASCII.
func isPrintableASCII(s string) bool {
for i := 0; i < len(s); i++ {
b := s[i]
if b < 0x20 || b >= 0x80 {
return false
}
}
return true
}
// isFinite reports whether f represents a finite rational value.
// It is equivalent to !math.IsNan(f) && !math.IsInf(f, 0).
func isFinite(f float64) bool {
return math.Abs(f) <= math.MaxFloat64
}
func indent(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
prefix, indent := "", "\t" // keyword-only
if err := starlark.UnpackArgs(b.Name(), nil, kwargs,
"prefix?", &prefix,
"indent?", &indent,
); err != nil {
return nil, err
}
var str string // positional-only
if err := starlark.UnpackPositionalArgs(b.Name(), args, nil, 1, &str); err != nil {
return nil, err
}
buf := new(bytes.Buffer)
if err := json.Indent(buf, []byte(str), prefix, indent); err != nil {
return nil, fmt.Errorf("%s: %v", b.Name(), err)
}
return starlark.String(buf.String()), nil
}
func decode(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (v starlark.Value, err error) {
var s string
var d starlark.Value
if err := starlark.UnpackArgs(b.Name(), args, kwargs, "x", &s, "default?", &d); err != nil {
return nil, err
}
if len(args) < 1 {
// "x" parameter is positional only; UnpackArgs does not allow us to
// directly express "def decode(x, *, default)"
return nil, fmt.Errorf("%s: unexpected keyword argument x", b.Name())
}
// The decoder necessarily makes certain representation choices
// such as list vs tuple, struct vs dict, int vs float.
// In principle, we could parameterize it to allow the caller to
// control the returned types, but there's no compelling need yet.
// Use panic/recover with a distinguished type (failure) for error handling.
// If "default" is set, we only want to return it when encountering invalid
// json - not for any other possible causes of panic.
// In particular, if we ever extend the json.decode API to take a callback,
// a distinguished, private failure type prevents the possibility of
// json.decode with "default" becoming abused as a try-catch mechanism.
type failure string
fail := func(format string, args ...interface{}) {
panic(failure(fmt.Sprintf(format, args...)))
}
i := 0
// skipSpace consumes leading spaces, and reports whether there is more input.
skipSpace := func() bool {
for ; i < len(s); i++ {
b := s[i]
if b != ' ' && b != '\t' && b != '\n' && b != '\r' {
return true
}
}
return false
}
// next consumes leading spaces and returns the first non-space.
// It panics if at EOF.
next := func() byte {
if skipSpace() {
return s[i]
}
fail("unexpected end of file")
panic("unreachable")
}
// parse returns the next JSON value from the input.
// It consumes leading but not trailing whitespace.
// It panics on error.
var parse func() starlark.Value
parse = func() starlark.Value {
b := next()
switch b {
case '"':
// string
// Find end of quotation.
// Also, record whether trivial unquoting is safe.
// Non-trivial unquoting is handled by Go's encoding/json.
safe := true
closed := false
j := i + 1
for ; j < len(s); j++ {
b := s[j]
if b == '\\' {
safe = false
j++ // skip x in \x
} else if b == '"' {
closed = true
j++ // skip '"'
break
} else if b >= utf8.RuneSelf {
safe = false
}
}
if !closed {
fail("unclosed string literal")
}
r := s[i:j]
i = j
// unquote
if safe {
r = r[1 : len(r)-1]
} else if err := json.Unmarshal([]byte(r), &r); err != nil {
fail("%s", err)
}
return starlark.String(r)
case 'n':
if strings.HasPrefix(s[i:], "null") {
i += len("null")
return starlark.None
}
case 't':
if strings.HasPrefix(s[i:], "true") {
i += len("true")
return starlark.True
}
case 'f':
if strings.HasPrefix(s[i:], "false") {
i += len("false")
return starlark.False
}
case '[':
// array
var elems []starlark.Value
i++ // '['
b = next()
if b != ']' {
for {
elem := parse()
elems = append(elems, elem)
b = next()
if b != ',' {
if b != ']' {
fail("got %q, want ',' or ']'", b)
}
break
}
i++ // ','
}
}
i++ // ']'
return starlark.NewList(elems)
case '{':
// object
dict := new(starlark.Dict)
i++ // '{'
b = next()
if b != '}' {
for {
key := parse()
if _, ok := key.(starlark.String); !ok {
fail("got %s for object key, want string", key.Type())
}
b = next()
if b != ':' {
fail("after object key, got %q, want ':' ", b)
}
i++ // ':'
value := parse()
dict.SetKey(key, value) // can't fail
b = next()
if b != ',' {
if b != '}' {
fail("in object, got %q, want ',' or '}'", b)
}
break
}
i++ // ','
}
}
i++ // '}'
return dict
default:
// number?
if isdigit(b) || b == '-' {
// scan literal. Allow [0-9+-eE.] for now.
float := false
var j int
for j = i + 1; j < len(s); j++ {
b = s[j]
if isdigit(b) {
// ok
} else if b == '.' ||
b == 'e' ||
b == 'E' ||
b == '+' ||
b == '-' {
float = true
} else {
break
}
}
num := s[i:j]
i = j
// Unlike most C-like languages,
// JSON disallows a leading zero before a digit.
digits := num
if num[0] == '-' {
digits = num[1:]
}
if digits == "" || digits[0] == '0' && len(digits) > 1 && isdigit(digits[1]) {
fail("invalid number: %s", num)
}
// parse literal
if float {
x, err := strconv.ParseFloat(num, 64)
if err != nil {
fail("invalid number: %s", num)
}
return starlark.Float(x)
} else {
x, ok := new(big.Int).SetString(num, 10)
if !ok {
fail("invalid number: %s", num)
}
return starlark.MakeBigInt(x)
}
}
}
fail("unexpected character %q", b)
panic("unreachable")
}
defer func() {
x := recover()
switch x := x.(type) {
case failure:
if d != nil {
v = d
} else {
err = fmt.Errorf("json.decode: at offset %d, %s", i, x)
}
case nil:
// nop
default:
panic(x) // unexpected panic
}
}()
v = parse()
if skipSpace() {
fail("unexpected character %q after value", s[i])
}
return v, nil
}
func isdigit(b byte) bool {
return b >= '0' && b <= '9'
}
@@ -0,0 +1,205 @@
// Copyright 2021 The Bazel Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package math provides basic constants and mathematical functions.
package math // import "go.starlark.net/lib/math"
import (
"errors"
"fmt"
"math"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
)
// Module math is a Starlark module of math-related functions and constants.
// The module defines the following functions:
//
// ceil(x) - Returns the ceiling of x, the smallest integer greater than or equal to x.
// copysign(x, y) - Returns a value with the magnitude of x and the sign of y.
// fabs(x) - Returns the absolute value of x as float.
// floor(x) - Returns the floor of x, the largest integer less than or equal to x.
// mod(x, y) - Returns the floating-point remainder of x/y. The magnitude of the result is less than y and its sign agrees with that of x.
// pow(x, y) - Returns x**y, the base-x exponential of y.
// remainder(x, y) - Returns the IEEE 754 floating-point remainder of x/y.
// round(x) - Returns the nearest integer, rounding half away from zero.
//
// exp(x) - Returns e raised to the power x, where e = 2.718281… is the base of natural logarithms.
// sqrt(x) - Returns the square root of x.
//
// acos(x) - Returns the arc cosine of x, in radians.
// asin(x) - Returns the arc sine of x, in radians.
// atan(x) - Returns the arc tangent of x, in radians.
// atan2(y, x) - Returns atan(y / x), in radians.
// The result is between -pi and pi.
// The vector in the plane from the origin to point (x, y) makes this angle with the positive X axis.
// The point of atan2() is that the signs of both inputs are known to it, so it can compute the correct
// quadrant for the angle.
// For example, atan(1) and atan2(1, 1) are both pi/4, but atan2(-1, -1) is -3*pi/4.
// cos(x) - Returns the cosine of x, in radians.
// hypot(x, y) - Returns the Euclidean norm, sqrt(x*x + y*y). This is the length of the vector from the origin to point (x, y).
// sin(x) - Returns the sine of x, in radians.
// tan(x) - Returns the tangent of x, in radians.
//
// degrees(x) - Converts angle x from radians to degrees.
// radians(x) - Converts angle x from degrees to radians.
//
// acosh(x) - Returns the inverse hyperbolic cosine of x.
// asinh(x) - Returns the inverse hyperbolic sine of x.
// atanh(x) - Returns the inverse hyperbolic tangent of x.
// cosh(x) - Returns the hyperbolic cosine of x.
// sinh(x) - Returns the hyperbolic sine of x.
// tanh(x) - Returns the hyperbolic tangent of x.
//
// log(x, base) - Returns the logarithm of x in the given base, or natural logarithm by default.
//
// gamma(x) - Returns the Gamma function of x.
//
// All functions accept both int and float values as arguments.
//
// The module also defines approximations of the following constants:
//
// e - The base of natural logarithms, approximately 2.71828.
// pi - The ratio of a circle's circumference to its diameter, approximately 3.14159.
//
var Module = &starlarkstruct.Module{
Name: "math",
Members: starlark.StringDict{
"ceil": starlark.NewBuiltin("ceil", ceil),
"copysign": newBinaryBuiltin("copysign", math.Copysign),
"fabs": newUnaryBuiltin("fabs", math.Abs),
"floor": starlark.NewBuiltin("floor", floor),
"mod": newBinaryBuiltin("mod", math.Mod),
"pow": newBinaryBuiltin("pow", math.Pow),
"remainder": newBinaryBuiltin("remainder", math.Remainder),
"round": newUnaryBuiltin("round", math.Round),
"exp": newUnaryBuiltin("exp", math.Exp),
"sqrt": newUnaryBuiltin("sqrt", math.Sqrt),
"acos": newUnaryBuiltin("acos", math.Acos),
"asin": newUnaryBuiltin("asin", math.Asin),
"atan": newUnaryBuiltin("atan", math.Atan),
"atan2": newBinaryBuiltin("atan2", math.Atan2),
"cos": newUnaryBuiltin("cos", math.Cos),
"hypot": newBinaryBuiltin("hypot", math.Hypot),
"sin": newUnaryBuiltin("sin", math.Sin),
"tan": newUnaryBuiltin("tan", math.Tan),
"degrees": newUnaryBuiltin("degrees", degrees),
"radians": newUnaryBuiltin("radians", radians),
"acosh": newUnaryBuiltin("acosh", math.Acosh),
"asinh": newUnaryBuiltin("asinh", math.Asinh),
"atanh": newUnaryBuiltin("atanh", math.Atanh),
"cosh": newUnaryBuiltin("cosh", math.Cosh),
"sinh": newUnaryBuiltin("sinh", math.Sinh),
"tanh": newUnaryBuiltin("tanh", math.Tanh),
"log": starlark.NewBuiltin("log", log),
"gamma": newUnaryBuiltin("gamma", math.Gamma),
"e": starlark.Float(math.E),
"pi": starlark.Float(math.Pi),
},
}
// floatOrInt is an Unpacker that converts a Starlark int or float to Go's float64.
type floatOrInt float64
func (p *floatOrInt) Unpack(v starlark.Value) error {
switch v := v.(type) {
case starlark.Int:
*p = floatOrInt(v.Float())
return nil
case starlark.Float:
*p = floatOrInt(v)
return nil
}
return fmt.Errorf("got %s, want float or int", v.Type())
}
// newUnaryBuiltin wraps a unary floating-point Go function
// as a Starlark built-in that accepts int or float arguments.
func newUnaryBuiltin(name string, fn func(float64) float64) *starlark.Builtin {
return starlark.NewBuiltin(name, func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x floatOrInt
if err := starlark.UnpackPositionalArgs(name, args, kwargs, 1, &x); err != nil {
return nil, err
}
return starlark.Float(fn(float64(x))), nil
})
}
// newBinaryBuiltin wraps a binary floating-point Go function
// as a Starlark built-in that accepts int or float arguments.
func newBinaryBuiltin(name string, fn func(float64, float64) float64) *starlark.Builtin {
return starlark.NewBuiltin(name, func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x, y floatOrInt
if err := starlark.UnpackPositionalArgs(name, args, kwargs, 2, &x, &y); err != nil {
return nil, err
}
return starlark.Float(fn(float64(x), float64(y))), nil
})
}
// log wraps the Log function
// as a Starlark built-in that accepts int or float arguments.
func log(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var (
x floatOrInt
base floatOrInt = math.E
)
if err := starlark.UnpackPositionalArgs("log", args, kwargs, 1, &x, &base); err != nil {
return nil, err
}
if base == 1 {
return nil, errors.New("division by zero")
}
return starlark.Float(math.Log(float64(x)) / math.Log(float64(base))), nil
}
func ceil(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x starlark.Value
if err := starlark.UnpackPositionalArgs("ceil", args, kwargs, 1, &x); err != nil {
return nil, err
}
switch t := x.(type) {
case starlark.Int:
return t, nil
case starlark.Float:
return starlark.NumberToInt(starlark.Float(math.Ceil(float64(t))))
}
return nil, fmt.Errorf("got %s, want float or int", x.Type())
}
func floor(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x starlark.Value
if err := starlark.UnpackPositionalArgs("floor", args, kwargs, 1, &x); err != nil {
return nil, err
}
switch t := x.(type) {
case starlark.Int:
return t, nil
case starlark.Float:
return starlark.NumberToInt(starlark.Float(math.Floor(float64(t))))
}
return nil, fmt.Errorf("got %s, want float or int", x.Type())
}
func degrees(x float64) float64 {
return 360 * x / (2 * math.Pi)
}
func radians(x float64) float64 {
return 2 * math.Pi * x / 360
}
@@ -0,0 +1,143 @@
// The star2proto command executes a Starlark file and prints a protocol
// message, which it expects to find in a module-level variable named 'result'.
//
// THIS COMMAND IS EXPERIMENTAL AND ITS INTERFACE MAY CHANGE.
package main
// TODO(adonovan): add features to make this a useful tool for querying,
// converting, and building messages in proto, JSON, and YAML.
// - define operations for reading and writing files.
// - support (e.g.) querying a proto file given a '-e expr' flag.
// This will need a convenient way to put the relevant descriptors in scope.
import (
"flag"
"fmt"
"log"
"os"
"strings"
"go.starlark.net/lib/json"
starlarkproto "go.starlark.net/lib/proto"
"go.starlark.net/resolve"
"go.starlark.net/starlark"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
)
// flags
var (
outputFlag = flag.String("output", "text", "output format (text, wire, json)")
varFlag = flag.String("var", "result", "the variable to output")
descriptors = flag.String("descriptors", "", "comma-separated list of names of files containing proto.FileDescriptorProto messages")
)
// Starlark dialect flags
func init() {
flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type")
// obsolete, no effect:
flag.BoolVar(&resolve.AllowFloat, "fp", true, "allow floating-point numbers")
flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions")
flag.BoolVar(&resolve.AllowNestedDef, "nesteddef", resolve.AllowNestedDef, "allow nested def statements")
}
func main() {
log.SetPrefix("star2proto: ")
log.SetFlags(0)
flag.Parse()
if len(flag.Args()) != 1 {
fatalf("requires a single Starlark file name")
}
filename := flag.Args()[0]
// By default, use the linked-in descriptors
// (very few in star2proto, e.g. descriptorpb itself).
pool := protoregistry.GlobalFiles
// Load a user-provided FileDescriptorSet produced by a command such as:
// $ protoc --descriptor_set_out=foo.fds foo.proto
if *descriptors != "" {
var fdset descriptorpb.FileDescriptorSet
for i, filename := range strings.Split(*descriptors, ",") {
data, err := os.ReadFile(filename)
if err != nil {
log.Fatalf("--descriptors[%d]: %s", i, err)
}
// Accumulate into the repeated field of FileDescriptors.
if err := (proto.UnmarshalOptions{Merge: true}).Unmarshal(data, &fdset); err != nil {
log.Fatalf("%s does not contain a proto2.FileDescriptorSet: %v", filename, err)
}
}
files, err := protodesc.NewFiles(&fdset)
if err != nil {
log.Fatalf("protodesc.NewFiles: could not build FileDescriptor index: %v", err)
}
pool = files
}
// Execute the Starlark file.
thread := &starlark.Thread{
Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) },
}
starlarkproto.SetPool(thread, pool)
predeclared := starlark.StringDict{
"proto": starlarkproto.Module,
"json": json.Module,
}
globals, err := starlark.ExecFile(thread, filename, nil, predeclared)
if err != nil {
if evalErr, ok := err.(*starlark.EvalError); ok {
fatalf("%s", evalErr.Backtrace())
} else {
fatalf("%s", err)
}
}
// Print the output variable as a message.
// TODO(adonovan): this is clumsy.
// Let the user call print(), or provide an expression on the command line.
result, ok := globals[*varFlag]
if !ok {
fatalf("%s must define a module-level variable named %q", filename, *varFlag)
}
msgwrap, ok := result.(*starlarkproto.Message)
if !ok {
fatalf("got %s, want proto.Message, for %q", result.Type(), *varFlag)
}
msg := msgwrap.Message()
// -output
var marshal func(protoreflect.ProtoMessage) ([]byte, error)
switch *outputFlag {
case "wire":
marshal = proto.Marshal
case "text":
marshal = prototext.MarshalOptions{Multiline: true, Indent: "\t"}.Marshal
case "json":
marshal = protojson.MarshalOptions{Multiline: true, Indent: "\t"}.Marshal
default:
fatalf("unsupported -output format: %s", *outputFlag)
}
data, err := marshal(msg)
if err != nil {
fatalf("%s", err)
}
os.Stdout.Write(data)
}
func fatalf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "star2proto: ")
fmt.Fprintf(os.Stderr, format, args...)
fmt.Fprintln(os.Stderr)
os.Exit(1)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,516 @@
// Copyright 2021 The Bazel Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package time provides time-related constants and functions.
package time // import "go.starlark.net/lib/time"
import (
"errors"
"fmt"
"sort"
"time"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"go.starlark.net/syntax"
)
// Module time is a Starlark module of time-related functions and constants.
// The module defines the following functions:
//
// from_timestamp(sec, nsec) - Converts the given Unix time corresponding to the number of seconds
// and (optionally) nanoseconds since January 1, 1970 UTC into an object
// of type Time. For more details, refer to https://pkg.go.dev/time#Unix.
//
// is_valid_timezone(loc) - Reports whether loc is a valid time zone name.
//
// now() - Returns the current local time. Applications may replace this function by a deterministic one.
//
// parse_duration(d) - Parses the given duration string. For more details, refer to
// https://pkg.go.dev/time#ParseDuration.
//
// parse_time(x, format, location) - Parses the given time string using a specific time format and location.
// The expected arguments are a time string (mandatory), a time format
// (optional, set to RFC3339 by default, e.g. "2021-03-22T23:20:50.52Z")
// and a name of location (optional, set to UTC by default). For more details,
// refer to https://pkg.go.dev/time#Parse and https://pkg.go.dev/time#ParseInLocation.
//
// time(year, month, day, hour, minute, second, nanosecond, location) - Returns the Time corresponding to
// yyyy-mm-dd hh:mm:ss + nsec nanoseconds
// in the appropriate zone for that time
// in the given location. All the parameters
// are optional.
// The module also defines the following constants:
//
// nanosecond - A duration representing one nanosecond.
// microsecond - A duration representing one microsecond.
// millisecond - A duration representing one millisecond.
// second - A duration representing one second.
// minute - A duration representing one minute.
// hour - A duration representing one hour.
//
var Module = &starlarkstruct.Module{
Name: "time",
Members: starlark.StringDict{
"from_timestamp": starlark.NewBuiltin("from_timestamp", fromTimestamp),
"is_valid_timezone": starlark.NewBuiltin("is_valid_timezone", isValidTimezone),
"now": starlark.NewBuiltin("now", now),
"parse_duration": starlark.NewBuiltin("parse_duration", parseDuration),
"parse_time": starlark.NewBuiltin("parse_time", parseTime),
"time": starlark.NewBuiltin("time", newTime),
"nanosecond": Duration(time.Nanosecond),
"microsecond": Duration(time.Microsecond),
"millisecond": Duration(time.Millisecond),
"second": Duration(time.Second),
"minute": Duration(time.Minute),
"hour": Duration(time.Hour),
},
}
// NowFunc is a function that reports the current time. Intentionally exported
// so that it can be overridden, for example by applications that require their
// Starlark scripts to be fully deterministic.
//
// Deprecated: avoid updating this global variable
// and instead use SetNow on each thread to set its clock function.
var NowFunc = time.Now
const contextKey = "time.now"
// SetNow sets the thread's optional clock function.
// If non-nil, it will be used in preference to NowFunc when the
// thread requests the current time by executing a call to time.now.
func SetNow(thread *starlark.Thread, nowFunc func() (time.Time, error)) {
thread.SetLocal(contextKey, nowFunc)
}
// Now returns the clock function previously associated with this thread.
func Now(thread *starlark.Thread) func() (time.Time, error) {
nowFunc, _ := thread.Local(contextKey).(func() (time.Time, error))
return nowFunc
}
func parseDuration(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var d Duration
err := starlark.UnpackPositionalArgs("parse_duration", args, kwargs, 1, &d)
return d, err
}
func isValidTimezone(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var s string
if err := starlark.UnpackPositionalArgs("is_valid_timezone", args, kwargs, 1, &s); err != nil {
return nil, err
}
_, err := time.LoadLocation(s)
return starlark.Bool(err == nil), nil
}
func parseTime(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var (
x string
location = "UTC"
format = time.RFC3339
)
if err := starlark.UnpackArgs("parse_time", args, kwargs, "x", &x, "format?", &format, "location?", &location); err != nil {
return nil, err
}
if location == "UTC" {
t, err := time.Parse(format, x)
if err != nil {
return nil, err
}
return Time(t), nil
}
loc, err := time.LoadLocation(location)
if err != nil {
return nil, err
}
t, err := time.ParseInLocation(format, x, loc)
if err != nil {
return nil, err
}
return Time(t), nil
}
func fromTimestamp(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var (
sec int64
nsec int64 = 0
)
if err := starlark.UnpackPositionalArgs("from_timestamp", args, kwargs, 1, &sec, &nsec); err != nil {
return nil, err
}
return Time(time.Unix(sec, nsec)), nil
}
func now(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
nowErrFunc := Now(thread)
if nowErrFunc != nil {
t, err := nowErrFunc()
if err != nil {
return nil, err
}
return Time(t), nil
}
nowFunc := NowFunc
if nowFunc == nil {
return nil, errors.New("time.now() is not available")
}
return Time(nowFunc()), nil
}
// Duration is a Starlark representation of a duration.
type Duration time.Duration
// Assert at compile time that Duration implements Unpacker.
var _ starlark.Unpacker = (*Duration)(nil)
// Unpack is a custom argument unpacker
func (d *Duration) Unpack(v starlark.Value) error {
switch x := v.(type) {
case Duration:
*d = x
return nil
case starlark.String:
dur, err := time.ParseDuration(string(x))
if err != nil {
return err
}
*d = Duration(dur)
return nil
}
return fmt.Errorf("got %s, want a duration, string, or int", v.Type())
}
// String implements the Stringer interface.
func (d Duration) String() string { return time.Duration(d).String() }
// Type returns a short string describing the value's type.
func (d Duration) Type() string { return "time.duration" }
// Freeze renders Duration immutable. required by starlark.Value interface
// because duration is already immutable this is a no-op.
func (d Duration) Freeze() {}
// Hash returns a function of x such that Equals(x, y) => Hash(x) == Hash(y)
// required by starlark.Value interface.
func (d Duration) Hash() (uint32, error) {
return uint32(d) ^ uint32(int64(d)>>32), nil
}
// Truth reports whether the duration is non-zero.
func (d Duration) Truth() starlark.Bool { return d != 0 }
// Attr gets a value for a string attribute, implementing dot expression support
// in starklark. required by starlark.HasAttrs interface.
func (d Duration) Attr(name string) (starlark.Value, error) {
switch name {
case "hours":
return starlark.Float(time.Duration(d).Hours()), nil
case "minutes":
return starlark.Float(time.Duration(d).Minutes()), nil
case "seconds":
return starlark.Float(time.Duration(d).Seconds()), nil
case "milliseconds":
return starlark.MakeInt64(time.Duration(d).Milliseconds()), nil
case "microseconds":
return starlark.MakeInt64(time.Duration(d).Microseconds()), nil
case "nanoseconds":
return starlark.MakeInt64(time.Duration(d).Nanoseconds()), nil
}
return nil, fmt.Errorf("unrecognized %s attribute %q", d.Type(), name)
}
// AttrNames lists available dot expression strings. required by
// starlark.HasAttrs interface.
func (d Duration) AttrNames() []string {
return []string{
"hours",
"minutes",
"seconds",
"milliseconds",
"microseconds",
"nanoseconds",
}
}
// Cmp implements comparison of two Duration values. required by
// starlark.TotallyOrdered interface.
func (d Duration) Cmp(v starlark.Value, depth int) (int, error) {
if x, y := d, v.(Duration); x < y {
return -1, nil
} else if x > y {
return 1, nil
}
return 0, nil
}
// Binary implements binary operators, which satisfies the starlark.HasBinary
// interface. operators:
// duration + duration = duration
// duration + time = time
// duration - duration = duration
// duration / duration = float
// duration / int = duration
// duration / float = duration
// duration // duration = int
// duration * int = duration
func (d Duration) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
x := time.Duration(d)
switch op {
case syntax.PLUS:
switch y := y.(type) {
case Duration:
return Duration(x + time.Duration(y)), nil
case Time:
return Time(time.Time(y).Add(x)), nil
}
case syntax.MINUS:
switch y := y.(type) {
case Duration:
return Duration(x - time.Duration(y)), nil
}
case syntax.SLASH:
switch y := y.(type) {
case Duration:
if y == 0 {
return nil, fmt.Errorf("%s division by zero", d.Type())
}
return starlark.Float(x.Nanoseconds()) / starlark.Float(time.Duration(y).Nanoseconds()), nil
case starlark.Int:
if side == starlark.Right {
return nil, fmt.Errorf("unsupported operation")
}
i, ok := y.Int64()
if !ok {
return nil, fmt.Errorf("int value out of range (want signed 64-bit value)")
}
if i == 0 {
return nil, fmt.Errorf("%s division by zero", d.Type())
}
return d / Duration(i), nil
case starlark.Float:
f := float64(y)
if f == 0 {
return nil, fmt.Errorf("%s division by zero", d.Type())
}
return Duration(float64(x.Nanoseconds()) / f), nil
}
case syntax.SLASHSLASH:
switch y := y.(type) {
case Duration:
if y == 0 {
return nil, fmt.Errorf("%s division by zero", d.Type())
}
return starlark.MakeInt64(x.Nanoseconds() / time.Duration(y).Nanoseconds()), nil
}
case syntax.STAR:
switch y := y.(type) {
case starlark.Int:
i, ok := y.Int64()
if !ok {
return nil, fmt.Errorf("int value out of range (want signed 64-bit value)")
}
return d * Duration(i), nil
}
}
return nil, nil
}
// Time is a Starlark representation of a moment in time.
type Time time.Time
func newTime(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var (
year, month, day, hour, min, sec, nsec int
loc string
)
if err := starlark.UnpackArgs("time", args, kwargs,
"year?", &year,
"month?", &month,
"day?", &day,
"hour?", &hour,
"minute?", &min,
"second?", &sec,
"nanosecond?", &nsec,
"location?", &loc,
); err != nil {
return nil, err
}
if len(args) > 0 {
return nil, fmt.Errorf("time: unexpected positional arguments")
}
location, err := time.LoadLocation(loc)
if err != nil {
return nil, err
}
return Time(time.Date(year, time.Month(month), day, hour, min, sec, nsec, location)), nil
}
// String returns the time formatted using the format string
// "2006-01-02 15:04:05.999999999 -0700 MST".
func (t Time) String() string { return time.Time(t).String() }
// Type returns "time.time".
func (t Time) Type() string { return "time.time" }
// Freeze renders time immutable. required by starlark.Value interface
// because Time is already immutable this is a no-op.
func (t Time) Freeze() {}
// Hash returns a function of x such that Equals(x, y) => Hash(x) == Hash(y)
// required by starlark.Value interface.
func (t Time) Hash() (uint32, error) {
return uint32(time.Time(t).UnixNano()) ^ uint32(int64(time.Time(t).UnixNano())>>32), nil
}
// Truth returns the truth value of an object required by starlark.Value
// interface.
func (t Time) Truth() starlark.Bool { return !starlark.Bool(time.Time(t).IsZero()) }
// Attr gets a value for a string attribute, implementing dot expression support
// in starklark. required by starlark.HasAttrs interface.
func (t Time) Attr(name string) (starlark.Value, error) {
switch name {
case "year":
return starlark.MakeInt(time.Time(t).Year()), nil
case "month":
return starlark.MakeInt(int(time.Time(t).Month())), nil
case "day":
return starlark.MakeInt(time.Time(t).Day()), nil
case "hour":
return starlark.MakeInt(time.Time(t).Hour()), nil
case "minute":
return starlark.MakeInt(time.Time(t).Minute()), nil
case "second":
return starlark.MakeInt(time.Time(t).Second()), nil
case "nanosecond":
return starlark.MakeInt(time.Time(t).Nanosecond()), nil
case "unix":
return starlark.MakeInt64(time.Time(t).Unix()), nil
case "unix_nano":
return starlark.MakeInt64(time.Time(t).UnixNano()), nil
}
return builtinAttr(t, name, timeMethods)
}
// AttrNames lists available dot expression strings for time. required by
// starlark.HasAttrs interface.
func (t Time) AttrNames() []string {
return append(builtinAttrNames(timeMethods),
"year",
"month",
"day",
"hour",
"minute",
"second",
"nanosecond",
"unix",
"unix_nano",
)
}
// Cmp implements comparison of two Time values. Required by
// starlark.TotallyOrdered interface.
func (t Time) Cmp(yV starlark.Value, depth int) (int, error) {
x := time.Time(t)
y := time.Time(yV.(Time))
if x.Before(y) {
return -1, nil
} else if x.After(y) {
return 1, nil
}
return 0, nil
}
// Binary implements binary operators, which satisfies the starlark.HasBinary
// interface
// time + duration = time
// time - duration = time
// time - time = duration
func (t Time) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
x := time.Time(t)
switch op {
case syntax.PLUS:
switch y := y.(type) {
case Duration:
return Time(x.Add(time.Duration(y))), nil
}
case syntax.MINUS:
switch y := y.(type) {
case Duration:
return Time(x.Add(time.Duration(-y))), nil
case Time:
// time - time = duration
return Duration(x.Sub(time.Time(y))), nil
}
}
return nil, nil
}
var timeMethods = map[string]builtinMethod{
"in_location": timeIn,
"format": timeFormat,
}
func timeFormat(fnname string, recV starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x string
if err := starlark.UnpackPositionalArgs("format", args, kwargs, 1, &x); err != nil {
return nil, err
}
recv := time.Time(recV.(Time))
return starlark.String(recv.Format(x)), nil
}
func timeIn(fnname string, recV starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var x string
if err := starlark.UnpackPositionalArgs("in_location", args, kwargs, 1, &x); err != nil {
return nil, err
}
loc, err := time.LoadLocation(x)
if err != nil {
return nil, err
}
recv := time.Time(recV.(Time))
return Time(recv.In(loc)), nil
}
type builtinMethod func(fnname string, recv starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error)
func builtinAttr(recv starlark.Value, name string, methods map[string]builtinMethod) (starlark.Value, error) {
method := methods[name]
if method == nil {
return nil, nil // no such method
}
// Allocate a closure over 'method'.
impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
return method(b.Name(), b.Receiver(), args, kwargs)
}
return starlark.NewBuiltin(name, impl).BindReceiver(recv), nil
}
func builtinAttrNames(methods map[string]builtinMethod) []string {
names := make([]string, 0, len(methods))
for name := range methods {
names = append(names, name)
}
sort.Strings(names)
return names
}
@@ -0,0 +1,82 @@
package time
import (
"errors"
"testing"
"time"
"go.starlark.net/starlark"
)
func TestPerThreadNowReturnsCorrectTime(t *testing.T) {
th := &starlark.Thread{}
date := time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)
SetNow(th, func() (time.Time, error) {
return date, nil
})
res, err := starlark.Call(th, Module.Members["now"], nil, nil)
if err != nil {
t.Fatal(err)
}
retTime := time.Time(res.(Time))
if !retTime.Equal(date) {
t.Fatal("Expected time to be equal", retTime, date)
}
}
func TestPerThreadNowReturnsError(t *testing.T) {
th := &starlark.Thread{}
e := errors.New("no time")
SetNow(th, func() (time.Time, error) {
return time.Time{}, e
})
_, err := starlark.Call(th, Module.Members["now"], nil, nil)
if !errors.Is(err, e) {
t.Fatal("Expected equal error", e, err)
}
}
func TestGlobalNowReturnsCorrectTime(t *testing.T) {
th := &starlark.Thread{}
oldNow := NowFunc
defer func() {
NowFunc = oldNow
}()
date := time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)
NowFunc = func() time.Time {
return date
}
res, err := starlark.Call(th, Module.Members["now"], nil, nil)
if err != nil {
t.Fatal(err)
}
retTime := time.Time(res.(Time))
if !retTime.Equal(date) {
t.Fatal("Expected time to be equal", retTime, date)
}
}
func TestGlobalNowReturnsErrorWhenNil(t *testing.T) {
th := &starlark.Thread{}
oldNow := NowFunc
defer func() {
NowFunc = oldNow
}()
NowFunc = nil
_, err := starlark.Call(th, Module.Members["now"], nil, nil)
if err == nil {
t.Fatal("Expected to get an error")
}
}