Skip to content

Commit

Permalink
Started on rust extension impl
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmyaxod committed Sep 21, 2023
1 parent 06b9cde commit df6cbbd
Show file tree
Hide file tree
Showing 36 changed files with 1,271 additions and 1 deletion.
6 changes: 6 additions & 0 deletions extension/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ type Options struct {
GolangPackageImportPath string
GolangPackageName string
GolangPackageVersion string

RustPackageName string
RustPackageVersion string

TypescriptPackageName string
TypescriptPackageVersion string
}

func GenerateGuestLocal(options *Options) (*GuestLocalPackage, error) {
Expand Down
2 changes: 1 addition & 1 deletion extension/generator/golang/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

scaleVersion "github.com/loopholelabs/scale/version"

"github.com/loopholelabs/scale/extension/generator/templates"
"github.com/loopholelabs/scale/extension/generator/golang/templates"
"github.com/loopholelabs/scale/signature/generator/utils"
)

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
279 changes: 279 additions & 0 deletions extension/generator/rust/generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
/*
Copyright 2023 Loophole Labs
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 rust

import (
"bytes"
"context"
"strings"
"text/template"

interfacesVersion "github.com/loopholelabs/scale-extension-interfaces/version"

polyglotVersion "github.com/loopholelabs/polyglot/version"

"github.com/loopholelabs/scale/signature"
scaleVersion "github.com/loopholelabs/scale/version"

polyglotUtils "github.com/loopholelabs/polyglot/utils"

"github.com/loopholelabs/scale/extension"
"github.com/loopholelabs/scale/extension/generator/rust/templates"
"github.com/loopholelabs/scale/signature/generator/rust/format"
"github.com/loopholelabs/scale/signature/generator/utils"
)

const (
defaultPackageName = "types"
)

var generator *Generator

// GenerateTypes generates the types for the extension
func GenerateTypes(extensionSchema *extension.Schema, packageName string) ([]byte, error) {
return generator.GenerateTypes(extensionSchema, packageName)
}

// GenerateCargofile generates the cargo.toml file for the extension
func GenerateCargofile(packageName string, packageVersion string) ([]byte, error) {
return generator.GenerateCargofile(packageName, packageVersion)
}

func GenerateGuest(extensionSchema *extension.Schema, extensionHash string, packageName string) ([]byte, error) {
return generator.GenerateGuest(extensionSchema, extensionHash, packageName)
}

func init() {
var err error
generator, err = New()
if err != nil {
panic(err)
}
}

// Generator is the rust generator
type Generator struct {
templ *template.Template
formatter *format.Formatter
}

// New creates a new rust generator
func New() (*Generator, error) {
templ, err := template.New("").Funcs(templateFunctions()).ParseFS(templates.FS, "*.rs.templ")
if err != nil {
return nil, err
}

formatter, err := format.New()
if err != nil {
return nil, err
}

return &Generator{
templ: templ,
formatter: formatter,
}, nil
}

// GenerateTypes generates the types for the extension
func (g *Generator) GenerateTypes(extensionSchema *extension.Schema, packageName string) ([]byte, error) {
if packageName == "" {
packageName = defaultPackageName
}

buf := new(bytes.Buffer)
err := g.templ.ExecuteTemplate(buf, "types.rs.templ", map[string]any{
"signature_schema": extensionSchema,
"package_name": packageName,
})
if err != nil {
return nil, err
}

formatted, err := g.formatter.Format(context.Background(), buf.String())
if err != nil {
return nil, err
}

buf.Reset()
err = g.templ.ExecuteTemplate(buf, "header.rs.templ", map[string]any{
"generator_version": strings.Trim(scaleVersion.Version(), "v"),
"package_name": packageName,
})
if err != nil {
return nil, err
}
return []byte(buf.String() + "\n\n" + formatted), nil
}

// GenerateCargofile generates the cargofile for the signature
func (g *Generator) GenerateCargofile(packageName string, packageVersion string) ([]byte, error) {
buf := new(bytes.Buffer)
err := g.templ.ExecuteTemplate(buf, "cargo.rs.templ", map[string]any{
"polyglot_version": strings.TrimPrefix(polyglotVersion.Version(), "v"),
"scale_extension_interfaces_version": strings.TrimPrefix(interfacesVersion.Version(), "v"),
"package_name": packageName,
"package_version": strings.TrimPrefix(packageVersion, "v"),
})
if err != nil {
return nil, err
}

return buf.Bytes(), nil
}

// GenerateGuest generates the guest bindings
func (g *Generator) GenerateGuest(extensionSchema *extension.Schema, extensionHash string, packageName string) ([]byte, error) {
if packageName == "" {
packageName = defaultPackageName
}

buf := new(bytes.Buffer)
err := g.templ.ExecuteTemplate(buf, "guest.rs.templ", map[string]any{
"extension_schema": extensionSchema,
"extension_hash": extensionHash,
})
if err != nil {
return nil, err
}

formatted, err := g.formatter.Format(context.Background(), buf.String())
if err != nil {
return nil, err
}

buf.Reset()
err = g.templ.ExecuteTemplate(buf, "header.rs.templ", map[string]any{
"generator_version": strings.TrimPrefix(scaleVersion.Version(), "v"),
"package_name": packageName,
})
if err != nil {
return nil, err
}
return []byte(buf.String() + "\n\n" + formatted), nil
}

func templateFunctions() template.FuncMap {
return template.FuncMap{
"Primitive": primitive,
"IsPrimitive": signature.ValidPrimitiveType,
"PolyglotPrimitive": polyglotPrimitive,
"PolyglotPrimitiveEncode": polyglotPrimitiveEncode,
"PolyglotPrimitiveDecode": polyglotPrimitiveDecode,
"Deref": func(i *bool) bool { return *i },
"LowerFirst": func(s string) string { return string(s[0]+32) + s[1:] },
"SnakeCase": polyglotUtils.SnakeCase,
"Params": utils.Params,
}
}

func primitive(t string) string {
switch t {
case "string":
return "String"
case "int32":
return "i32"
case "int64":
return "i64"
case "uint32":
return "u32"
case "uint64":
return "u64"
case "float32":
return "f32"
case "float64":
return "f64"
case "bool":
return "bool"
case "bytes":
return "Vec<u8>"
default:
return t
}
}

func polyglotPrimitive(t string) string {
switch t {
case "string":
return "Kind::String"
case "int32":
return "Kind::I32"
case "int64":
return "Kind::I64"
case "uint32":
return "Kind::U32"
case "uint64":
return "Kind::U64"
case "float32":
return "Kind::F32"
case "float64":
return "Kind::F64"
case "bool":
return "Kind::Bool"
case "bytes":
return "Kind::Bytes"
default:
return "Kind::Any"
}
}

func polyglotPrimitiveEncode(t string) string {
switch t {
case "string":
return "encode_string"
case "int32":
return "encode_i32"
case "int64":
return "encode_i64"
case "uint32":
return "encode_u32"
case "uint64":
return "encode_u64"
case "float32":
return "encode_f32"
case "float64":
return "encode_f64"
case "bool":
return "encode_bool"
case "bytes":
return "encode_bytes"
default:
return t
}
}

func polyglotPrimitiveDecode(t string) string {
switch t {
case "string":
return "decode_string"
case "int32":
return "decode_i32"
case "int64":
return "decode_i64"
case "uint32":
return "decode_u32"
case "uint64":
return "decode_u64"
case "float32":
return "decode_f32"
case "float64":
return "decode_f64"
case "bool":
return "decode_bool"
case "bytes":
return "decode_bytes"
default:
return ""
}
}
43 changes: 43 additions & 0 deletions extension/generator/rust/generator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//go:build !integration && !generate

/*
Copyright 2023 Loophole Labs
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 rust

import (
"os"
"testing"

"github.com/stretchr/testify/require"

"github.com/loopholelabs/scale/extension"
)

func TestGenerator(t *testing.T) {
s := new(extension.Schema)
err := s.Decode([]byte(extension.MasterTestingSchema))
require.NoError(t, err)

formatted, err := GenerateTypes(s, "types")
require.NoError(t, err)

os.WriteFile("./generated.txt", formatted, 0644)
/*
master, err := os.ReadFile("./generated.txt")
require.NoError(t, err)
require.Equal(t, string(master), string(formatted))
*/
t.Log(string(formatted))

}
37 changes: 37 additions & 0 deletions extension/generator/rust/templates/arrays.rs.templ
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{{ define "rs_arrays_struct_reference" }}
{{ $type := .Type }}
{{- range .Entries }}
{{- if (Deref .Accessor) }}
{{ SnakeCase .Name }}: Vec<{{ Primitive $type }}>,
{{- else }}
pub {{ SnakeCase .Name }}: Vec<{{ Primitive $type }}>,
{{- end -}}
{{- end }}
{{ end }}

{{ define "rs_arrays_new_struct_reference" }}
{{ $type := .Type }}
{{- range .Entries }}
{{ SnakeCase .Name }}: Vec::with_capacity({{ .InitialSize }}),
{{- end }}
{{ end }}

{{ define "rs_arrays_encode" }}
{{ $type := .Type }}
{{- range .Entries }}
e.encode_array(self.{{ SnakeCase .Name }}.len(), {{ PolyglotPrimitive $type }})?;
for a in &self.{{ SnakeCase .Name }} {
e.{{ PolyglotPrimitiveEncode $type }}(*a)?;
}
{{- end }}
{{ end }}

{{ define "rs_arrays_decode" }}
{{ $type := .Type }}
{{- range .Entries }}
let size_{{ SnakeCase .Name }} = d.decode_array({{ PolyglotPrimitive $type }})?;
for _ in 0..size_{{ SnakeCase .Name }} {
x.{{ SnakeCase .Name }}.push(d.{{ PolyglotPrimitiveDecode $type }}()?);
}
{{- end }}
{{ end }}
Loading

0 comments on commit df6cbbd

Please sign in to comment.