Skip to content

Commit

Permalink
Merge pull request #697 from vmware-tanzu/amend-math-module
Browse files Browse the repository at this point in the history
Warn callers math functions are not necessarily deterministic
  • Loading branch information
pivotaljohn authored Jul 1, 2022
2 parents d252642 + 8aca177 commit 813e88e
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 64 deletions.
6 changes: 3 additions & 3 deletions pkg/workspace/template_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (l *TemplateLoader) EvalYAML(libraryCtx LibraryExecutionContext, file *file

yttLibrary := yttlibrary.NewAPI(compiledTemplate.TplReplaceNode,
yttlibrary.NewDataModule(l.values.Doc, DataLoader{libraryCtx}),
NewLibraryModule(libraryCtx, l.libraryExecFactory, l.libraryValuess, l.librarySchemas).AsModule())
NewLibraryModule(libraryCtx, l.libraryExecFactory, l.libraryValuess, l.librarySchemas).AsModule(), l.ui)

thread := l.newThread(libraryCtx, yttLibrary, file)

Expand Down Expand Up @@ -238,7 +238,7 @@ func (l *TemplateLoader) EvalText(libraryCtx LibraryExecutionContext, file *file

yttLibrary := yttlibrary.NewAPI(compiledTemplate.TplReplaceNode,
yttlibrary.NewDataModule(l.values.Doc, DataLoader{libraryCtx}),
NewLibraryModule(libraryCtx, l.libraryExecFactory, l.libraryValuess, l.librarySchemas).AsModule())
NewLibraryModule(libraryCtx, l.libraryExecFactory, l.libraryValuess, l.librarySchemas).AsModule(), l.ui)

thread := l.newThread(libraryCtx, yttLibrary, file)

Expand Down Expand Up @@ -268,7 +268,7 @@ func (l *TemplateLoader) EvalStarlark(libraryCtx LibraryExecutionContext, file *

yttLibrary := yttlibrary.NewAPI(compiledTemplate.TplReplaceNode,
yttlibrary.NewDataModule(l.values.Doc, DataLoader{libraryCtx}),
NewLibraryModule(libraryCtx, l.libraryExecFactory, l.libraryValuess, l.librarySchemas).AsModule())
NewLibraryModule(libraryCtx, l.libraryExecFactory, l.libraryValuess, l.librarySchemas).AsModule(), l.ui)

thread := l.newThread(libraryCtx, yttLibrary, file)

Expand Down
2 changes: 1 addition & 1 deletion pkg/yamltemplate/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func (l stdTemplateLoader) FindCompiledTemplate(_ string) *template.CompiledTemp

func (l stdTemplateLoader) Load(thread *starlark.Thread, module string) (starlark.StringDict, error) {
api := yttlibrary.NewAPI(l.compiledTemplate.TplReplaceNode,
yttlibrary.NewDataModule(defaultInput(), nil), nil)
yttlibrary.NewDataModule(defaultInput(), nil), nil, nil)
return api.FindModule(strings.TrimPrefix(module, "@ytt:"))
}

Expand Down
11 changes: 8 additions & 3 deletions pkg/yttlibrary/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/k14s/starlark-go/starlark"
"github.com/k14s/starlark-go/starlarkstruct"
"github.com/vmware-tanzu/carvel-ytt/pkg/cmd/ui"
tplcore "github.com/vmware-tanzu/carvel-ytt/pkg/template/core"
"github.com/vmware-tanzu/carvel-ytt/pkg/yttlibrary/overlay"
)
Expand Down Expand Up @@ -35,12 +36,16 @@ type API struct {
modules map[string]starlark.StringDict
}

func NewAPI(replaceNodeFunc tplcore.StarlarkFunc, dataMod DataModule,
libraryMod starlark.StringDict) API {
// NewAPI builds an API instance to be used in executing a template.
func NewAPI(
replaceNodeFunc tplcore.StarlarkFunc,
dataMod DataModule,
libraryMod starlark.StringDict,
ui ui.UI) API {

std := map[string]starlark.StringDict{
"assert": AssertAPI,
"math": MathAPI,
"math": NewMathModule(ui).AsModule(),
"regexp": RegexpAPI,

// Hashes
Expand Down
141 changes: 84 additions & 57 deletions pkg/yttlibrary/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ package yttlibrary

import (
"fmt"
"math"

"github.com/k14s/starlark-go/starlark"
"github.com/k14s/starlark-go/starlarkstruct"
"github.com/vmware-tanzu/carvel-ytt/pkg/cmd/ui"
"github.com/vmware-tanzu/carvel-ytt/pkg/template/core"
"math"
)

// MathAPI contains the definition of the @ytt:math module.
// MathModule contains the definition of the @ytt:math module.
// It contains math-related functions and constants.
// The module defines the following functions:
//
Expand Down Expand Up @@ -91,57 +93,71 @@ import (
// e - The base of natural logarithms, approximately 2.71828.
// pi - The ratio of a circle's circumference to its diameter, approximately 3.14159.
//
var MathAPI = starlark.StringDict{
"math": &starlarkstruct.Module{
Name: "math",
Members: starlark.StringDict{
"ceil": starlark.NewBuiltin("ceil", core.ErrWrapper(mathModule{}.ceil)),
"copysign": mathModule{}.newBinaryBuiltin("copysign", math.Copysign),
"fabs": mathModule{}.newUnaryBuiltin("fabs", math.Abs),
"floor": starlark.NewBuiltin("floor", core.ErrWrapper(mathModule{}.floor)),
"mod": mathModule{}.newBinaryBuiltin("round", math.Mod),
"pow": mathModule{}.newBinaryBuiltin("pow", math.Pow),
"remainder": mathModule{}.newBinaryBuiltin("remainder", math.Remainder),
"round": mathModule{}.newUnaryBuiltin("round", math.Round),

"exp": mathModule{}.newUnaryBuiltin("exp", math.Exp),
"sqrt": mathModule{}.newUnaryBuiltin("sqrt", math.Sqrt),

"acos": mathModule{}.newUnaryBuiltin("acos", math.Acos),
"asin": mathModule{}.newUnaryBuiltin("asin", math.Asin),
"atan": mathModule{}.newUnaryBuiltin("atan", math.Atan),
"atan2": mathModule{}.newBinaryBuiltin("atan2", math.Atan2),
"cos": mathModule{}.newUnaryBuiltin("cos", math.Cos),
"hypot": mathModule{}.newBinaryBuiltin("hypot", math.Hypot),
"sin": mathModule{}.newUnaryBuiltin("sin", math.Sin),
"tan": mathModule{}.newUnaryBuiltin("tan", math.Tan),

"degrees": mathModule{}.newUnaryBuiltin("degrees", mathModule{}.degrees),
"radians": mathModule{}.newUnaryBuiltin("radians", mathModule{}.radians),

"acosh": mathModule{}.newUnaryBuiltin("acosh", math.Acosh),
"asinh": mathModule{}.newUnaryBuiltin("asinh", math.Asinh),
"atanh": mathModule{}.newUnaryBuiltin("atanh", math.Atanh),
"cosh": mathModule{}.newUnaryBuiltin("cosh", math.Cosh),
"sinh": mathModule{}.newUnaryBuiltin("sinh", math.Sinh),
"tanh": mathModule{}.newUnaryBuiltin("tanh", math.Tanh),

"log": starlark.NewBuiltin("log", mathModule{}.log),

"gamma": mathModule{}.newUnaryBuiltin("gamma", math.Gamma),

"e": starlark.Float(math.E),
"pi": starlark.Float(math.Pi),
},
},
type MathModule struct {
ui ui.UI
}

// hasWarned indicates whether the caveat associated with using this module has been displayed.
// This flag ensures we display that warning only once; more than once and its noise.
var hasWarned bool

// NewMathModule constructs a new instance of MathModule with the configured UI (to enable displaying a warning).
func NewMathModule(ui ui.UI) MathModule {
return MathModule{ui: ui}
}

type mathModule struct{}
// AsModule produces the corresponding Starlark module definition suitable for use in running a Starlark program.
func (m MathModule) AsModule() starlark.StringDict {
return starlark.StringDict{
"math": &starlarkstruct.Module{
Name: "math",
Members: starlark.StringDict{
"ceil": starlark.NewBuiltin("ceil", m.warnOnCall(core.ErrWrapper(m.ceil))),
"copysign": m.newBinaryBuiltin("copysign", math.Copysign),
"fabs": m.newUnaryBuiltin("fabs", math.Abs),
"floor": starlark.NewBuiltin("floor", m.warnOnCall(core.ErrWrapper(m.floor))),
"mod": m.newBinaryBuiltin("round", math.Mod),
"pow": m.newBinaryBuiltin("pow", math.Pow),
"remainder": m.newBinaryBuiltin("remainder", math.Remainder),
"round": m.newUnaryBuiltin("round", math.Round),

"exp": m.newUnaryBuiltin("exp", math.Exp),
"sqrt": m.newUnaryBuiltin("sqrt", math.Sqrt),

"acos": m.newUnaryBuiltin("acos", math.Acos),
"asin": m.newUnaryBuiltin("asin", math.Asin),
"atan": m.newUnaryBuiltin("atan", math.Atan),
"atan2": m.newBinaryBuiltin("atan2", math.Atan2),
"cos": m.newUnaryBuiltin("cos", math.Cos),
"hypot": m.newBinaryBuiltin("hypot", math.Hypot),
"sin": m.newUnaryBuiltin("sin", math.Sin),
"tan": m.newUnaryBuiltin("tan", math.Tan),

"degrees": m.newUnaryBuiltin("degrees", m.degrees),
"radians": m.newUnaryBuiltin("radians", m.radians),

"acosh": m.newUnaryBuiltin("acosh", math.Acosh),
"asinh": m.newUnaryBuiltin("asinh", math.Asinh),
"atanh": m.newUnaryBuiltin("atanh", math.Atanh),
"cosh": m.newUnaryBuiltin("cosh", math.Cosh),
"sinh": m.newUnaryBuiltin("sinh", math.Sinh),
"tanh": m.newUnaryBuiltin("tanh", math.Tanh),

"log": starlark.NewBuiltin("log", m.warnOnCall(m.log)),

"gamma": m.newUnaryBuiltin("gamma", math.Gamma),

"e": starlark.Float(math.E),
"pi": starlark.Float(math.Pi),
},
},
}
}

// newUnaryBuiltin wraps a unary floating-point Go function
// as a Starlark built-in that accepts int or float arguments.
func (b mathModule) newUnaryBuiltin(name string, fn func(float64) float64) *starlark.Builtin {
return starlark.NewBuiltin(name, core.ErrWrapper(func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
func (m MathModule) newUnaryBuiltin(name string, fn func(float64) float64) *starlark.Builtin {
return starlark.NewBuiltin(name, m.warnOnCall(core.ErrWrapper(func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if args.Len() != 1 {
return starlark.None, fmt.Errorf("expected exactly one argument")
}
Expand All @@ -152,13 +168,13 @@ func (b mathModule) newUnaryBuiltin(name string, fn func(float64) float64) *star
}

return starlark.Float(fn(x)), nil
}))
})))
}

// newBinaryBuiltin wraps a binary floating-point Go function
// as a Starlark built-in that accepts int or float arguments.
func (b mathModule) newBinaryBuiltin(name string, fn func(float64, float64) float64) *starlark.Builtin {
return starlark.NewBuiltin(name, core.ErrWrapper(func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
func (m MathModule) newBinaryBuiltin(name string, fn func(float64, float64) float64) *starlark.Builtin {
return starlark.NewBuiltin(name, m.warnOnCall(core.ErrWrapper(func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if args.Len() != 2 {
return starlark.None, fmt.Errorf("expected exactly two arguments")
}
Expand All @@ -173,12 +189,23 @@ func (b mathModule) newBinaryBuiltin(name string, fn func(float64, float64) floa
return starlark.None, err
}
return starlark.Float(fn(x, y)), nil
}))
})))
}

// warnOnCall ensures that if any wrapped function is called, the user is warned that the execution is no longer guaranteed to be deterministic.
func (m MathModule) warnOnCall(wrappedFunc core.StarlarkFunc) core.StarlarkFunc {
return func(thread *starlark.Thread, f *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (val starlark.Value, resultErr error) {
if !hasWarned {
m.ui.Warnf("\nWarning: a function from the @ytt:math module is used in this invocation; this module does not guarantee bit-identical results across CPU architectures.\n")
hasWarned = true
}
return wrappedFunc(thread, f, args, kwargs)
}
}

// log wraps the Log function
// as a Starlark built-in that accepts int or float arguments.
func (b mathModule) log(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
func (m MathModule) log(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var (
xValue starlark.Value
baseValue starlark.Value = starlark.Float(math.E)
Expand All @@ -200,7 +227,7 @@ func (b mathModule) log(_ *starlark.Thread, _ *starlark.Builtin, args starlark.T
return starlark.Float(math.Log(x) / math.Log(base)), nil
}

func (b mathModule) ceil(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
func (m MathModule) ceil(_ *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 {
Expand All @@ -217,7 +244,7 @@ func (b mathModule) ceil(_ *starlark.Thread, _ *starlark.Builtin, args starlark.
return nil, fmt.Errorf("expected float value, but was %T", x)
}

func (b mathModule) floor(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
func (m MathModule) floor(_ *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 {
Expand All @@ -234,10 +261,10 @@ func (b mathModule) floor(_ *starlark.Thread, _ *starlark.Builtin, args starlark
return nil, fmt.Errorf("expected float value, but was %T", x)
}

func (b mathModule) degrees(x float64) float64 {
func (m MathModule) degrees(x float64) float64 {
return 360 * x / (2 * math.Pi)
}

func (b mathModule) radians(x float64) float64 {
func (m MathModule) radians(x float64) float64 {
return 2 * math.Pi * x / 360
}

0 comments on commit 813e88e

Please sign in to comment.