// Copyright (c) 2023 IBM Corp. // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cli import ( "fmt" "log" "os" "path/filepath" "strings" "time" C "github.com/urfave/cli/v2" ) func writeTupleType(f *os.File, symbol string, i int) { fmt.Fprintf(f, "Tuple%d[", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "%s%d", symbol, j) } fmt.Fprintf(f, "]") } func makeTupleType(name string) func(i int) string { return func(i int) string { var buf strings.Builder buf.WriteString(fmt.Sprintf("Tuple%d[", i)) for j := 0; j < i; j++ { if j > 0 { buf.WriteString(", ") } buf.WriteString(fmt.Sprintf("%s%d", name, j+1)) } buf.WriteString("]") return buf.String() } } func generatePush(f *os.File, i int) { tuple1 := makeTupleType("T")(i) tuple2 := makeTupleType("T")(i + 1) // Create the replicate version fmt.Fprintf(f, "\n// Push%d creates a [Tuple%d] from a [Tuple%d] by appending a constant value\n", i, i+1, i) fmt.Fprintf(f, "func Push%d[", i) // function prototypes for j := 0; j <= i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j+1) } fmt.Fprintf(f, " any](value T%d) func(%s) %s {\n", i+1, tuple1, tuple2) fmt.Fprintf(f, " return func(t %s) %s {\n", tuple1, tuple2) fmt.Fprintf(f, " return MakeTuple%d(", i+1) for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t.F%d", j+1) } fmt.Fprintf(f, ", value)\n") fmt.Fprintf(f, " }\n") fmt.Fprintf(f, "}\n") } func generateReplicate(f *os.File, i int) { // Create the replicate version fmt.Fprintf(f, "\n// Replicate%d creates a [Tuple%d] with all fields set to the input value `t`\n", i, i) fmt.Fprintf(f, "func Replicate%d[T any](t T) Tuple%d[", i, i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T") } fmt.Fprintf(f, "] {\n") // execute the mapping fmt.Fprintf(f, " return MakeTuple%d(", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t") } fmt.Fprintf(f, ")\n") fmt.Fprintf(f, "}\n") } func generateMap(f *os.File, i int) { // Create the optionize version fmt.Fprintf(f, "\n// Map%d maps each value of a [Tuple%d] via a mapping function\n", i, i) fmt.Fprintf(f, "func Map%d[", i) // function prototypes for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "F%d ~func(T%d) R%d", j, j, j) } for j := 1; j <= i; j++ { fmt.Fprintf(f, ", T%d, R%d", j, j) } fmt.Fprintf(f, " any](") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "f%d F%d", j, j) } fmt.Fprintf(f, ") func(") writeTupleType(f, "T", i) fmt.Fprintf(f, ") ") writeTupleType(f, "R", i) fmt.Fprintf(f, " {\n") fmt.Fprintf(f, " return func(t ") writeTupleType(f, "T", i) fmt.Fprintf(f, ") ") writeTupleType(f, "R", i) fmt.Fprintf(f, " {\n") // execute the mapping fmt.Fprintf(f, " return MakeTuple%d(\n", i) for j := 1; j <= i; j++ { fmt.Fprintf(f, " f%d(t.F%d),\n", j, j) } fmt.Fprintf(f, " )\n") fmt.Fprintf(f, " }\n") fmt.Fprintf(f, "}\n") } func generateMonoid(f *os.File, i int) { // Create the optionize version fmt.Fprintf(f, "\n// Monoid%d creates a [Monoid] for a [Tuple%d] based on %d monoids for the contained types\n", i, i, i) fmt.Fprintf(f, "func Monoid%d[", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j) } fmt.Fprintf(f, " any](") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "m%d M.Monoid[T%d]", j, j) } fmt.Fprintf(f, ") M.Monoid[") writeTupleType(f, "T", i) fmt.Fprintf(f, "] {\n") fmt.Fprintf(f, " return M.MakeMonoid(func(l, r ") writeTupleType(f, "T", i) fmt.Fprintf(f, ") ") writeTupleType(f, "T", i) fmt.Fprintf(f, "{\n") fmt.Fprintf(f, " return MakeTuple%d(", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "m%d.Concat(l.F%d, r.F%d)", j, j, j) } fmt.Fprintf(f, ")\n") fmt.Fprintf(f, " }, MakeTuple%d(", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "m%d.Empty()", j) } fmt.Fprintf(f, "))\n") fmt.Fprintf(f, "}\n") } func generateOrd(f *os.File, i int) { // Create the optionize version fmt.Fprintf(f, "\n// Ord%d creates n [Ord] for a [Tuple%d] based on %d [Ord]s for the contained types\n", i, i, i) fmt.Fprintf(f, "func Ord%d[", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j) } fmt.Fprintf(f, " any](") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "o%d O.Ord[T%d]", j, j) } fmt.Fprintf(f, ") O.Ord[") writeTupleType(f, "T", i) fmt.Fprintf(f, "] {\n") fmt.Fprintf(f, " return O.MakeOrd(func(l, r ") writeTupleType(f, "T", i) fmt.Fprintf(f, ") int {\n") for j := 1; j <= i; j++ { fmt.Fprintf(f, " if c:= o%d.Compare(l.F%d, r.F%d); c != 0 {return c}\n", j, j, j) } fmt.Fprintf(f, " return 0\n") fmt.Fprintf(f, " }, func(l, r ") writeTupleType(f, "T", i) fmt.Fprintf(f, ") bool {\n") fmt.Fprintf(f, " return ") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, " && ") } fmt.Fprintf(f, "o%d.Equals(l.F%d, r.F%d)", j, j, j) } fmt.Fprintf(f, "\n") fmt.Fprintf(f, " })\n") fmt.Fprintf(f, "}\n") } func generateTupleType(f *os.File, i int) { // Create the optionize version fmt.Fprintf(f, "\n// Tuple%d is a struct that carries %d independently typed values\n", i, i) fmt.Fprintf(f, "type Tuple%d[", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j) } fmt.Fprintf(f, " any] struct {\n") for j := 1; j <= i; j++ { fmt.Fprintf(f, " F%d T%d\n", j, j) } fmt.Fprintf(f, "}\n") } func generateMakeTupleType(f *os.File, i int) { // Create the optionize version fmt.Fprintf(f, "\n// MakeTuple%d is a function that converts its %d parameters into a [Tuple%d]\n", i, i, i) fmt.Fprintf(f, "func MakeTuple%d[", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j) } fmt.Fprintf(f, " any](") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t%d T%d", j, j) } fmt.Fprintf(f, ") ") writeTupleType(f, "T", i) fmt.Fprintf(f, " {\n") fmt.Fprintf(f, " return Tuple%d[", i) for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j) } fmt.Fprintf(f, "]{") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t%d", j) } fmt.Fprintf(f, "}\n") fmt.Fprintf(f, "}\n") } func generateUntupled(f *os.File, i int) { // Create the optionize version fmt.Fprintf(f, "\n// Untupled%d converts a function with a [Tuple%d] parameter into a function with %d parameters\n// The inverse function is [Tupled%d]\n", i, i, i, i) fmt.Fprintf(f, "func Untupled%d[F ~func(Tuple%d[", i, i) for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j+1) } fmt.Fprintf(f, "]) R") for j := 0; j < i; j++ { fmt.Fprintf(f, ", T%d", j+1) } fmt.Fprintf(f, ", R any](f F) func(") for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j+1) } fmt.Fprintf(f, ") R {\n") fmt.Fprintf(f, " return func(") for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t%d T%d", j+1, j+1) } fmt.Fprintf(f, ") R {\n") fmt.Fprintf(f, " return f(MakeTuple%d(", i) for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t%d", j+1) } fmt.Fprintln(f, "))") fmt.Fprintln(f, " }") fmt.Fprintln(f, "}") } func generateTupled(f *os.File, i int) { // Create the optionize version fmt.Fprintf(f, "\n// Tupled%d converts a function with %d parameters into a function taking a Tuple%d\n// The inverse function is [Untupled%d]\n", i, i, i, i) fmt.Fprintf(f, "func Tupled%d[F ~func(", i) for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j+1) } fmt.Fprintf(f, ") R") for j := 0; j < i; j++ { fmt.Fprintf(f, ", T%d", j+1) } fmt.Fprintf(f, ", R any](f F) func(Tuple%d[", i) for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j+1) } fmt.Fprintf(f, "]) R {\n") fmt.Fprintf(f, " return func(t Tuple%d[", i) for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "T%d", j+1) } fmt.Fprintf(f, "]) R {\n") fmt.Fprintf(f, " return f(") for j := 0; j < i; j++ { if j > 0 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t.F%d", j+1) } fmt.Fprintf(f, ")\n") fmt.Fprintf(f, " }\n") fmt.Fprintln(f, "}") } func generateTupleHelpers(filename string, count int) error { dir, err := os.Getwd() if err != nil { return err } absDir, err := filepath.Abs(dir) if err != nil { return err } pkg := filepath.Base(absDir) f, err := os.Create(filepath.Clean(filename)) if err != nil { return err } defer f.Close() // log log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) // some header fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") fmt.Fprintln(f, "// This file was generated by robots at") fmt.Fprintf(f, "// %s\n\n", time.Now()) fmt.Fprintf(f, "package %s\n\n", pkg) fmt.Fprintf(f, ` import ( M "github.com/IBM/fp-go/monoid" O "github.com/IBM/fp-go/ord" ) `) for i := 1; i <= count; i++ { // tuple type generateTupleType(f, i) } for i := 1; i <= count; i++ { // tuple generator generateMakeTupleType(f, i) // tupled wrapper generateTupled(f, i) // untupled wrapper generateUntupled(f, i) // monoid generateMonoid(f, i) // generate order generateOrd(f, i) // generate map generateMap(f, i) // generate replicate generateReplicate(f, i) // generate tuple functions such as string and fmt generateTupleString(f, i) // generate json support generateTupleMarshal(f, i) // generate json support generateTupleUnmarshal(f, i) // generate toArray generateToArray(f, i) // generate fromArray generateFromArray(f, i) // generate push if i < count { generatePush(f, i) } } return nil } func generateTupleMarshal(f *os.File, i int) { // Create the stringify version fmt.Fprintf(f, "\n// MarshalJSON marshals the [Tuple%d] into a JSON array\n", i) fmt.Fprintf(f, "func (t ") writeTupleType(f, "T", i) fmt.Fprintf(f, ") MarshalJSON() ([]byte, error) {\n") fmt.Fprintf(f, " return tupleMarshalJSON(") // function prototypes for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t.F%d", j) } fmt.Fprintf(f, ")\n") fmt.Fprintf(f, "}\n") } func generateTupleUnmarshal(f *os.File, i int) { // Create the stringify version fmt.Fprintf(f, "\n// UnmarshalJSON unmarshals a JSON array into a [Tuple%d]\n", i) fmt.Fprintf(f, "func (t *") writeTupleType(f, "T", i) fmt.Fprintf(f, ") UnmarshalJSON(data []byte) error {\n") fmt.Fprintf(f, " return tupleUnmarshalJSON(data") // function prototypes for j := 1; j <= i; j++ { fmt.Fprintf(f, ", &t.F%d", j) } fmt.Fprintf(f, ")\n") fmt.Fprintf(f, "}\n") } func generateToArray(f *os.File, i int) { // Create the stringify version fmt.Fprintf(f, "\n// ToArray converts the [Tuple%d] into an array of type [R] using %d transformation functions from [T] to [R]\n// The inverse function is [FromArray%d]\n", i, i, i) fmt.Fprintf(f, "func ToArray%d[", i) // function prototypes for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "F%d ~func(T%d) R", j, j) } for j := 1; j <= i; j++ { fmt.Fprintf(f, ", T%d", j) } fmt.Fprintf(f, ", R any](") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "f%d F%d", j, j) } fmt.Fprintf(f, ") func(t ") writeTupleType(f, "T", i) fmt.Fprintf(f, ") []R {\n") fmt.Fprintf(f, " return func(t ") writeTupleType(f, "T", i) fmt.Fprintf(f, ") []R {\n") fmt.Fprintf(f, " return []R{\n") for j := 1; j <= i; j++ { fmt.Fprintf(f, " f%d(t.F%d),\n", j, j) } fmt.Fprintf(f, " }\n") fmt.Fprintf(f, " }\n") fmt.Fprintf(f, "}\n") } func generateFromArray(f *os.File, i int) { // Create the stringify version fmt.Fprintf(f, "\n// FromArray converts an array of [R] into a [Tuple%d] using %d functions from [R] to [T]\n// The inverse function is [ToArray%d]\n", i, i, i) fmt.Fprintf(f, "func FromArray%d[", i) // function prototypes for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "F%d ~func(R) T%d", j, j) } for j := 1; j <= i; j++ { fmt.Fprintf(f, ", T%d", j) } fmt.Fprintf(f, ", R any](") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "f%d F%d", j, j) } fmt.Fprintf(f, ") func(r []R) ") writeTupleType(f, "T", i) fmt.Fprintf(f, " {\n") fmt.Fprintf(f, " return func(r []R) ") writeTupleType(f, "T", i) fmt.Fprintf(f, " {\n") fmt.Fprintf(f, " return MakeTuple%d(\n", i) for j := 1; j <= i; j++ { fmt.Fprintf(f, " f%d(r[%d]),\n", j, j-1) } fmt.Fprintf(f, " )\n") fmt.Fprintf(f, " }\n") fmt.Fprintf(f, "}\n") } func generateTupleString(f *os.File, i int) { // Create the stringify version fmt.Fprintf(f, "\n// String prints some debug info for the [Tuple%d]\n", i) fmt.Fprintf(f, "func (t ") writeTupleType(f, "T", i) fmt.Fprintf(f, ") String() string {\n") // convert to string fmt.Fprint(f, " return tupleString(") for j := 1; j <= i; j++ { if j > 1 { fmt.Fprintf(f, ", ") } fmt.Fprintf(f, "t.F%d", j) } fmt.Fprintf(f, ")\n") fmt.Fprintf(f, "}\n") } // func generateTupleJson(f *os.File, i int) { // // Create the stringify version // fmt.Fprintf(f, "\n// MarshalJSON converts the [Tuple%d] into a JSON byte stream\n", i) // fmt.Fprintf(f, "func (t ") // writeTupleType(f, "T", i) // fmt.Fprintf(f, ") MarshalJSON() ([]byte, error) {\n") // // convert to string // fmt.Fprintf(f, " return fmt.Sprintf(\"Tuple%d[", i) // for j := 1; j <= i; j++ { // if j > 1 { // fmt.Fprintf(f, ", ") // } // fmt.Fprintf(f, "%s", "%T") // } // fmt.Fprintf(f, "](") // for j := 1; j <= i; j++ { // if j > 1 { // fmt.Fprintf(f, ", ") // } // fmt.Fprintf(f, "%s", "%v") // } // fmt.Fprintf(f, ")\", ") // for j := 1; j <= i; j++ { // if j > 1 { // fmt.Fprintf(f, ", ") // } // fmt.Fprintf(f, "t.F%d", j) // } // for j := 1; j <= i; j++ { // fmt.Fprintf(f, ", t.F%d", j) // } // fmt.Fprintf(f, ")\n") // fmt.Fprintf(f, "}\n") // } func TupleCommand() *C.Command { return &C.Command{ Name: "tuple", Usage: "generate code for Tuple", Flags: []C.Flag{ flagCount, flagFilename, }, Action: func(ctx *C.Context) error { return generateTupleHelpers( ctx.String(keyFilename), ctx.Int(keyCount), ) }, } }