Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 8 additions & 125 deletions abi/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,150 +3,33 @@
package main

import (
"fmt"
"go/format"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"

"github.com/tonkeeper/tongo/abi/parser"
)

const HEADER = `package abi
// Code autogenerated. DO NOT EDIT.

import (
%v
)

`
const SCHEMAS_PATH = "schemas/"

func mergeMethods(methods []parser.GetMethod) ([]parser.GetMethod, error) {
methodsMap := map[string]parser.GetMethod{}
var golangNamedMethods []parser.GetMethod
for _, method := range methods {
current, ok := methodsMap[method.Name]
if !ok {
methodsMap[method.Name] = method
continue
}
if len(current.Input.StackValues) > 0 || len(method.Input.StackValues) > 0 {
return nil, fmt.Errorf("method '%s' has a version with input params, it has to be defined with golang_name to avoid collision", method.Name)
}
current.Output = append(current.Output, method.Output...)
methodsMap[method.Name] = current
}
var results []parser.GetMethod
for _, method := range methodsMap {
results = append(results, method)
}
results = append(results, golangNamedMethods...)
sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})
return results, nil
}
const schemasPath = "schemas/"

func main() {
var abi parser.ABI
var methods []parser.GetMethod
filepath.Walk(SCHEMAS_PATH, func(path string, info fs.FileInfo, err error) error {
if !strings.HasSuffix(info.Name(), ".xml") {
return nil
}
scheme, err := os.ReadFile(path)
if err != nil {
panic(err)
var xmlFiles []string
filepath.Walk(schemasPath, func(path string, info fs.FileInfo, err error) error {
if strings.HasSuffix(info.Name(), ".xml") {
xmlFiles = append(xmlFiles, path)
}
a, err := parser.ParseABI(scheme)
if err != nil {
panic(err)
}
methods = append(methods, a.Methods...)
abi.ExtOut = append(abi.ExtOut, a.ExtOut...)
abi.ExtIn = append(abi.ExtIn, a.ExtIn...)
abi.Internals = append(abi.Internals, a.Internals...)
abi.JettonPayloads = append(abi.JettonPayloads, a.JettonPayloads...)
abi.NFTPayloads = append(abi.NFTPayloads, a.NFTPayloads...)
abi.Types = append(abi.Types, a.Types...)
abi.Interfaces = append(abi.Interfaces, a.Interfaces...)
return nil
})

methods, err := mergeMethods(methods)
if err != nil {
panic(err)
}
abi.Methods = methods

gen, err := parser.NewGenerator(nil, abi)
files, err := parser.Generate(xmlFiles, parser.GenerateOptions{})
if err != nil {
panic(err)
}

types := gen.CollectedTypes()
msgDecoder := gen.GenerateMsgDecoder()

getMethods, simpleMethods, err := gen.GetMethods()
if err != nil {
panic(err)
}
invocationOrder, err := gen.RenderInvocationOrderList(simpleMethods)
if err != nil {
panic(err)
}
messagesMD, err := gen.RenderMessagesMD()
if err != nil {
panic(err)
}

jettons, err := gen.RenderJetton()
if err != nil {
panic(err)
}

nfts, err := gen.RenderNFT()
if err != nil {
panic(err)
}

contractErrors, err := gen.RenderContractErrors()
if err != nil {
panic(err)
}

for _, f := range [][]string{
{types, "types.go", `"github.com/tonkeeper/tongo/tlb"`, `"fmt"`, `"encoding/json"`},
{msgDecoder, "messages_generated.go", `"github.com/tonkeeper/tongo/tlb"`},
{getMethods, "get_methods.go", `"context"`, `"fmt"`, `"github.com/tonkeeper/tongo/ton"`, `"github.com/tonkeeper/tongo/boc"`, `"github.com/tonkeeper/tongo/tlb"`},
{invocationOrder, "interfaces.go", `"github.com/tonkeeper/tongo/ton"`},
{jettons, "jetton_msg_types.go", `"github.com/tonkeeper/tongo/boc"`, `"github.com/tonkeeper/tongo/tlb"`},
{nfts, "nfts_msg_types.go", `"github.com/tonkeeper/tongo/boc"`, `"github.com/tonkeeper/tongo/tlb"`},
{contractErrors, "contracts_errors.go"},
} {
file, err := os.Create(f[1])
if err != nil {
panic(err)
}
code := []byte(fmt.Sprintf(HEADER, strings.Join(f[2:], "\n")) + f[0])
formatedCode, err := format.Source(code)
if err != nil {
formatedCode = code
//panic(err)
}
_, err = file.Write(formatedCode)
if err != nil {
panic(err)
}
err = file.Close()
if err != nil {
for filename, content := range files {
if err := os.WriteFile(filename, content, 0644); err != nil {
panic(err)
}
}
if err := os.WriteFile("messages.md", []byte(messagesMD), 0644); err != nil {
panic(err)
}
}
174 changes: 174 additions & 0 deletions abi/parser/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package parser

import (
"fmt"
"go/format"
"os"
"sort"
"strings"
)

// GenerateOptions configures code generation.
type GenerateOptions struct {
// PackageName is the package name for generated code. Defaults to "abi".
PackageName string
}

// GeneratedFiles maps filenames to their formatted Go code content.
type GeneratedFiles map[string][]byte

const defaultPackageName = "abi"

const headerTemplate = `package %s
// Code autogenerated. DO NOT EDIT.

import (
%v
)

`

// Generate parses XML ABI files and generates Go code.
// Returns a map of filename to formatted Go source code.
func Generate(xmlFiles []string, opts GenerateOptions) (GeneratedFiles, error) {
if opts.PackageName == "" {
opts.PackageName = defaultPackageName
}

abi, err := parseAndMergeABIs(xmlFiles)
if err != nil {
return nil, fmt.Errorf("Generate() failed: %v", err)
}

gen, err := NewGenerator(nil, abi)
if err != nil {
return nil, fmt.Errorf("Generate() failed: %v", err)
}

types := gen.CollectedTypes()
msgDecoder := gen.GenerateMsgDecoder()

getMethods, simpleMethods, err := gen.GetMethods()
if err != nil {
return nil, fmt.Errorf("Generate() failed: %v", err)
}

invocationOrder, err := gen.RenderInvocationOrderList(simpleMethods)
if err != nil {
return nil, fmt.Errorf("Generate() failed: %v", err)
}

jettons, err := gen.RenderJetton()
if err != nil {
return nil, fmt.Errorf("Generate() failed: %v", err)
}

nfts, err := gen.RenderNFT()
if err != nil {
return nil, fmt.Errorf("Generate() failed: %v", err)
}

contractErrors, err := gen.RenderContractErrors()
if err != nil {
return nil, fmt.Errorf("Generate() failed: %v", err)
}

messagesMD, err := gen.RenderMessagesMD()
if err != nil {
return nil, fmt.Errorf("Generate() failed: %v", err)
}

files := []struct {
content string
filename string
imports []string
}{
{types, "types.go", []string{`"github.com/tonkeeper/tongo/tlb"`, `"fmt"`, `"encoding/json"`}},
{msgDecoder, "messages_generated.go", []string{`"github.com/tonkeeper/tongo/tlb"`}},
{getMethods, "get_methods.go", []string{`"context"`, `"fmt"`, `"github.com/tonkeeper/tongo/ton"`, `"github.com/tonkeeper/tongo/boc"`, `"github.com/tonkeeper/tongo/tlb"`}},
{invocationOrder, "interfaces.go", []string{`"github.com/tonkeeper/tongo/ton"`}},
{jettons, "jetton_msg_types.go", []string{`"github.com/tonkeeper/tongo/boc"`, `"github.com/tonkeeper/tongo/tlb"`}},
{nfts, "nfts_msg_types.go", []string{`"github.com/tonkeeper/tongo/boc"`, `"github.com/tonkeeper/tongo/tlb"`}},
{contractErrors, "contracts_errors.go", nil},
}

result := make(GeneratedFiles)
for _, f := range files {
header := fmt.Sprintf(headerTemplate, opts.PackageName, strings.Join(f.imports, "\n"))
code := []byte(header + f.content)

formatted, err := format.Source(code)
if err != nil {
formatted = code // keep unformatted if formatting fails
}
result[f.filename] = formatted
}

result["messages.md"] = []byte(messagesMD)

return result, nil
}

func parseAndMergeABIs(xmlFiles []string) (ABI, error) {
var abi ABI
var methods []GetMethod

for _, path := range xmlFiles {
data, err := os.ReadFile(path)
if err != nil {
return ABI{}, fmt.Errorf("parseAndMergeABIs() failed reading %s: %v", path, err)
}

a, err := ParseABI(data)
if err != nil {
return ABI{}, fmt.Errorf("parseAndMergeABIs() failed parsing %s: %v", path, err)
}

methods = append(methods, a.Methods...)
abi.ExtOut = append(abi.ExtOut, a.ExtOut...)
abi.ExtIn = append(abi.ExtIn, a.ExtIn...)
abi.Internals = append(abi.Internals, a.Internals...)
abi.JettonPayloads = append(abi.JettonPayloads, a.JettonPayloads...)
abi.NFTPayloads = append(abi.NFTPayloads, a.NFTPayloads...)
abi.Types = append(abi.Types, a.Types...)
abi.Interfaces = append(abi.Interfaces, a.Interfaces...)
}

merged, err := mergeMethods(methods)
if err != nil {
return ABI{}, fmt.Errorf("parseAndMergeABIs() failed: %v", err)
}
abi.Methods = merged

return abi, nil
}

func mergeMethods(methods []GetMethod) ([]GetMethod, error) {
methodsMap := map[string]GetMethod{}
var golangNamedMethods []GetMethod

for _, method := range methods {
current, ok := methodsMap[method.Name]
if !ok {
methodsMap[method.Name] = method
continue
}
if len(current.Input.StackValues) > 0 || len(method.Input.StackValues) > 0 {
return nil, fmt.Errorf("method '%s' has a version with input params, it has to be defined with golang_name to avoid collision", method.Name)
}
current.Output = append(current.Output, method.Output...)
methodsMap[method.Name] = current
}

var results []GetMethod
for _, method := range methodsMap {
results = append(results, method)
}
results = append(results, golangNamedMethods...)

sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
})

return results, nil
}
Loading