whatcanGOwrong
This commit is contained in:
@@ -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
|
||||
}
|
||||
+143
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user