diff --git a/internal/executor/check/fp.go b/internal/executor/check/fp.go new file mode 100644 index 0000000..2b2ff09 --- /dev/null +++ b/internal/executor/check/fp.go @@ -0,0 +1,18 @@ +package check + +import ( + "github.com/snowmerak/jetti/v2/lib/model" + "strings" +) + +func HasFp(pkg *model.Package) (bool, error) { + directive := "jetti:fp" + + for _, fn := range pkg.Functions { + if strings.Contains(fn.Doc, directive) { + return true, nil + } + } + + return false, nil +} diff --git a/internal/executor/generate.go b/internal/executor/generate.go index a00697c..2a40caf 100644 --- a/internal/executor/generate.go +++ b/internal/executor/generate.go @@ -4,6 +4,7 @@ import ( "github.com/snowmerak/jetti/v2/internal/cache" "github.com/snowmerak/jetti/v2/internal/executor/check" "github.com/snowmerak/jetti/v2/internal/executor/generate" + "github.com/snowmerak/jetti/v2/internal/executor/generate/fp" "github.com/snowmerak/jetti/v2/lib/parser" "log" "os" @@ -145,6 +146,17 @@ func Generate(root string) error { } log.Printf("generate getter: %s", relativePath) } + + hasFp, err := check.HasFp(pkg) + if err != nil { + return err + } + + if hasFp { + if err := fp.FunctionalProgramming(moduleName, root); err != nil { + return err + } + } case ".json": if err := generate.ConvertJson(path); err != nil { return err diff --git a/internal/executor/generate/fp/functional_programming.go b/internal/executor/generate/fp/functional_programming.go new file mode 100644 index 0000000..909e334 --- /dev/null +++ b/internal/executor/generate/fp/functional_programming.go @@ -0,0 +1,41 @@ +package fp + +import ( + "os" + "path/filepath" +) + +func FunctionalProgramming(moduleName, root string) error { + genPath, err := InitFunctionalProgramming(root) + if err != nil { + return err + } + + if err := MonadOption(genPath); err != nil { + return err + } + + if err := MonadResult(genPath); err != nil { + return err + } + + moduleName = moduleName + "/gen/fp" + + if err := LambdaCond(moduleName, genPath); err != nil { + return err + } + + if err := LambdaWhen(moduleName, genPath); err != nil { + return err + } + + return nil +} + +func InitFunctionalProgramming(root string) (string, error) { + functionalProgrammingPath := filepath.Join(root, "gen", "fp") + if err := os.MkdirAll(functionalProgrammingPath, os.ModePerm); err != nil { + return "", err + } + return functionalProgrammingPath, nil +} diff --git a/internal/executor/generate/fp/lambda.go b/internal/executor/generate/fp/lambda.go new file mode 100644 index 0000000..a7bef20 --- /dev/null +++ b/internal/executor/generate/fp/lambda.go @@ -0,0 +1,178 @@ +package fp + +import ( + "github.com/snowmerak/jetti/v2/internal/executor/generate" + "github.com/snowmerak/jetti/v2/lib/generator" + "github.com/snowmerak/jetti/v2/lib/model" + "os" + "path/filepath" +) + +func LambdaCond(modulePath, genPath string) error { + genPath = filepath.Join(genPath, "cond") + if err := os.MkdirAll(genPath, os.ModePerm); err != nil { + return err + } + f, err := os.Create(generate.MakeGeneratedFileName(genPath, "cond")) + if err != nil { + return err + } + defer func(f *os.File) { + if err := f.Close(); err != nil { + panic(err) + } + }(f) + + pkg := &model.Package{ + Name: "cond", + Imports: []model.Import{ + { + Path: modulePath + "/result", + }, + }, + Functions: []model.Function{ + { + Name: "If[T, R any]", + Params: []model.Field{ + { + Name: "cond", + Type: "bool", + }, + { + Name: "trueFn", + Type: "func() T", + }, + { + Name: "falseFn", + Type: "func() R", + }, + }, + Return: []model.Field{ + { + Type: "result.Result[T, R]", + }, + }, + Code: []string{ + "if cond {", + "return result.Ok[T, R](trueFn())", + "}", + "return result.Err[T, R](falseFn())", + }, + }, + }, + } + + data, err := generator.GenerateFile(pkg) + if err != nil { + return err + } + + if _, err := f.Write(data); err != nil { + return err + } + + return nil +} + +func LambdaWhen(modulePath, genPath string) error { + genPath = filepath.Join(genPath, "when") + if err := os.MkdirAll(genPath, os.ModePerm); err != nil { + return err + } + f, err := os.Create(generate.MakeGeneratedFileName(genPath, "when")) + if err != nil { + return err + } + defer func(f *os.File) { + if err := f.Close(); err != nil { + panic(err) + } + }(f) + + pkg := &model.Package{ + Name: "when", + Imports: []model.Import{ + { + Path: modulePath + "/option", + }, + }, + Structs: []model.Struct{ + { + Name: "Condition[T, R any]", + Fields: []model.Field{ + { + Name: "criteria", + Type: "func (T) bool", + }, + { + Name: "fn", + Type: "func (T) R", + }, + }, + }, + }, + Functions: []model.Function{ + { + Name: "Cond[T, R any]", + Params: []model.Field{ + { + Name: "criteria", + Type: "func (T) bool", + }, + { + Name: "fn", + Type: "func (T) R", + }, + }, + Return: []model.Field{ + { + Type: "Condition[T, R]", + }, + }, + Code: []string{ + "return Condition[T, R]{", + "\tcriteria: criteria,", + "\tfn: fn,", + "}", + }, + }, + { + Name: "When[T, R any]", + Params: []model.Field{ + { + Name: "criteria", + Type: "T", + }, + { + Name: "cond", + Type: "...Condition[T, R]", + }, + }, + Return: []model.Field{ + { + Type: "option.Option[R]", + }, + }, + Code: []string{ + "for _, c := range cond {", + "\tif c.criteria(criteria) {", + "\t\treturn option.Some[R](c.fn(criteria))", + "\t}", + "}", + "return option.None[R]()", + }, + }, + }, + } + + data, err := generator.GenerateFile(pkg) + if err != nil { + return err + } + + if _, err := f.Write(data); err != nil { + return err + } + + return nil +} diff --git a/internal/executor/generate/fp/monad.go b/internal/executor/generate/fp/monad.go new file mode 100644 index 0000000..a6b15a6 --- /dev/null +++ b/internal/executor/generate/fp/monad.go @@ -0,0 +1,387 @@ +package fp + +import ( + "github.com/snowmerak/jetti/v2/internal/executor/generate" + "github.com/snowmerak/jetti/v2/lib/generator" + "github.com/snowmerak/jetti/v2/lib/model" + "os" + "path/filepath" +) + +func MonadOption(genPath string) error { + genPath = filepath.Join(genPath, "option") + if err := os.MkdirAll(genPath, os.ModePerm); err != nil { + return err + } + f, err := os.Create(generate.MakeGeneratedFileName(genPath, "option")) + if err != nil { + return err + } + defer func(f *os.File) { + if err := f.Close(); err != nil { + panic(err) + } + }(f) + + pkg := &model.Package{ + Name: "option", + Structs: []model.Struct{ + { + Name: "Option[T any]", + Fields: []model.Field{ + { + Name: "value", + Type: "any", + }, + }, + }, + }, + Methods: []model.Method{ + { + Name: "Unwrap", + Receiver: model.Field{ + Name: "o", + Type: "Option[T]", + }, + Return: []model.Field{ + { + Type: "T", + }, + }, + Code: []string{ + "if o.value == nil {", + "\tpanic(\"unwrap a nil value\")", + "}", + "r, ok := o.value.(T)", + "if !ok {", + "\tpanic(\"unwrap a invalid value\")", + "}", + "return r", + }, + }, + { + Name: "IsSome", + Receiver: model.Field{ + Name: "o", + Type: "Option[T]", + }, + Return: []model.Field{ + { + Type: "bool", + }, + }, + Code: []string{ + "return o.value != nil", + }, + }, + { + Name: "IsNone", + Receiver: model.Field{ + Name: "o", + Type: "Option[T]", + }, + Return: []model.Field{ + { + Type: "bool", + }, + }, + Code: []string{ + "return o.value == nil", + }, + }, + { + Name: "UnwrapOr", + Receiver: model.Field{ + Name: "o", + Type: "Option[T]", + }, + Params: []model.Field{ + { + Name: "defaultValue", + Type: "T", + }, + }, + Return: []model.Field{ + { + Type: "T", + }, + }, + Code: []string{ + "if o.value == nil {", + "\treturn defaultValue", + "}", + "r, ok := o.value.(T)", + "if !ok {", + "\treturn defaultValue", + "}", + "return r", + }, + }, + }, + Functions: []model.Function{ + { + Name: "Some[T any]", + Params: []model.Field{ + { + Name: "value", + Type: "T", + }, + }, + Return: []model.Field{ + { + Type: "Option[T]", + }, + }, + Code: []string{ + "return Option[T]{", + "\tvalue: value,", + "}", + }, + }, + { + Name: "None[T any]", + Return: []model.Field{ + { + Type: "Option[T]", + }, + }, + Code: []string{ + "return Option[T]{", + "\tvalue: nil,", + "}", + }, + }, + }, + } + + data, err := generator.GenerateFile(pkg) + if err != nil { + return err + } + + if _, err := f.Write(data); err != nil { + return err + } + + return nil +} + +func MonadResult(genPath string) error { + genPath = filepath.Join(genPath, "result") + if err := os.MkdirAll(genPath, os.ModePerm); err != nil { + return err + } + f, err := os.Create(generate.MakeGeneratedFileName(genPath, "result")) + if err != nil { + return err + } + defer func(f *os.File) { + if err := f.Close(); err != nil { + panic(err) + } + }(f) + + pkg := &model.Package{ + Name: "result", + Structs: []model.Struct{ + { + Name: "Result[T, R any]", + Fields: []model.Field{ + { + Name: "value", + Type: "any", + }, + { + Name: "isOk", + Type: "bool", + }, + }, + }, + }, + Methods: []model.Method{ + { + Name: "Unwrap", + Receiver: model.Field{ + Name: "r", + Type: "Result[T, R]", + }, + Return: []model.Field{ + { + Type: "T", + }, + }, + Code: []string{ + "if !r.isOk {", + "\tpanic(\"unwrap a error value\")", + "}", + "v, ok := r.value.(T)", + "if !ok {", + "\tpanic(\"unwrap a invalid value\")", + "}", + "return v", + }, + }, + { + Name: "IsOk", + Receiver: model.Field{ + Name: "r", + Type: "Result[T, R]", + }, + Return: []model.Field{ + { + Type: "bool", + }, + }, + Code: []string{ + "return r.isOk", + }, + }, + { + Name: "IsErr", + Receiver: model.Field{ + Name: "r", + Type: "Result[T, R]", + }, + Return: []model.Field{ + { + Type: "bool", + }, + }, + Code: []string{ + "return !r.isOk", + }, + }, + { + Name: "UnwrapOr", + Receiver: model.Field{ + Name: "r", + Type: "Result[T, R]", + }, + Params: []model.Field{ + { + Name: "defaultValue", + Type: "T", + }, + }, + Return: []model.Field{ + { + Type: "T", + }, + }, + Code: []string{ + "if !r.isOk {", + "\treturn defaultValue", + "}", + "v, ok := r.value.(T)", + "if !ok {", + "\treturn defaultValue", + "}", + "return v", + }, + }, + { + Name: "UnwrapErr", + Receiver: model.Field{ + Name: "r", + Type: "Result[T, R]", + }, + Return: []model.Field{ + { + Type: "R", + }, + }, + Code: []string{ + "if r.isOk {", + "\tpanic(\"unwrap a ok value\")", + "}", + "v, ok := r.value.(R)", + "if !ok {", + "\tpanic(\"unwrap a invalid error\")", + "}", + "return v", + }, + }, + { + Name: "UnwrapErrOr", + Receiver: model.Field{ + Name: "r", + Type: "Result[T, R]", + }, + Params: []model.Field{ + { + Name: "defaultValue", + Type: "R", + }, + }, + Return: []model.Field{ + { + Type: "R", + }, + }, + Code: []string{ + "if r.isOk {", + "\treturn defaultValue", + "}", + "v, ok := r.value.(R)", + "if !ok {", + "\treturn defaultValue", + "}", + "return v", + }, + }, + }, + Functions: []model.Function{ + { + Name: "Ok[T, R any]", + Params: []model.Field{ + { + Name: "value", + Type: "T", + }, + }, + Return: []model.Field{ + { + Type: "Result[T, R]", + }, + }, + Code: []string{ + "return Result[T, R]{", + "\tvalue: value,", + "\tisOk: true,", + "}", + }, + }, + { + Name: "Err[T, R any]", + Params: []model.Field{ + { + Name: "err", + Type: "R", + }, + }, + Return: []model.Field{ + { + Type: "Result[T, R]", + }, + }, + Code: []string{ + "return Result[T, R]{", + "\tvalue: err,", + "\tisOk: false,", + "}", + }, + }, + }, + } + + data, err := generator.GenerateFile(pkg) + if err != nil { + return err + } + + if _, err := f.Write(data); err != nil { + return err + } + + return nil +}